[automerger skipped] dns_tls_frontend.cpp: set queries_ to 0 in startServer
am: 589bd52d90 -s ours
am skip reason: change_id I9ce9314c34420b346703500f4120304dfa58b9af with SHA1 7184fb338c is in history

Change-Id: I73fe17e72d126747ae633da3c9ae88fe4ab83d91
diff --git a/.clang-format b/.clang-format
new file mode 120000
index 0000000..ddcf5a2
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1 @@
+../../build/soong/scripts/system-clang-format
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..9777c60
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+# To use this config with your editor, follow the instructions at:
+# https://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+insert_final_newline = true
+end_of_line = lf
+
+[*.{c,h,cpp,hpp,cc,sh,bp}]
+indent_style = space
+indent_size = 4
diff --git a/Android.bp b/Android.bp
index 8e6ca0f..114b2e1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3,4 +3,40 @@
     export_include_dirs: ["include"],
 }
 
-subdirs = ["*"]
+cc_defaults {
+    name: "netd_defaults",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        // Override -Wno-error=implicit-fallthrough from soong
+        "-Werror=implicit-fallthrough",
+        "-Wnullable-to-nonnull-conversion",
+        "-Wsign-compare",
+        "-Wthread-safety",
+        "-Wunused-parameter",
+        "-Wuninitialized",
+    ],
+    tidy: true,
+    tidy_checks: [
+        "android-*",
+        "cert-*",
+        "clang-analyzer-security*",
+        "google-*",
+        "misc-*",
+        "performance-*",
+        "-cert-err34-c",  // TODO: re-enable after removing atoi() and sscanf() calls
+        "-google-readability-*",  // Too pedantic
+        "-google-runtime-int",  // Too many unavoidable warnings due to strtol()
+        "-google-runtime-references",  // Grandfathered usage of pass by non-const reference
+        "-misc-non-private-member-variables-in-classes",  // Also complains about structs
+    ],
+    tidy_flags: [
+        "-warnings-as-errors="
+        + "'android-*'"
+        + ",'clang-analyzer-security*'"
+        + ",'cert-*'"
+        + ",'google-*'"
+        + ",'performance-*'"
+        + ",'misc-*'"
+    ],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 5053e7d..0000000
--- a/Android.mk
+++ /dev/null
@@ -1 +0,0 @@
-include $(call all-subdir-makefiles)
diff --git a/OWNERS b/OWNERS
index c49a3a9..107eefc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,9 @@
-ek@google.com
+cken@google.com
+codewiz@google.com
+huangluke@google.com
 jchalard@google.com
 lorenzo@google.com
+maze@google.com
+reminv@google.com
 satk@google.com
+xiaom@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..c8dbf77
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..2d3673c
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+    "presubmit": [
+        { "name": "netd_integration_test" },
+        { "name": "netd_unit_test" },
+        { "name": "libnetdbpf_test" },
+        { "name": "netdutils_test" },
+        { "name": "resolv_integration_test" },
+        { "name": "resolv_unit_test" }
+    ]
+}
diff --git a/apex/Android.bp b/apex/Android.bp
new file mode 100644
index 0000000..c1d33e1
--- /dev/null
+++ b/apex/Android.bp
@@ -0,0 +1,41 @@
+// 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.
+
+
+apex {
+    name: "com.android.resolv",
+    manifest: "manifest.json",
+    multilib: {
+        first: {
+             native_shared_libs: ["libnetd_resolv"],
+        },
+    },
+    key: "com.android.resolv.key",
+    certificate: ":com.android.resolv.certificate",
+
+    // Use a custom AndroidManifest.xml used for API targeting.
+    androidManifest: "AndroidManifest.xml",
+}
+
+apex_key {
+    name: "com.android.resolv.key",
+    public_key: "com.android.resolv.avbpubkey",
+    private_key: "com.android.resolv.pem",
+}
+
+android_app_certificate {
+     name: "com.android.resolv.certificate",
+     // will use cert.pk8 and cert.x509.pem
+     certificate: "testcert",
+}
diff --git a/apex/AndroidManifest.xml b/apex/AndroidManifest.xml
new file mode 100644
index 0000000..9d6455c
--- /dev/null
+++ b/apex/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.android.resolv">
+  <!-- APEX does not have classes.dex -->
+  <application android:hasCode="false" />
+  <!--
+    * The interface between the resolver and netd is backwards compatible, so the resolver APEX
+    * can work with any version of netd.
+    -->
+  <uses-sdk
+      android:targetSdkVersion="29"
+  />
+</manifest>
diff --git a/apex/OWNERS b/apex/OWNERS
new file mode 100644
index 0000000..bc97a1b
--- /dev/null
+++ b/apex/OWNERS
@@ -0,0 +1,4 @@
+chenbruce@google.com
+codewiz@google.com
+martinwu@google.com
+
diff --git a/apex/com.android.resolv.avbpubkey b/apex/com.android.resolv.avbpubkey
new file mode 100644
index 0000000..e0af34c
--- /dev/null
+++ b/apex/com.android.resolv.avbpubkey
Binary files differ
diff --git a/apex/com.android.resolv.pem b/apex/com.android.resolv.pem
new file mode 100644
index 0000000..6eb688d
--- /dev/null
+++ b/apex/com.android.resolv.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJJwIBAAKCAgEAqcW/Lsj7Ftp8jUpWaW82J+uSpIlcs5w/vsIBEPW/nrakXvLG
+GBA1gauamV+2hvQ76CIkXqsYeaBl8jo1JZuF+ek0tEtHnvMCuGWZZmJSni9CWdXa
+GjQf74+HD0LcVYZd07xjW4aR42httOq9S9zGEucQHaTSQ0C0JBRFMUaAva7QJG15
+w0oqogze7TKTxO2aXZ9R1n3STprY+dv5FXcgGNraAZiSZRYc2FDvgE3J1V1PrdV8
+9RZv6133Iz5eINxR/6MTo2yxNxwZ6gsLD72gTX4N7nc9xtbC9WXCi8Uf7b6dhfyf
+zwZfJSKnwHh6EMa1AdIEgSSdIjbbcCP5mFscbvzUWNuAtUn8RWXUFOF5ssWsTMQC
+QtmfAnbVYH3oMxv3HBlDzS4CVMBmIBUZDbtvpPdQYBJPA/l/kU4DVjCv6Kpgx5Z3
+tVXVA9elta6eZ2dXL7aOPeOJS7OwP6nnEZr/7JJ8NRtQNOwRUU1xRcTxkwRmr/2A
+4h2qoYvxCOebqijvobKzB9cCzM5PvQzLA2aF7yohMN24HNNcTaybeBY6yUGd/iaD
+q0xyD8t7XyoEpMs7SOKkVp08G/kpp7hxRWUzADhhYrvMEAIsyIxX5jSTZYtvYLU/
+UMbfpUNE3qQEYNMv6d5+/Psv8EAjQA5mkNiSY/VWWnPkWapha8MAvYA9nTcCAwEA
+AQKCAgBKG8gY5CK3FyjHhGu0GpfJVUvuBwgwX0R0QLu2a4/5+EBIMjHGz0yMDhyu
+VtmWj4aXDlBSAt3sOdhGpKIOiJUzQll/Wl0pRWsqky8jQkhORNLx5CgQyDAoor6S
+Kt2Dd+P9SX8VtOh6HFvHXbDELNtJ5RNj3U7rjJMWLMMHf38zTdwOCrvcQ8PYeUXW
+xP08OXvo4mMW/lWPeoi8iQlyFU4ti1se1zsQZVayFqJ1n387ZEAj4c1qLyEr3RjE
+ibUNIx+oN8eazxeMtveY8rkhZeVT7nKmZebRpW31MTZ7TNFjNpIMqvoXpPBjhX00
+x2KBcDwTT1ooBZOSG+AuU4Xgu+Ts1+6nZKbhnQuxvgslFyDEdtuID7nuXu08D3B2
+OpCtrQt08QS/dLHpcOhSMjkxqH2aITVPQgGWK16E7pcVex/Lapyjr4ETx1GqjpCL
+uKhmvGsrcdUD9fETzV5ZsMBHrnCSlq0XRnWFuGYmxlSIAciNUTMFfy7D/ml++Boe
+S33HwuaJlnUp3iAbIddXXx+zZHBocJ1Iznl1iu8CMdh+79wMOPkcMDRRNZPpMca+
+ukfGRRdrtyPyrWdrv9d+jtORBC3L9Kgg5yCEXe3HZCSLj/9UUrEAAACEw/IuYxy0
+AsDwpHS9WuqvLYbFPL3UQlSlFJWKkgCZwO+K+c2MsVnllWI0AQKCAQEA0NqVa0Jx
+TGxE2EEEgxA63c+rp5Qm2IlKns+tbnWnfI6XW/JiFkDqTVkIcg6F3dVyjEaZnGWa
+MPCORCtOGQ8IyAYQANyza/CW2+i0YvJBSMmCsbo7wUdrYU9krPRLLWYLhue5HdTP
+5+H+lFXmu3o36YhhWwfMflZsV1MOzRF3eIH3FaTgzoXmBQH3CH5y2c3OWjhMrTnz
+51HZFPu3XB3wmqEIyHAsQke68HxgyJ7uBYUmdAIYYMLgV3Jp/ds+QxXSqwYz97tS
+oe6778QLqcn/Q9LtonagDMO/ATSw/0fLt1+8QLdbksDTC4MUoaGKxuiL7sy5PnKd
+72oV75t06Tz26QKCAQEA0BiyFaC3EcZG6KmoZvM+bHGK9gp5tKr0AgERLAVe+M/f
+1NsBxf1HxoHlkudGRo64J+SwM+ZVVh5UPtrb1Le5a3IR9qL++ZfPM1WuvlHz9uBc
+lJLPozboWoUovbfaoKQtuRzzOYBwodwAbY02LNNtM33jqXNUN0MM0L3ZH3FmOglU
+u/4dH+EvgLDpnCV0czMlGSo9I9977Nvy3dQBXjzz9EF+covAhq7skmfwDblTAZR7
+EkmsCmoeEoKRAEtKgAxs0Hgyo2iMIxYy0pdvjGhJe64JQb3EKLjxhYsadnI+S5YI
+QBH9+EX/ZLSii+cIkEeh1lSMk9uT1/Rp4V40sdmfHwKCAQA9oTLUWt/qCEzsb4sw
+TbrBN4XUxpAL979wS6JG5SsOQ1mAxW6SWIeLWYl3drej9Vd+81pi30x3wpSIoyrB
+lEEoHTndt0KuyGkL7YhFZm1DWEtbvLPjXfre1TnFG7WPaPxfy8NBfUn4iCTCivKy
+Qed730WzQgLjCQehYR4N/2h8xAUwet3ns3Vj5ueJtx6XDPcmglSGDYLesLVZTsoV
+wbP9PSXFV+yHhnkwe9NngBaGxHrLYs7kxrzCsT17rpWZ4DexRfxRzxIcPMFyiCxU
+5wmPbw+2kEC8Y4rahTzxp7MCopp/klvQW1wrmnudEnlMJtUcG4dSWSonuutMMFh4
+dwf5AoIBAEX9+HAT1V9yHEmHPvAZooZhDkdBMLxWv6mo6DixOcdgS73RR+BrF2gq
+Kqhlh5qVyFUKs0VRlKRZoSZfAI+kmAYOoQIewrn6mKOAjzzOXctMnXcPhi00e6Ru
+o0xkeXGMpyBbH2fYzolycOZoF6+uEU2/awKEmu/XaokPQec0ghjFKK3ug6dEW3Di
+kECHzIouyqyTK2tUcN8y/5YjB67Fu5wNJ1WpscDbNxDrSq+jBMtEQLze8LG0DZdW
+OSUrLcyx4SuhMg8KTBBFGCUC8G7+aLDj0ZM+G6tCwWGUbvsl49QSi4XZR13pVURv
+CTkbJSM6JLHtUDcvJKP/PlmiEQE83CsCggEAOIlm8zRHeJe3GdZAPIwV8EYCh7rd
+ghRjGIwzjylazzZyeHLQBKaIwNdlgOZG1HqTljdp4ZE0f+Oyh2lGOqQEqWjMBJPp
+yZG1jBCCsJMfTweWxzLhmzV4UUQ+MZQDjZ7uOKGYwN/rkhW30zZMenoZ+PywFhhl
+ldnPsQMBvqS03eCIaQveRkq8S/qA3uDGPjsEPTaYMJgw9/imsP8hW3z+kd7hH76R
+Guk98WHe3wvasV1RNOp5ixwUQgVmFJLrTVX+dfSKLw0XzekkG/1Uis7r2HXCfoaZ
+boHvICyW0kmhnsOVA7mWwByhUav9Ghevm9czVATTIGiA8lIZxFFbvr2KTg==
+-----END RSA PRIVATE KEY-----
diff --git a/apex/manifest.json b/apex/manifest.json
new file mode 100644
index 0000000..6739a4d
--- /dev/null
+++ b/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.resolv",
+  "version": 290000000
+}
diff --git a/apex/testcert.pk8 b/apex/testcert.pk8
new file mode 100644
index 0000000..adfc589
--- /dev/null
+++ b/apex/testcert.pk8
Binary files differ
diff --git a/apex/testcert.x509.pem b/apex/testcert.x509.pem
new file mode 100644
index 0000000..cba30d6
--- /dev/null
+++ b/apex/testcert.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGADCCA+igAwIBAgIJAJiRMVvanGUaMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xODEwMzAxMjEzNTFaFw00NjAzMTcxMjEzNTFaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAL3ghKA8Gz9qOORY8gMY4wlB2tCJLDUO2tFG
+LVK1UphtQMp+YEcz/0VQKVV7de7z6V4EMQ5P1HbxHOsjcKn/zXAl4YgFt7b5kZbC
+bpNK4CYHEfho3j6fpYtq5d9q8rIA2kI0uZkkqPy1zXKTl2C2PjOoAnLQRk5xBVQG
+M10/wYsf7yX36mSWoJJwKPp/EzVFpA+hX8HpljeIiZ6CFzKwJdqv9zO/xzfp6NsX
+Tv5EGdkDxmw3qQqKgyl8dLMTZ/2zNfvVOMeZDusEPDF7A/lbU1byLWrKQdCzVb40
+yc7BCSRGYwM29R/byOcgD+lslwKSGzgzNmQXICt1tXz9bSJR8qh4tlAaiRc3ZKBe
+hJWIFGkGtD/cDGtDE5DbNAOz6CdSDdE2XN0Qf0cfN1RHVE6fo2FtFicRRVuFBt8M
+2cbQ7bzmEvtHD6W6dsf120FH7gppXKmnhMx1WazpxR2QltbiYDTy2ZZi4paS/jDB
+fL9gMCWp3Ohg2y74NGfUw5CQWQsDpcki6I7RvwClBCyOV51LHn5LE/nY4DkVrZxk
+Pw0/YrTWz5J5PbdMetTuIunE4ec4lm8nZnh1ET+2MHx2+RoyF5vBs4rp1KHHRaEA
+veD2AfQOWxz7kOG9+akFot7n+QoWEGdwY0mJ9jsO/IITCjv3VbD7o0OoJv1R2AW5
+sK2KQ4PDAgMBAAGjUzBRMB0GA1UdDgQWBBT2EbrayXGhY6VCvSlLtRNyjW9ceDAf
+BgNVHSMEGDAWgBT2EbrayXGhY6VCvSlLtRNyjW9ceDAPBgNVHRMBAf8EBTADAQH/
+MA0GCSqGSIb3DQEBCwUAA4ICAQC7SsWap9zDKmuR0qMUZ6wlualnag0hUG1jZHQP
+t63KO6LmNNMSuXRX60Zcq6WWzgLOyoT4HqHZZ47Jamfb4XQQcnWMMW0tJ3pDtTkz
+dZILBInHJO8QPYI8Du6XWsDLSvMajq6ueBtO3NdcgsNL7eiHf3WoOtajLZxFM94Z
+MESkUQOIsqHolYeTMHLTsuGkX1CK2Zw3Xn18bUSTYwZCHa6mYH00ItUBfetGCnWh
+Y7bth/R15Cc+hocSB7ZsOa/R5kDyDdFDIKrnV5nH5Yd7CryrYC6Ac5UarYrxSJTq
+eKPwqUlJB/tJW/lvdLt8YaURbFGzf/ZqU12zZRafYjmMjcQvfpzMoDSnbvHTA9IR
+ZGO7dwhwykoSaL4/8LWde49xQUq6F2pQBRmEr+7mTzml1MaM5cWEk5emkCMXgLog
+k+c56CAk1EdM1teWik7wR0TIqkkYyYJHTSg61GkXUIXrZJ6iYx2ejDg1+QTPm9rU
+Yr7nP52gVkQuUAX1+xB6wKLSDizQJw8SNiUGXl5+2vwV6+0BI3/CXlQ8I/nRPBC1
+oqOIkRSbE+IF7DP9QvYuNG/3bZZQ8LUVeHxqI5Mq8K2VIJZd95AIwPNMH34SaDGz
+9xjG28Fq4ZkuDP0pCsHM9d2XEwK5PEVS18WW5fJ/QcJKMno4IPTB70ZBBjVzv6Y+
+MYjOrw==
+-----END CERTIFICATE-----
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
new file mode 100644
index 0000000..a393035
--- /dev/null
+++ b/bpf_progs/Android.bp
@@ -0,0 +1,44 @@
+//
+// 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.
+//
+
+//
+// bpf kernel programs
+//
+bpf {
+    name: "clatd.o",
+    srcs: ["clatd.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    include_dirs: [
+        "system/netd/libnetdbpf/include",
+        "system/netd/libnetdutils/include",
+    ],
+}
+
+bpf {
+    name: "netd.o",
+    srcs: ["netd.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    include_dirs: [
+        "system/netd/libnetdbpf/include",
+        "system/netd/libnetdutils/include",
+    ],
+}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
new file mode 100644
index 0000000..578d244
--- /dev/null
+++ b/bpf_progs/clatd.c
@@ -0,0 +1,179 @@
+/*
+ * 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 <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/swab.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "bpf_helpers.h"
+#include "netdbpf/bpf_shared.h"
+
+// bionic/libc/kernel/uapi/linux/udp.h:
+//   struct __kernel_udphdr {
+// bionic/libc/kernel/tools/defaults.py:
+//   # We want to support both BSD and Linux member names in struct udphdr.
+//   "udphdr": "__kernel_udphdr",
+// so instead it just doesn't work... ugh.
+#define udphdr __kernel_udphdr
+
+// From kernel:include/net/ip.h
+#define IP_DF 0x4000  // Flag: "Don't Fragment"
+
+// Android only supports little endian architectures
+#define htons(x) (__builtin_constant_p(x) ? ___constant_swab16(x) : __builtin_bswap16(x))
+#define htonl(x) (__builtin_constant_p(x) ? ___constant_swab32(x) : __builtin_bswap32(x))
+#define ntohs(x) htons(x)
+#define ntohl(x) htonl(x)
+
+DEFINE_BPF_MAP(clat_ingress_map, HASH, ClatIngressKey, ClatIngressValue, 16)
+
+static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
+    const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+    void* data = (void*)(long)skb->data;
+    const void* data_end = (void*)(long)skb->data_end;
+    const struct ethhdr* const eth = is_ethernet ? data : NULL;  // used iff is_ethernet
+    const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
+    const struct tcphdr* const tcp = (void*)(ip6 + 1);
+    const struct udphdr* const udp = (void*)(ip6 + 1);
+
+    // Must be meta-ethernet IPv6 frame
+    if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK;
+
+    // Must have (ethernet and) ipv6 header
+    if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_OK;
+
+    // Ethertype - if present - must be IPv6
+    if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_OK;
+
+    // IP version must be 6
+    if (ip6->version != 6) return TC_ACT_OK;
+
+    // Maximum IPv6 payload length that can be translated to IPv4
+    if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_OK;
+
+    switch (ip6->nexthdr) {
+        case IPPROTO_TCP:  // If TCP, must have 20 byte minimal TCP header
+            if (tcp + 1 > (struct tcphdr*)data_end) return TC_ACT_OK;
+            break;
+
+        case IPPROTO_UDP:  // If UDP, must have 8 byte minimal UDP header
+            if (udp + 1 > (struct udphdr*)data_end) return TC_ACT_OK;
+            break;
+
+        default:  // do not know how to handle anything else
+            return TC_ACT_OK;
+    }
+
+    ClatIngressKey k = {
+            .iif = skb->ifindex,
+            .pfx96.in6_u.u6_addr32 =
+                    {
+                            ip6->saddr.in6_u.u6_addr32[0],
+                            ip6->saddr.in6_u.u6_addr32[1],
+                            ip6->saddr.in6_u.u6_addr32[2],
+                    },
+            .local6 = ip6->daddr,
+    };
+
+    ClatIngressValue* v = bpf_clat_ingress_map_lookup_elem(&k);
+
+    if (!v) return TC_ACT_OK;
+
+    struct ethhdr eth2;  // used iff is_ethernet
+    if (is_ethernet) {
+        eth2 = *eth;                     // Copy over the ethernet header (src/dst mac)
+        eth2.h_proto = htons(ETH_P_IP);  // But replace the ethertype
+    }
+
+    struct iphdr ip = {
+            .version = 4,                                                      // u4
+            .ihl = sizeof(struct iphdr) / sizeof(__u32),                       // u4
+            .tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4),             // u8
+            .tot_len = htons(ntohs(ip6->payload_len) + sizeof(struct iphdr)),  // u16
+            .id = 0,                                                           // u16
+            .frag_off = htons(IP_DF),                                          // u16
+            .ttl = ip6->hop_limit,                                             // u8
+            .protocol = ip6->nexthdr,                                          // u8
+            .check = 0,                                                        // u16
+            .saddr = ip6->saddr.in6_u.u6_addr32[3],                            // u32
+            .daddr = v->local4.s_addr,                                         // u32
+    };
+
+    // Calculate the IPv4 one's complement checksum of the IPv4 header.
+    __u32 sum = 0;
+    for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
+        sum += ((__u16*)&ip)[i];
+    }
+    // Note that sum is guaranteed to be non-zero by virtue of ip.version == 4
+    sum = (sum & 0xFFFF) + (sum >> 16);  // collapse u32 into range 1 .. 0x1FFFE
+    sum = (sum & 0xFFFF) + (sum >> 16);  // collapse any potential carry into u16
+    ip.check = (__u16)~sum;              // sum cannot be zero, so this is never 0xFFFF
+
+    // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+    // of the ipv6 address chosen by netd's ClatdController.
+
+    // Packet mutations begin - point of no return.
+    if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) return TC_ACT_SHOT;
+
+    // bpf_skb_change_proto() invalidates all pointers - reload them.
+    data = (void*)(long)skb->data;
+    data_end = (void*)(long)skb->data_end;
+
+    // I cannot think of any valid way for this error condition to trigger, however I do
+    // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+    if (data + l2_header_size + sizeof(struct iphdr) > data_end) return TC_ACT_SHOT;
+
+    if (is_ethernet) {
+        struct ethhdr* new_eth = data;
+
+        // Copy over the updated ethernet header
+        *new_eth = eth2;
+
+        // Copy over the new ipv4 header.
+        *(struct iphdr*)(new_eth + 1) = ip;
+    } else {
+        // Copy over the new ipv4 header without an ethernet header.
+        *(struct iphdr*)data = ip;
+    }
+
+    // Redirect, possibly back to same interface, so tcpdump sees packet twice.
+    if (v->oif) return bpf_redirect(v->oif, BPF_F_INGRESS);
+
+    // Just let it through, tcpdump will not see IPv4 packet.
+    return TC_ACT_OK;
+}
+
+SEC("schedcls/ingress/clat_ether")
+int sched_cls_ingress_clat_ether(struct __sk_buff* skb) {
+    return nat64(skb, true);
+}
+
+SEC("schedcls/ingress/clat_rawip")
+int sched_cls_ingress_clat_rawip(struct __sk_buff* skb) {
+    return nat64(skb, false);
+}
+
+char _license[] SEC("license") = "Apache 2.0";
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
new file mode 100644
index 0000000..8f2863e
--- /dev/null
+++ b/bpf_progs/netd.c
@@ -0,0 +1,83 @@
+/*
+ * 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 "netd.h"
+#include <linux/bpf.h>
+
+SEC("cgroupskb/ingress/stats")
+int bpf_cgroup_ingress(struct __sk_buff* skb) {
+    return bpf_traffic_account(skb, BPF_INGRESS);
+}
+
+SEC("cgroupskb/egress/stats")
+int bpf_cgroup_egress(struct __sk_buff* skb) {
+    return bpf_traffic_account(skb, BPF_EGRESS);
+}
+
+SEC("skfilter/egress/xtbpf")
+int xt_bpf_egress_prog(struct __sk_buff* skb) {
+    uint32_t key = skb->ifindex;
+    update_iface_stats_map(skb, BPF_EGRESS, &key);
+    return BPF_MATCH;
+}
+
+SEC("skfilter/ingress/xtbpf")
+int xt_bpf_ingress_prog(struct __sk_buff* skb) {
+    uint32_t key = skb->ifindex;
+    update_iface_stats_map(skb, BPF_INGRESS, &key);
+    return BPF_MATCH;
+}
+
+SEC("skfilter/whitelist/xtbpf")
+int xt_bpf_whitelist_prog(struct __sk_buff* skb) {
+    uint32_t sock_uid = bpf_get_socket_uid(skb);
+    if (is_system_uid(sock_uid)) return BPF_MATCH;
+    UidOwnerValue* whitelistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+    if (whitelistMatch) return whitelistMatch->rule & HAPPY_BOX_MATCH;
+    return BPF_NOMATCH;
+}
+
+SEC("skfilter/blacklist/xtbpf")
+int xt_bpf_blacklist_prog(struct __sk_buff* skb) {
+    uint32_t sock_uid = bpf_get_socket_uid(skb);
+    UidOwnerValue* blacklistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+    if (blacklistMatch) return blacklistMatch->rule & PENALTY_BOX_MATCH;
+    return BPF_NOMATCH;
+}
+
+DEFINE_BPF_MAP(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE)
+
+SEC("cgroupsock/inet/create")
+int inet_socket_create(struct bpf_sock* sk) {
+    uint64_t gid_uid = bpf_get_current_uid_gid();
+    /*
+     * A given app is guaranteed to have the same app ID in all the profiles in
+     * which it is installed, and install permission is granted to app for all
+     * user at install time so we only check the appId part of a request uid at
+     * run time. See UserHandle#isSameApp for detail.
+     */
+    uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
+    uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
+    if (!permissions) {
+        // UID not in map. Default to just INTERNET permission.
+        return 1;
+    }
+
+    // A return value of 1 means allow, everything else means deny.
+    return (*permissions & BPF_PERMISSION_INTERNET) == BPF_PERMISSION_INTERNET;
+}
+
+char _license[] SEC("license") = "Apache 2.0";
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
new file mode 100644
index 0000000..8be21be
--- /dev/null
+++ b/bpf_progs/netd.h
@@ -0,0 +1,245 @@
+/*
+ * 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 h file together with netd.c is used for compiling the eBPF kernel
+ * program.
+ */
+
+#include <bpf_helpers.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "netdbpf/bpf_shared.h"
+
+typedef struct {
+    uint32_t uid;
+    uint32_t tag;
+} uid_tag;
+
+typedef struct {
+    uint32_t uid;
+    uint32_t tag;
+    uint32_t counterSet;
+    uint32_t ifaceIndex;
+} stats_key;
+
+typedef struct {
+    uint64_t rxPackets;
+    uint64_t rxBytes;
+    uint64_t txPackets;
+    uint64_t txBytes;
+} stats_value;
+
+typedef struct {
+    char name[IFNAMSIZ];
+} IfaceValue;
+
+// This is defined for cgroup bpf filter only.
+#define BPF_PASS 1
+#define BPF_DROP 0
+
+// This is used for xt_bpf program only.
+#define BPF_NOMATCH 0
+#define BPF_MATCH 1
+
+#define BPF_EGRESS 0
+#define BPF_INGRESS 1
+
+#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
+#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+#define IPPROTO_IHL_OFF 0
+#define TCP_FLAG_OFF 13
+#define RST_OFFSET 2
+
+DEFINE_BPF_MAP(cookie_tag_map, HASH, uint64_t, uid_tag, COOKIE_UID_MAP_SIZE)
+DEFINE_BPF_MAP(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE)
+DEFINE_BPF_MAP(app_uid_stats_map, HASH, uint32_t, stats_value, APP_STATS_MAP_SIZE)
+DEFINE_BPF_MAP(stats_map_A, HASH, stats_key, stats_value, STATS_MAP_SIZE)
+DEFINE_BPF_MAP(stats_map_B, HASH, stats_key, stats_value, STATS_MAP_SIZE)
+DEFINE_BPF_MAP(iface_stats_map, HASH, uint32_t, stats_value, IFACE_STATS_MAP_SIZE)
+DEFINE_BPF_MAP(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE)
+DEFINE_BPF_MAP(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE)
+
+/* never actually used from ebpf */
+DEFINE_BPF_MAP_NO_ACCESSORS(iface_index_name_map, HASH, uint32_t, IfaceValue,
+                            IFACE_INDEX_NAME_MAP_SIZE)
+
+static __always_inline int is_system_uid(uint32_t uid) {
+    return (uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID);
+}
+
+#define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey)                                          \
+    static __always_inline inline void update_##the_stats_map(struct __sk_buff* skb,           \
+                                                              int direction, TypeOfKey* key) { \
+        stats_value* value;                                                                    \
+        value = bpf_##the_stats_map##_lookup_elem(key);                                        \
+        if (!value) {                                                                          \
+            stats_value newValue = {};                                                         \
+            bpf_##the_stats_map##_update_elem(key, &newValue, BPF_NOEXIST);                    \
+            value = bpf_##the_stats_map##_lookup_elem(key);                                    \
+        }                                                                                      \
+        if (value) {                                                                           \
+            if (direction == BPF_EGRESS) {                                                     \
+                __sync_fetch_and_add(&value->txPackets, 1);                                    \
+                __sync_fetch_and_add(&value->txBytes, skb->len);                               \
+            } else if (direction == BPF_INGRESS) {                                             \
+                __sync_fetch_and_add(&value->rxPackets, 1);                                    \
+                __sync_fetch_and_add(&value->rxBytes, skb->len);                               \
+            }                                                                                  \
+        }                                                                                      \
+    }
+
+DEFINE_UPDATE_STATS(app_uid_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(iface_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(stats_map_A, stats_key)
+DEFINE_UPDATE_STATS(stats_map_B, stats_key)
+
+static inline bool skip_owner_match(struct __sk_buff* skb) {
+    int offset = -1;
+    int ret = 0;
+    if (skb->protocol == ETH_P_IP) {
+        offset = IP_PROTO_OFF;
+        uint8_t proto, ihl;
+        uint16_t flag;
+        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+        if (!ret) {
+            if (proto == IPPROTO_ESP) {
+                return true;
+            } else if (proto == IPPROTO_TCP) {
+                ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
+                ihl = ihl & 0x0F;
+                ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
+                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+                    return true;
+                }
+            }
+        }
+    } else if (skb->protocol == ETH_P_IPV6) {
+        offset = IPV6_PROTO_OFF;
+        uint8_t proto;
+        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+        if (!ret) {
+            if (proto == IPPROTO_ESP) {
+                return true;
+            } else if (proto == IPPROTO_TCP) {
+                uint16_t flag;
+                ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
+                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+static __always_inline BpfConfig getConfig(uint32_t configKey) {
+    uint32_t mapSettingKey = configKey;
+    BpfConfig* config = bpf_configuration_map_lookup_elem(&mapSettingKey);
+    if (!config) {
+        // Couldn't read configuration entry. Assume everything is disabled.
+        return DEFAULT_CONFIG;
+    }
+    return *config;
+}
+
+static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
+    if (skip_owner_match(skb)) return BPF_PASS;
+
+    if ((uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID)) return BPF_PASS;
+
+    BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+
+    UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
+    uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+    uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
+
+    if (enabledRules) {
+        if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
+            return BPF_DROP;
+        }
+        if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
+            return BPF_DROP;
+        }
+        if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
+            return BPF_DROP;
+        }
+    }
+    if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
+        // Drops packets not coming from lo nor the whitelisted interface
+        if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
+            return BPF_DROP;
+        }
+    }
+    return BPF_PASS;
+}
+
+static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
+                                                            stats_key* key, uint8_t selectedMap) {
+    if (selectedMap == SELECT_MAP_A) {
+        update_stats_map_A(skb, direction, key);
+    } else if (selectedMap == SELECT_MAP_B) {
+        update_stats_map_B(skb, direction, key);
+    }
+}
+
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
+    uint32_t sock_uid = bpf_get_socket_uid(skb);
+    int match = bpf_owner_match(skb, sock_uid, direction);
+    if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
+        // If an outbound packet is going to be dropped, we do not count that
+        // traffic.
+        return match;
+    }
+
+    uint64_t cookie = bpf_get_socket_cookie(skb);
+    uid_tag* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+    uint32_t uid, tag;
+    if (utag) {
+        uid = utag->uid;
+        tag = utag->tag;
+    } else {
+        uid = sock_uid;
+        tag = 0;
+    }
+
+    stats_key key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
+
+    uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
+    if (counterSet) key.counterSet = (uint32_t)*counterSet;
+
+    uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+    uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+    if (!selectedMap) {
+        return match;
+    }
+
+    if (key.tag) {
+        update_stats_with_config(skb, direction, &key, *selectedMap);
+        key.tag = 0;
+    }
+
+    update_stats_with_config(skb, direction, &key, *selectedMap);
+    update_app_uid_stats_map(skb, direction, &uid);
+    return match;
+}
diff --git a/bpfloader/Android.bp b/bpfloader/Android.bp
deleted file mode 100644
index 846264c..0000000
--- a/bpfloader/Android.bp
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// 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.
-//
-
-//
-// bpfLoad binary
-//
-cc_binary {
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wthread-safety",
-    ],
-    sanitize: {
-        integer_overflow: true,
-    },
-    clang: true,
-    name: "bpfloader",
-    shared_libs: [
-        "libcutils",
-        "libbpf",
-        "libbase",
-        "liblog",
-        "libnetdutils",
-    ],
-    srcs: [
-        "BpfLoader.cpp",
-    ],
-
-    required: [
-        "bpf_kern.o",
-    ],
-
-}
-
-bpf {
-    name: "bpf_kern.o",
-    srcs: ["bpf_kern.c"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-    include_dirs: ["system/netd/libbpf/include"],
-}
diff --git a/bpfloader/BpfLoader.cpp b/bpfloader/BpfLoader.cpp
deleted file mode 100644
index 8ab34d9..0000000
--- a/bpfloader/BpfLoader.cpp
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * 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 LOG_TAG
-#define LOG_TAG "bpfloader"
-#endif
-
-#include <arpa/inet.h>
-#include <elf.h>
-#include <error.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <linux/bpf.h>
-#include <linux/unistd.h>
-#include <net/if.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <sys/mman.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <cutils/log.h>
-
-#include <netdutils/MemBlock.h>
-#include <netdutils/Misc.h>
-#include <netdutils/Slice.h>
-#include "bpf/BpfUtils.h"
-#include "bpf/bpf_shared.h"
-
-using android::base::unique_fd;
-using android::netdutils::MemBlock;
-using android::netdutils::Slice;
-
-#define BPF_PROG_PATH "/system/etc/bpf"
-#define BPF_PROG_SRC BPF_PROG_PATH "/bpf_kern.o"
-#define MAP_LD_CMD_HEAD 0x18
-
-#define FAIL(...)      \
-    do {               \
-        ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)); \
-        exit(-1);     \
-    } while (0)
-
-// The BPF instruction bytes that we need to replace. x is a placeholder (e.g., COOKIE_TAG_MAP).
-#define MAP_SEARCH_PATTERN(x)             \
-    {                                     \
-        0x18, 0x01, 0x00, 0x00,           \
-        (x)[0], (x)[1], (x)[2], (x)[3],   \
-        0x00, 0x00, 0x00, 0x00,           \
-        (x)[4], (x)[5], (x)[6], (x)[7]    \
-    }
-
-// The bytes we'll replace them with. x is the actual fd number for the map at runtime.
-// The second byte is changed from 0x01 to 0x11 since 0x11 is the special command used
-// for bpf map fd loading. The original 0x01 is only a normal load command.
-#define MAP_REPLACE_PATTERN(x)            \
-    {                                     \
-        0x18, 0x11, 0x00, 0x00,           \
-        (x)[0], (x)[1], (x)[2], (x)[3],   \
-        0x00, 0x00, 0x00, 0x00,           \
-        (x)[4], (x)[5], (x)[6], (x)[7]    \
-    }
-
-#define DECLARE_MAP(_mapFd, _mapPath)                             \
-    unique_fd _mapFd(android::bpf::mapRetrieve((_mapPath), 0));   \
-    if (_mapFd < 0) {                                             \
-        FAIL("Failed to get map from %s", (_mapPath));            \
-    }
-
-#define MAP_CMD_SIZE 16
-#define LOG_BUF_SIZE 65536
-
-namespace android {
-namespace bpf {
-
-struct ReplacePattern {
-    std::array<uint8_t, MAP_CMD_SIZE> search;
-    std::array<uint8_t, MAP_CMD_SIZE> replace;
-
-    ReplacePattern(uint64_t dummyFd, int realFd) {
-        // Ensure that the fd numbers are big-endian.
-        uint8_t beDummyFd[sizeof(uint64_t)];
-        uint8_t beRealFd[sizeof(uint64_t)];
-        for (size_t i = 0; i < sizeof(uint64_t); i++) {
-            beDummyFd[i] = (dummyFd >> (i * 8)) & 0xFF;
-            beRealFd[i] = (realFd >> (i * 8)) & 0xFF;
-        }
-        search = MAP_SEARCH_PATTERN(beDummyFd);
-        replace = MAP_REPLACE_PATTERN(beRealFd);
-    }
-};
-
-MemBlock cgroupIngressProg;
-MemBlock cgroupEgressProg;
-MemBlock xtIngressProg;
-MemBlock xtEgressProg;
-
-MemBlock getProgFromMem(Slice buffer, Elf64_Shdr* section) {
-    uint64_t progSize = (uint64_t)section->sh_size;
-    Slice progSection = take(drop(buffer, section->sh_offset), progSize);
-    if (progSection.size() < progSize) FAIL("programSection out of bound\n");
-
-    MemBlock progCopy(progSection);
-    if (progCopy.get().size() != progSize) {
-        FAIL("program cannot be extracted");
-    }
-    return progCopy;
-}
-
-void parseProgramsFromFile(const char* path) {
-    int fd = open(path, O_RDONLY);
-    if (fd == -1) {
-        FAIL("Failed to open %s program: %s", path, strerror(errno));
-    }
-
-    struct stat stat;
-    if (fstat(fd, &stat)) FAIL("Fail to get file size");
-
-    off_t fileLen = stat.st_size;
-    char* baseAddr = (char*)mmap(NULL, fileLen, PROT_READ, MAP_PRIVATE, fd, 0);
-    if (baseAddr == MAP_FAILED) FAIL("Failed to map the program into memory");
-
-    if ((uint32_t)fileLen < sizeof(Elf64_Ehdr)) FAIL("file size too small for Elf64_Ehdr");
-
-    Slice buffer(baseAddr, fileLen);
-
-    Slice elfHeader = take(buffer, sizeof(Elf64_Ehdr));
-
-    if (elfHeader.size() < sizeof(Elf64_Ehdr)) FAIL("bpf buffer does not have complete elf header");
-
-    Elf64_Ehdr* elf = (Elf64_Ehdr*)elfHeader.base();
-    // Find section names string table. This is the section whose index is e_shstrndx.
-    if (elf->e_shstrndx == SHN_UNDEF) {
-        FAIL("cannot locate namesSection\n");
-    }
-    size_t totalSectionSize = (elf->e_shnum) * sizeof(Elf64_Shdr);
-    Slice sections = take(drop(buffer, elf->e_shoff), totalSectionSize);
-    if (sections.size() < totalSectionSize) {
-        FAIL("sections corrupted");
-    }
-
-    Slice namesSection = take(drop(sections, elf->e_shstrndx * sizeof(Elf64_Shdr)),
-                              sizeof(Elf64_Shdr));
-    if (namesSection.size() != sizeof(Elf64_Shdr)) {
-        FAIL("namesSection corrupted");
-    }
-    size_t strTabOffset = ((Elf64_Shdr*) namesSection.base())->sh_offset;
-    size_t strTabSize = ((Elf64_Shdr*) namesSection.base())->sh_size;
-
-    Slice strTab = take(drop(buffer, strTabOffset), strTabSize);
-    if (strTab.size() < strTabSize) {
-        FAIL("string table out of bound\n");
-    }
-
-    for (int i = 0; i < elf->e_shnum; i++) {
-        Slice section = take(drop(sections, i * sizeof(Elf64_Shdr)), sizeof(Elf64_Shdr));
-        if (section.size() < sizeof(Elf64_Shdr)) {
-            FAIL("section %d is out of bound, section size: %zu, header size: %zu, total size: %zu",
-                 i, section.size(), sizeof(Elf64_Shdr), sections.size());
-        }
-        Elf64_Shdr* sectionPtr = (Elf64_Shdr*)section.base();
-        Slice nameSlice = drop(strTab, sectionPtr->sh_name);
-        if (nameSlice.size() == 0) {
-            FAIL("nameSlice out of bound, i: %d, strTabSize: %zu, sh_name: %u", i, strTabSize,
-                 sectionPtr->sh_name);
-        }
-        if (!strcmp((char *)nameSlice.base(), BPF_CGROUP_INGRESS_PROG_NAME)) {
-            cgroupIngressProg = getProgFromMem(buffer, sectionPtr);
-        } else if (!strcmp((char *)nameSlice.base(), BPF_CGROUP_EGRESS_PROG_NAME)) {
-            cgroupEgressProg = getProgFromMem(buffer, sectionPtr);
-        } else if (!strcmp((char *)nameSlice.base(), XT_BPF_INGRESS_PROG_NAME)) {
-            xtIngressProg = getProgFromMem(buffer, sectionPtr);
-        } else if (!strcmp((char *)nameSlice.base(), XT_BPF_EGRESS_PROG_NAME)) {
-            xtEgressProg = getProgFromMem(buffer, sectionPtr);
-        }
-    }
-}
-
-int loadProg(Slice prog, bpf_prog_type type, const std::vector<ReplacePattern>& mapPatterns) {
-    if (prog.size() == 0) {
-        FAIL("Couldn't find or parse program type %d", type);
-    }
-    Slice remaining = prog;
-    while (remaining.size() >= MAP_CMD_SIZE) {
-        // Scan the program, examining all possible places that might be the start of a map load
-        // operation (i.e., all bytes of value MAP_LD_CMD_HEAD).
-        // In each of these places, check whether it is the start of one of the patterns we want to
-        // replace, and if so, replace it.
-        Slice mapHead = findFirstMatching(remaining, MAP_LD_CMD_HEAD);
-        if (mapHead.size() < MAP_CMD_SIZE) break;
-        bool replaced = false;
-        for (const auto& pattern : mapPatterns) {
-            if (!memcmp(mapHead.base(), pattern.search.data(), MAP_CMD_SIZE)) {
-                memcpy(mapHead.base(), pattern.replace.data(), MAP_CMD_SIZE);
-                replaced = true;
-                break;
-            }
-        }
-        remaining = drop(mapHead, replaced ? MAP_CMD_SIZE : sizeof(uint8_t));
-    }
-    char bpf_log_buf[LOG_BUF_SIZE];
-    Slice bpfLog = Slice(bpf_log_buf, sizeof(bpf_log_buf));
-    return bpfProgLoad(type, prog, "Apache 2.0", 0, bpfLog);
-}
-
-int loadAndAttachProgram(bpf_attach_type type, const char* path, const char* name,
-                         std::vector<ReplacePattern> mapPatterns) {
-
-    unique_fd fd;
-    if (type == BPF_CGROUP_INET_INGRESS) {
-        fd.reset(loadProg(cgroupIngressProg, BPF_PROG_TYPE_CGROUP_SKB, mapPatterns));
-    } else if (type == BPF_CGROUP_INET_EGRESS) {
-        fd.reset(loadProg(cgroupEgressProg, BPF_PROG_TYPE_CGROUP_SKB, mapPatterns));
-    } else if (!strcmp(name, XT_BPF_INGRESS_PROG_NAME)) {
-        fd.reset(loadProg(xtIngressProg, BPF_PROG_TYPE_SOCKET_FILTER, mapPatterns));
-    } else if (!strcmp(name, XT_BPF_EGRESS_PROG_NAME)) {
-        fd.reset(loadProg(xtEgressProg, BPF_PROG_TYPE_SOCKET_FILTER, mapPatterns));
-    } else {
-        FAIL("Unrecognized program type: %s", name);
-    }
-
-    if (fd < 0) {
-        FAIL("load %s failed: %s", name, strerror(errno));
-    }
-    int ret = 0;
-    if (type == BPF_CGROUP_INET_EGRESS || type == BPF_CGROUP_INET_INGRESS) {
-        unique_fd cg_fd(open(CGROUP_ROOT_PATH, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
-        if (cg_fd < 0) {
-            FAIL("Failed to open the cgroup directory");
-        }
-        ret = attachProgram(type, fd, cg_fd);
-        if (ret) {
-            FAIL("%s attach failed: %s", name, strerror(errno));
-        }
-    }
-
-    ret = mapPin(fd, path);
-    if (ret) {
-        FAIL("Pin %s as file %s failed: %s", name, path, strerror(errno));
-    }
-    return 0;
-}
-
-}  // namespace bpf
-}  // namespace android
-
-using android::bpf::APP_UID_STATS_MAP_PATH;
-using android::bpf::BPF_EGRESS_PROG_PATH;
-using android::bpf::BPF_INGRESS_PROG_PATH;
-using android::bpf::COOKIE_TAG_MAP_PATH;
-using android::bpf::DOZABLE_UID_MAP_PATH;
-using android::bpf::IFACE_STATS_MAP_PATH;
-using android::bpf::POWERSAVE_UID_MAP_PATH;
-using android::bpf::STANDBY_UID_MAP_PATH;
-using android::bpf::TAG_STATS_MAP_PATH;
-using android::bpf::UID_COUNTERSET_MAP_PATH;
-using android::bpf::UID_STATS_MAP_PATH;
-using android::bpf::XT_BPF_EGRESS_PROG_PATH;
-using android::bpf::XT_BPF_INGRESS_PROG_PATH;
-
-using android::bpf::ReplacePattern;
-using android::bpf::loadAndAttachProgram;
-
-static void usage(void) {
-    ALOGE( "Usage: ./bpfloader [-i] [-e]\n"
-           "   -i load ingress bpf program\n"
-           "   -e load egress bpf program\n"
-           "   -p load prerouting xt_bpf program\n"
-           "   -m load mangle xt_bpf program\n");
-}
-
-int main(int argc, char** argv) {
-    int ret = 0;
-    DECLARE_MAP(cookieTagMap, COOKIE_TAG_MAP_PATH);
-    DECLARE_MAP(uidCounterSetMap, UID_COUNTERSET_MAP_PATH);
-    DECLARE_MAP(appUidStatsMap, APP_UID_STATS_MAP_PATH);
-    DECLARE_MAP(uidStatsMap, UID_STATS_MAP_PATH);
-    DECLARE_MAP(tagStatsMap, TAG_STATS_MAP_PATH);
-    DECLARE_MAP(ifaceStatsMap, IFACE_STATS_MAP_PATH);
-    DECLARE_MAP(dozableUidMap, DOZABLE_UID_MAP_PATH);
-    DECLARE_MAP(standbyUidMap, STANDBY_UID_MAP_PATH);
-    DECLARE_MAP(powerSaveUidMap, POWERSAVE_UID_MAP_PATH);
-
-    const std::vector<ReplacePattern> mapPatterns = {
-        ReplacePattern(COOKIE_TAG_MAP, cookieTagMap.get()),
-        ReplacePattern(UID_COUNTERSET_MAP, uidCounterSetMap.get()),
-        ReplacePattern(APP_UID_STATS_MAP, appUidStatsMap.get()),
-        ReplacePattern(UID_STATS_MAP, uidStatsMap.get()),
-        ReplacePattern(TAG_STATS_MAP, tagStatsMap.get()),
-        ReplacePattern(IFACE_STATS_MAP, ifaceStatsMap.get()),
-        ReplacePattern(DOZABLE_UID_MAP, dozableUidMap.get()),
-        ReplacePattern(STANDBY_UID_MAP, standbyUidMap.get()),
-        ReplacePattern(POWERSAVE_UID_MAP, powerSaveUidMap.get()),
-    };
-
-    int opt;
-    bool doIngress = false, doEgress = false, doPrerouting = false, doMangle = false;
-    while ((opt = getopt(argc, argv, "iepm")) != -1) {
-        switch (opt) {
-            case 'i':
-                doIngress = true;
-                break;
-            case 'e':
-                doEgress = true;
-                break;
-            case 'p':
-                doPrerouting = true;
-                break;
-            case 'm':
-                doMangle = true;
-                break;
-            default:
-                usage();
-                FAIL("unknown argument %c", opt);
-        }
-    }
-    android::bpf::parseProgramsFromFile(BPF_PROG_SRC);
-
-    if (doIngress) {
-        ret = loadAndAttachProgram(BPF_CGROUP_INET_INGRESS, BPF_INGRESS_PROG_PATH,
-                                   BPF_CGROUP_INGRESS_PROG_NAME, mapPatterns);
-        if (ret) {
-            FAIL("Failed to set up ingress program");
-        }
-    }
-    if (doEgress) {
-        ret = loadAndAttachProgram(BPF_CGROUP_INET_EGRESS, BPF_EGRESS_PROG_PATH,
-                                   BPF_CGROUP_EGRESS_PROG_NAME, mapPatterns);
-        if (ret) {
-            FAIL("Failed to set up ingress program");
-        }
-    }
-    if (doPrerouting) {
-        ret = loadAndAttachProgram(MAX_BPF_ATTACH_TYPE, XT_BPF_INGRESS_PROG_PATH,
-                                   XT_BPF_INGRESS_PROG_NAME, mapPatterns);
-        if (ret) {
-            FAIL("Failed to set up xt_bpf program");
-        }
-    }
-    if (doMangle) {
-        ret = loadAndAttachProgram(MAX_BPF_ATTACH_TYPE, XT_BPF_EGRESS_PROG_PATH,
-                                   XT_BPF_EGRESS_PROG_NAME, mapPatterns);
-        if (ret) {
-            FAIL("Failed to set up xt_bpf program");
-        }
-    }
-    return ret;
-}
diff --git a/bpfloader/bpf_kern.c b/bpfloader/bpf_kern.c
deleted file mode 100644
index 3a1668f..0000000
--- a/bpfloader/bpf_kern.c
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 <linux/bpf.h>
-#include "bpf_kern.h"
-
-
-ELF_SEC(BPF_CGROUP_INGRESS_PROG_NAME)
-int bpf_cgroup_ingress(struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, BPF_INGRESS);
-}
-
-ELF_SEC(BPF_CGROUP_EGRESS_PROG_NAME)
-int bpf_cgroup_egress(struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, BPF_EGRESS);
-}
-
-ELF_SEC(XT_BPF_EGRESS_PROG_NAME)
-int xt_bpf_egress_prog(struct __sk_buff* skb) {
-    uint32_t key = skb->ifindex;
-    bpf_update_stats(skb, IFACE_STATS_MAP, BPF_EGRESS, &key);
-    return BPF_PASS;
-}
-
-ELF_SEC(XT_BPF_INGRESS_PROG_NAME)
-int xt_bpf_ingress_prog(struct __sk_buff* skb) {
-    uint32_t key = skb->ifindex;
-    bpf_update_stats(skb, IFACE_STATS_MAP, BPF_INGRESS, &key);
-    return BPF_PASS;
-}
diff --git a/bpfloader/bpf_kern.h b/bpfloader/bpf_kern.h
deleted file mode 100644
index a59cb6d..0000000
--- a/bpfloader/bpf_kern.h
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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 h file together with bpf_kern.c is used for compiling the eBPF kernel
- * program. To generate the bpf_kern.o file manually, use the clang prebuilt in
- * this android tree to compile the files with --target=bpf options. For
- * example, in system/netd/ directory, execute the following command:
- * $: ANDROID_BASE_DIRECTORY/prebuilts/clang/host/linux-x86/clang-4691093/bin/clang  \
- *    -I ANDROID_BASE_DIRECTORY/bionic/libc/kernel/uapi/ \
- *    -I ANDROID_BASE_DIRECTORY/system/netd/bpfloader/ \
- *    -I ANDROID_BASE_DIRECTORY/bionic/libc/kernel/android/uapi/ \
- *    -I ANDROID_BASE_DIRECTORY/bionic/libc/include \
- *    -I ANDROID_BASE_DIRECTORY/system/netd/libbpf/include  \
- *    --target=bpf -O2 -c bpfloader/bpf_kern.c -o bpfloader/bpf_kern.o
- */
-
-#include <linux/bpf.h>
-#include <linux/if_ether.h>
-#include <linux/in.h>
-#include <linux/in6.h>
-#include <linux/ip.h>
-#include <linux/ipv6.h>
-#include <stdint.h>
-#include "bpf/bpf_shared.h"
-
-#define ELF_SEC(NAME) __attribute__((section(NAME), used))
-
-struct uid_tag {
-    uint32_t uid;
-    uint32_t tag;
-};
-
-struct stats_key {
-    uint32_t uid;
-    uint32_t tag;
-    uint32_t counterSet;
-    uint32_t ifaceIndex;
-};
-
-struct stats_value {
-    uint64_t rxPackets;
-    uint64_t rxBytes;
-    uint64_t txPackets;
-    uint64_t txBytes;
-};
-
-/* helper functions called from eBPF programs written in C */
-static void* (*find_map_entry)(uint64_t map, void* key) = (void*)BPF_FUNC_map_lookup_elem;
-static int (*write_to_map_entry)(uint64_t map, void* key, void* value,
-                                 uint64_t flags) = (void*)BPF_FUNC_map_update_elem;
-static int (*delete_map_entry)(uint64_t map, void* key) = (void*)BPF_FUNC_map_delete_elem;
-static uint64_t (*get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
-static uint32_t (*get_socket_uid)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_uid;
-static int (*bpf_skb_load_bytes)(struct __sk_buff* skb, int off, void* to,
-                                 int len) = (void*)BPF_FUNC_skb_load_bytes;
-#define BPF_PASS 1
-#define BPF_DROP 0
-#define BPF_EGRESS 0
-#define BPF_INGRESS 1
-
-#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
-#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
-#define IPPROTO_IHL_OFF 0
-#define TCP_FLAG_OFF 13
-#define RST_OFFSET 2
-
-static __always_inline inline void bpf_update_stats(struct __sk_buff* skb, uint64_t map,
-                                                    int direction, void *key) {
-    struct stats_value* value;
-    value = find_map_entry(map, key);
-    if (!value) {
-        struct stats_value newValue = {};
-        write_to_map_entry(map, key, &newValue, BPF_NOEXIST);
-        value = find_map_entry(map, key);
-    }
-    if (value) {
-        if (direction == BPF_EGRESS) {
-            __sync_fetch_and_add(&value->txPackets, 1);
-            __sync_fetch_and_add(&value->txBytes, skb->len);
-        } else if (direction == BPF_INGRESS) {
-            __sync_fetch_and_add(&value->rxPackets, 1);
-            __sync_fetch_and_add(&value->rxBytes, skb->len);
-        }
-    }
-}
-
-static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid) {
-    int offset = -1;
-    int ret = 0;
-    if (skb->protocol == ETH_P_IP) {
-        offset = IP_PROTO_OFF;
-        uint8_t proto, ihl;
-        uint16_t flag;
-        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
-        if (!ret) {
-            if (proto == IPPROTO_ESP) {
-                return 1;
-            } else if (proto == IPPROTO_TCP) {
-                ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
-                ihl = ihl & 0x0F;
-                ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
-                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
-                    return BPF_PASS;
-                }
-            }
-        }
-    } else if (skb->protocol == ETH_P_IPV6) {
-        offset = IPV6_PROTO_OFF;
-        uint8_t proto;
-        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
-        if (!ret) {
-            if (proto == IPPROTO_ESP) {
-                return BPF_PASS;
-            } else if (proto == IPPROTO_TCP) {
-                uint16_t flag;
-                ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
-                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
-                    return BPF_PASS;
-                }
-            }
-        }
-    }
-
-    if ((uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID)) return BPF_PASS;
-
-    // In each of these maps, the entry with key UID_MAP_ENABLED tells us whether that
-    // map is enabled or not.
-    // TODO: replace this with a map of size one that contains a config structure defined in
-    // bpf_shared.h that can be written by userspace and read here.
-    uint32_t mapSettingKey = UID_MAP_ENABLED;
-    uint8_t* ownerMatch;
-    uint8_t* mapEnabled = find_map_entry(DOZABLE_UID_MAP, &mapSettingKey);
-    if (mapEnabled && *mapEnabled) {
-        ownerMatch = find_map_entry(DOZABLE_UID_MAP, &uid);
-        if (ownerMatch) return *ownerMatch;
-        return BPF_DROP;
-    }
-    mapEnabled = find_map_entry(STANDBY_UID_MAP, &mapSettingKey);
-    if (mapEnabled && *mapEnabled) {
-        ownerMatch = find_map_entry(STANDBY_UID_MAP, &uid);
-        if (ownerMatch) return *ownerMatch;
-    }
-    mapEnabled = find_map_entry(POWERSAVE_UID_MAP, &mapSettingKey);
-    if (mapEnabled && *mapEnabled) {
-        ownerMatch = find_map_entry(POWERSAVE_UID_MAP, &uid);
-        if (ownerMatch) return *ownerMatch;
-        return BPF_DROP;
-    }
-    return BPF_PASS;
-}
-
-static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
-    uint32_t sock_uid = get_socket_uid(skb);
-    int match = bpf_owner_match(skb, sock_uid);
-    if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
-        // If an outbound packet is going to be dropped, we do not count that
-        // traffic.
-        return match;
-    }
-
-    uint64_t cookie = get_socket_cookie(skb);
-    struct uid_tag* utag = find_map_entry(COOKIE_TAG_MAP, &cookie);
-    uint32_t uid, tag;
-    if (utag) {
-        uid = utag->uid;
-        tag = utag->tag;
-    } else {
-        uid = sock_uid;
-        tag = 0;
-    }
-
-    struct stats_key key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
-
-    uint8_t* counterSet = find_map_entry(UID_COUNTERSET_MAP, &uid);
-    if (counterSet) key.counterSet = (uint32_t)*counterSet;
-
-    if (tag) {
-        bpf_update_stats(skb, TAG_STATS_MAP, direction, &key);
-    }
-
-    key.tag = 0;
-    bpf_update_stats(skb, UID_STATS_MAP, direction, &key);
-    bpf_update_stats(skb, APP_UID_STATS_MAP, direction, &uid);
-    return match;
-}
diff --git a/client/Android.bp b/client/Android.bp
index 85696cf..3a6e97d 100644
--- a/client/Android.bp
+++ b/client/Android.bp
@@ -14,28 +14,46 @@
 
 cc_library {
     name: "libnetd_client",
-
     srcs: [
         "FwmarkClient.cpp",
         "NetdClient.cpp",
     ],
-
-    header_libs: ["libnetd_client_headers"],
+    header_libs: [
+        "libnetd_client_headers",
+        "libbase_headers", // for unique_fd.h
+    ],
     export_header_lib_headers: ["libnetd_client_headers"],
-
     include_dirs: [
-        "bionic/libc/dns/include",
-        "system/netd/include",
+        "system/netd/resolv",
+        "system/netd/libnetdutils/include",
     ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
+    defaults: ["netd_defaults"],
     product_variables: {
         debuggable: {
             cflags: ["-DNETD_CLIENT_DEBUGGABLE_BUILD"],
         }
     }
 }
+
+cc_test {
+    name: "netdclient_test",
+    srcs: [
+        "NetdClientTest.cpp",
+    ],
+    defaults: ["netd_defaults"],
+    test_suites: ["device-tests"],
+    include_dirs: [
+        "system/netd/resolv",
+        "system/netd/include",
+        "system/netd/libnetdutils/include",
+    ],
+    static_libs: [
+        "libgmock",
+        "libbase",
+        "libnetd_client",
+    ],
+    sanitize: {
+        address: true,
+        recover: [ "all" ],
+    },
+}
diff --git a/client/FwmarkClient.cpp b/client/FwmarkClient.cpp
index 97d509d..cc4893d 100644
--- a/client/FwmarkClient.cpp
+++ b/client/FwmarkClient.cpp
@@ -25,10 +25,15 @@
 #include <sys/un.h>
 #include <unistd.h>
 
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+#include <algorithm>  // std::size()
+#include <iterator>
 
 namespace {
 
+// Env flag to control whether FwmarkClient sends sockets to netd for marking.
+// This can only be disabled in debuggable builds and is meant for kernel testing.
+inline constexpr char ANDROID_NO_USE_FWMARK_CLIENT[] = "ANDROID_NO_USE_FWMARK_CLIENT";
+
 const sockaddr_un FWMARK_SERVER_PATH = {AF_UNIX, "/dev/socket/fwmarkd"};
 
 #if defined(NETD_CLIENT_DEBUGGABLE_BUILD)
@@ -50,16 +55,12 @@
 }  // namespace
 
 bool FwmarkClient::shouldSetFwmark(int family) {
-    if (isOverriddenBy(ANDROID_NO_USE_FWMARK_CLIENT)) {
-        return false;
-    }
-    return family == AF_INET || family == AF_INET6;
+    if (isOverriddenBy(ANDROID_NO_USE_FWMARK_CLIENT)) return false;
+    return FwmarkCommand::isSupportedFamily(family);
 }
 
 bool FwmarkClient::shouldReportConnectComplete(int family) {
-    if (isOverriddenBy(ANDROID_FWMARK_METRICS_ONLY)) {
-        return false;
-    }
+    if (isOverriddenBy(ANDROID_NO_USE_FWMARK_CLIENT)) return false;
     return shouldSetFwmark(family);
 }
 
@@ -94,7 +95,7 @@
     msghdr message;
     memset(&message, 0, sizeof(message));
     message.msg_iov = iov;
-    message.msg_iovlen = ARRAY_SIZE(iov);
+    message.msg_iovlen = std::size(iov);
 
     union {
         cmsghdr cmh;
diff --git a/client/FwmarkClient.h b/client/FwmarkClient.h
index 3b5c6bd..31fcbc4 100644
--- a/client/FwmarkClient.h
+++ b/client/FwmarkClient.h
@@ -40,15 +40,6 @@
     // Returns 0 on success or a negative errno value on failure.
     int send(FwmarkCommand* data, int fd, FwmarkConnectInfo* connectInfo);
 
-    // Env flag to control whether FwmarkClient sends any information at all about network events
-    // back to the system server through FwmarkServer.
-    static constexpr const char* ANDROID_NO_USE_FWMARK_CLIENT = "ANDROID_NO_USE_FWMARK_CLIENT";
-
-    // Env flag to control whether FwmarkClient should exclude detailed information like IP
-    // addresses and only send basic information necessary for marking sockets.
-    // Has no effect if ANDROID_NO_USE_FWMARK_CLIENT is set.
-    static constexpr const char* ANDROID_FWMARK_METRICS_ONLY = "ANDROID_FWMARK_METRICS_ONLY";
-
 private:
     int mChannel;
 };
diff --git a/client/NetdClient.cpp b/client/NetdClient.cpp
index fbbc9e7..df1ece6 100644
--- a/client/NetdClient.cpp
+++ b/client/NetdClient.cpp
@@ -19,19 +19,37 @@
 #include <arpa/inet.h>
 #include <errno.h>
 #include <math.h>
+#include <resolv.h>
+#include <stdlib.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <unistd.h>
 
 #include <atomic>
+#include <string>
+#include <vector>
+
+#include <android-base/parseint.h>
+#include <android-base/unique_fd.h>
 
 #include "Fwmark.h"
 #include "FwmarkClient.h"
 #include "FwmarkCommand.h"
-#include "resolv_netid.h"
-#include "Stopwatch.h"
+#include "netdclient_priv.h"
+#include "netdutils/ResponseCode.h"
+#include "netdutils/Stopwatch.h"
+#include "netid_client.h"
+
+using android::base::ParseInt;
+using android::base::unique_fd;
+using android::netdutils::ResponseCode;
+using android::netdutils::Stopwatch;
 
 namespace {
 
+// Keep this in sync with CMD_BUF_SIZE in FrameworkListener.cpp.
+constexpr size_t MAX_CMD_SIZE = 4096;
+
 std::atomic_uint netIdForProcess(NETID_UNSET);
 std::atomic_uint netIdForResolv(NETID_UNSET);
 
@@ -39,12 +57,35 @@
 typedef int (*ConnectFunctionType)(int, const sockaddr*, socklen_t);
 typedef int (*SocketFunctionType)(int, int, int);
 typedef unsigned (*NetIdForResolvFunctionType)(unsigned);
+typedef int (*DnsOpenProxyType)();
 
 // These variables are only modified at startup (when libc.so is loaded) and never afterwards, so
 // it's okay that they are read later at runtime without a lock.
-Accept4FunctionType libcAccept4 = 0;
-ConnectFunctionType libcConnect = 0;
-SocketFunctionType libcSocket = 0;
+Accept4FunctionType libcAccept4 = nullptr;
+ConnectFunctionType libcConnect = nullptr;
+SocketFunctionType libcSocket = nullptr;
+
+int checkSocket(int socketFd) {
+    if (socketFd < 0) {
+        return -EBADF;
+    }
+    int family;
+    socklen_t familyLen = sizeof(family);
+    if (getsockopt(socketFd, SOL_SOCKET, SO_DOMAIN, &family, &familyLen) == -1) {
+        return -errno;
+    }
+    if (!FwmarkClient::shouldSetFwmark(family)) {
+        return -EAFNOSUPPORT;
+    }
+    return 0;
+}
+
+bool shouldMarkSocket(int socketFd, const sockaddr* dst) {
+    // Only mark inet sockets that are connecting to inet destinations. This excludes, for example,
+    // inet sockets connecting to AF_UNSPEC (i.e., being disconnected), and non-inet sockets that
+    // for some reason the caller wants to attempt to connect to an inet destination.
+    return dst && FwmarkClient::shouldSetFwmark(dst->sa_family) && (checkSocket(socketFd) == 0);
+}
 
 int closeFdAndSetErrno(int fd, int error) {
     close(fd);
@@ -76,8 +117,7 @@
 }
 
 int netdClientConnect(int sockfd, const sockaddr* addr, socklen_t addrlen) {
-    const bool shouldSetFwmark = (sockfd >= 0) && addr
-            && FwmarkClient::shouldSetFwmark(addr->sa_family);
+    const bool shouldSetFwmark = shouldMarkSocket(sockfd, addr);
     if (shouldSetFwmark) {
         FwmarkCommand command = {FwmarkCommand::ON_CONNECT, 0, 0, 0};
         if (int error = FwmarkClient().send(&command, sockfd, nullptr)) {
@@ -109,7 +149,7 @@
     if (socketFd == -1) {
         return -1;
     }
-    unsigned netId = netIdForProcess;
+    unsigned netId = netIdForProcess & ~NETID_USE_LOCAL_NAMESERVERS;
     if (netId != NETID_UNSET && FwmarkClient::shouldSetFwmark(domain)) {
         if (int error = setNetworkForSocket(netId, socketFd)) {
             return closeFdAndSetErrno(socketFd, error);
@@ -144,25 +184,134 @@
     // Verify that we are allowed to use |netId|, by creating a socket and trying to have it marked
     // with the netId. Call libcSocket() directly; else the socket creation (via netdClientSocket())
     // might itself cause another check with the fwmark server, which would be wasteful.
-    int socketFd;
-    if (libcSocket) {
-        socketFd = libcSocket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-    } else {
-        socketFd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-    }
+
+    const auto socketFunc = libcSocket ? libcSocket : socket;
+    int socketFd = socketFunc(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
     if (socketFd < 0) {
         return -errno;
     }
     int error = setNetworkForSocket(netId, socketFd);
     if (!error) {
-        *target = (target == &netIdForResolv) ? requestedNetId : netId;
+        *target = requestedNetId;
     }
     close(socketFd);
     return error;
 }
 
+int dns_open_proxy() {
+    const char* cache_mode = getenv("ANDROID_DNS_MODE");
+    const bool use_proxy = (cache_mode == NULL || strcmp(cache_mode, "local") != 0);
+    if (!use_proxy) {
+        errno = ENOSYS;
+        return -1;
+    }
+
+    const auto socketFunc = libcSocket ? libcSocket : socket;
+    int s = socketFunc(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (s == -1) {
+        return -1;
+    }
+    const int one = 1;
+    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+    static const struct sockaddr_un proxy_addr = {
+            .sun_family = AF_UNIX,
+            .sun_path = "/dev/socket/dnsproxyd",
+    };
+
+    const auto connectFunc = libcConnect ? libcConnect : connect;
+    if (TEMP_FAILURE_RETRY(
+                connectFunc(s, (const struct sockaddr*) &proxy_addr, sizeof(proxy_addr))) != 0) {
+        // Store the errno for connect because we only care about why we can't connect to dnsproxyd
+        int storedErrno = errno;
+        close(s);
+        errno = storedErrno;
+        return -1;
+    }
+
+    return s;
+}
+
+auto divCeil(size_t dividend, size_t divisor) {
+    return ((dividend + divisor - 1) / divisor);
+}
+
+// FrameworkListener only does only read() call, and fails if the read doesn't contain \0
+// Do single write here
+int sendData(int fd, const void* buf, size_t size) {
+    if (fd < 0) {
+        return -EBADF;
+    }
+
+    ssize_t rc = TEMP_FAILURE_RETRY(write(fd, (char*) buf, size));
+    if (rc > 0) {
+        return rc;
+    } else if (rc == 0) {
+        return -EIO;
+    } else {
+        return -errno;
+    }
+}
+
+int readData(int fd, void* buf, size_t size) {
+    if (fd < 0) {
+        return -EBADF;
+    }
+
+    size_t current = 0;
+    for (;;) {
+        ssize_t rc = TEMP_FAILURE_RETRY(read(fd, (char*) buf + current, size - current));
+        if (rc > 0) {
+            current += rc;
+            if (current == size) {
+                break;
+            }
+        } else if (rc == 0) {
+            return -EIO;
+        } else {
+            return -errno;
+        }
+    }
+    return 0;
+}
+
+bool readBE32(int fd, int32_t* result) {
+    int32_t tmp;
+    ssize_t n = TEMP_FAILURE_RETRY(read(fd, &tmp, sizeof(tmp)));
+    if (n < static_cast<ssize_t>(sizeof(tmp))) {
+        return false;
+    }
+    *result = ntohl(tmp);
+    return true;
+}
+
+bool readResponseCode(int fd, int* result) {
+    char buf[4];
+    ssize_t n = TEMP_FAILURE_RETRY(read(fd, &buf, sizeof(buf)));
+    if (n < static_cast<ssize_t>(sizeof(buf))) {
+        return false;
+    }
+
+    // The format of response code is 3 bytes followed by a space.
+    buf[3] = '\0';
+    if (!ParseInt(buf, result)) {
+        errno = EINVAL;
+        return false;
+    }
+
+    return true;
+}
+
 }  // namespace
 
+#define CHECK_SOCKET_IS_MARKABLE(sock)          \
+    do {                                        \
+        int err;                                \
+        if ((err = checkSocket(sock)) != 0) {   \
+            return err;                         \
+        }                                       \
+    } while (false);
+
 // accept() just calls accept4(..., 0), so there's no need to handle accept() separately.
 extern "C" void netdClientInitAccept4(Accept4FunctionType* function) {
     if (function && *function) {
@@ -191,6 +340,12 @@
     }
 }
 
+extern "C" void netdClientInitDnsOpenProxy(DnsOpenProxyType* function) {
+    if (function) {
+        *function = dns_open_proxy;
+    }
+}
+
 extern "C" int getNetworkForSocket(unsigned* netId, int socketFd) {
     if (!netId || socketFd < 0) {
         return -EBADF;
@@ -205,13 +360,11 @@
 }
 
 extern "C" unsigned getNetworkForProcess() {
-    return netIdForProcess;
+    return netIdForProcess & ~NETID_USE_LOCAL_NAMESERVERS;
 }
 
 extern "C" int setNetworkForSocket(unsigned netId, int socketFd) {
-    if (socketFd < 0) {
-        return -EBADF;
-    }
+    CHECK_SOCKET_IS_MARKABLE(socketFd);
     FwmarkCommand command = {FwmarkCommand::SELECT_NETWORK, netId, 0, 0};
     return FwmarkClient().send(&command, socketFd, nullptr);
 }
@@ -233,9 +386,7 @@
 }
 
 extern "C" int setNetworkForUser(uid_t uid, int socketFd) {
-    if (socketFd < 0) {
-        return -EBADF;
-    }
+    CHECK_SOCKET_IS_MARKABLE(socketFd);
     FwmarkCommand command = {FwmarkCommand::SELECT_FOR_USER, 0, uid, 0};
     return FwmarkClient().send(&command, socketFd, nullptr);
 }
@@ -246,11 +397,13 @@
 }
 
 extern "C" int tagSocket(int socketFd, uint32_t tag, uid_t uid) {
+    CHECK_SOCKET_IS_MARKABLE(socketFd);
     FwmarkCommand command = {FwmarkCommand::TAG_SOCKET, 0, uid, tag};
     return FwmarkClient().send(&command, socketFd, nullptr);
 }
 
 extern "C" int untagSocket(int socketFd) {
+    CHECK_SOCKET_IS_MARKABLE(socketFd);
     FwmarkCommand command = {FwmarkCommand::UNTAG_SOCKET, 0, 0, 0};
     return FwmarkClient().send(&command, socketFd, nullptr);
 }
@@ -264,3 +417,129 @@
     FwmarkCommand command = {FwmarkCommand::DELETE_TAGDATA, 0, uid, tag};
     return FwmarkClient().send(&command, -1, nullptr);
 }
+
+extern "C" int resNetworkQuery(unsigned netId, const char* dname, int ns_class, int ns_type,
+                               uint32_t flags) {
+    std::vector<uint8_t> buf(MAX_CMD_SIZE, 0);
+    int len = res_mkquery(ns_o_query, dname, ns_class, ns_type, nullptr, 0, nullptr, buf.data(),
+                          MAX_CMD_SIZE);
+
+    return resNetworkSend(netId, buf.data(), len, flags);
+}
+
+extern "C" int resNetworkSend(unsigned netId, const uint8_t* msg, size_t msglen, uint32_t flags) {
+    // Encode
+    // Base 64 encodes every 3 bytes into 4 characters, but then adds padding to the next
+    // multiple of 4 and a \0
+    const size_t encodedLen = divCeil(msglen, 3) * 4 + 1;
+    std::string encodedQuery(encodedLen - 1, 0);
+    int enLen = b64_ntop(msg, msglen, encodedQuery.data(), encodedLen);
+
+    if (enLen < 0) {
+        // Unexpected behavior, encode failed
+        // b64_ntop only fails when size is too long.
+        return -EMSGSIZE;
+    }
+    // Send
+    netId = getNetworkForResolv(netId);
+    const std::string cmd = "resnsend " + std::to_string(netId) + " " + std::to_string(flags) +
+                            " " + encodedQuery + '\0';
+    if (cmd.size() > MAX_CMD_SIZE) {
+        // Cmd size must less than buffer size of FrameworkListener
+        return -EMSGSIZE;
+    }
+    int fd = dns_open_proxy();
+    if (fd == -1) {
+        return -errno;
+    }
+    ssize_t rc = sendData(fd, cmd.c_str(), cmd.size());
+    if (rc < 0) {
+        close(fd);
+        return rc;
+    }
+    shutdown(fd, SHUT_WR);
+    return fd;
+}
+
+extern "C" int resNetworkResult(int fd, int* rcode, uint8_t* answer, size_t anslen) {
+    int32_t result = 0;
+    unique_fd ufd(fd);
+    // Read -errno/rcode
+    if (!readBE32(fd, &result)) {
+        // Unexpected behavior, read -errno/rcode fail
+        return -errno;
+    }
+    if (result < 0) {
+        // result < 0, it's -errno
+        return result;
+    }
+    // result >= 0, it's rcode
+    *rcode = result;
+
+    // Read answer
+    int32_t size = 0;
+    if (!readBE32(fd, &size)) {
+        // Unexpected behavior, read ans len fail
+        return -EREMOTEIO;
+    }
+    if (anslen < static_cast<size_t>(size)) {
+        // Answer buffer is too small
+        return -EMSGSIZE;
+    }
+    int rc = readData(fd, answer, size);
+    if (rc < 0) {
+        // Reading the answer failed.
+        return rc;
+    }
+    return size;
+}
+
+extern "C" void resNetworkCancel(int fd) {
+    close(fd);
+}
+
+extern "C" int getNetworkForDns(unsigned* dnsNetId) {
+    if (dnsNetId == nullptr) return -EFAULT;
+    int fd = dns_open_proxy();
+    if (fd == -1) {
+        return -errno;
+    }
+    unique_fd ufd(fd);
+    return getNetworkForDnsInternal(fd, dnsNetId);
+}
+
+int getNetworkForDnsInternal(int fd, unsigned* dnsNetId) {
+    if (fd == -1) {
+        return -EBADF;
+    }
+
+    unsigned resolvNetId = getNetworkForResolv(NETID_UNSET);
+
+    const std::string cmd = "getdnsnetid " + std::to_string(resolvNetId);
+    ssize_t rc = sendData(fd, cmd.c_str(), cmd.size() + 1);
+    if (rc < 0) {
+        return rc;
+    }
+
+    int responseCode = 0;
+    // Read responseCode
+    if (!readResponseCode(fd, &responseCode)) {
+        // Unexpected behavior, read responseCode fail
+        return -errno;
+    }
+
+    if (responseCode != ResponseCode::DnsProxyQueryResult) {
+        return -EOPNOTSUPP;
+    }
+
+    int32_t result = 0;
+    // Read -errno/dnsnetid
+    if (!readBE32(fd, &result)) {
+        // Unexpected behavior, read -errno/dnsnetid fail
+        return -errno;
+    }
+
+    *dnsNetId = result;
+
+    return 0;
+}
diff --git a/client/NetdClientTest.cpp b/client/NetdClientTest.cpp
new file mode 100644
index 0000000..6f2fc09
--- /dev/null
+++ b/client/NetdClientTest.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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 <poll.h> /* poll */
+#include <sys/socket.h>
+
+#include <thread>
+
+#include <android-base/parseint.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "NetdClient.h"
+#include "netdclient_priv.h"
+
+namespace {
+
+// Keep in sync with FrameworkListener.cpp (500, "Command not recognized")
+constexpr char NOT_SUPPORT_MSG[] = "500 Command not recognized";
+
+void serverLoop(int dnsProxyFd) {
+    while (true) {
+        pollfd fds[1] = {{.fd = dnsProxyFd, .events = POLLIN}};
+        enum { SERVERFD = 0 };
+
+        const int s = TEMP_FAILURE_RETRY(poll(fds, std::size(fds), -1));
+        if (s <= 0) break;
+
+        if (fds[SERVERFD].revents & POLLIN) {
+            char buf[4096];
+            TEMP_FAILURE_RETRY(read(fds[SERVERFD].fd, &buf, sizeof(buf)));
+            // TODO: verify command
+            TEMP_FAILURE_RETRY(write(fds[SERVERFD].fd, NOT_SUPPORT_MSG, sizeof(NOT_SUPPORT_MSG)));
+        }
+    }
+}
+
+}  // namespace
+
+TEST(NetdClientTest, getNetworkForDnsInternal) {
+    // Test invalid fd
+    unsigned dnsNetId = 0;
+    const int invalidFd = -1;
+    EXPECT_EQ(-EBADF, getNetworkForDnsInternal(invalidFd, &dnsNetId));
+
+    // Test what the client does if the resolver does not support the "getdnsnetid" command.
+    android::base::unique_fd clientFd, serverFd;
+    ASSERT_TRUE(android::base::Socketpair(AF_UNIX, &clientFd, &serverFd));
+
+    std::thread serverThread = std::thread(serverLoop, serverFd.get());
+
+    EXPECT_EQ(-EOPNOTSUPP, getNetworkForDnsInternal(clientFd.get(), &dnsNetId));
+
+    clientFd.reset();  // Causes serverLoop() to exit
+    serverThread.join();
+}
+
+TEST(NetdClientTest, getNetworkForDns) {
+    // Test null input
+    unsigned* testNull = nullptr;
+    EXPECT_EQ(-EFAULT, getNetworkForDns(testNull));
+}
diff --git a/server/binder/android/net/UidRange.aidl b/client/netdclient_priv.h
similarity index 70%
copy from server/binder/android/net/UidRange.aidl
copy to client/netdclient_priv.h
index 55747d0..52952c5 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/client/netdclient_priv.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package android.net;
+#ifndef NETD_CLIENT_NETD_CLIENT_PRIV_H
+#define NETD_CLIENT_NETD_CLIENT_PRIV_H
 
-/**
- * An inclusive range of UIDs.
- *
- * {@hide}
- */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+int getNetworkForDnsInternal(int fd, unsigned* dnsNetId);
+
+#endif  // NETD_CLIENT_NETD_CLIENT_PRIV_H
diff --git a/include/BinderUtil.h b/include/BinderUtil.h
new file mode 100644
index 0000000..9333dcd
--- /dev/null
+++ b/include/BinderUtil.h
@@ -0,0 +1,108 @@
+/*
+ * 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 NETD_INCLUDE_BINDER_UTIL_H
+#define NETD_INCLUDE_BINDER_UTIL_H
+
+#ifdef ANDROID_BINDER_STATUS_H
+#define IS_BINDER_OK(__ex__) (__ex__ == ::android::binder::Status::EX_NONE)
+
+#define EXCEPTION_TO_STRING(__ex__, str)    \
+    case ::android::binder::Status::__ex__: \
+        return str;
+
+#define TO_EXCEPTION(__ex__) __ex__;
+
+#else
+#define IS_BINDER_OK(__ex__) (AStatus_isOk(AStatus_fromExceptionCode(__ex__)))
+
+#define EXCEPTION_TO_STRING(__ex__, str) \
+    case __ex__:                         \
+        return str;
+
+#define TO_EXCEPTION(__ex__) AStatus_getExceptionCode(AStatus_fromExceptionCode(__ex__));
+
+#endif
+
+std::string exceptionToString(int32_t exception) {
+    switch (exception) {
+        EXCEPTION_TO_STRING(EX_SECURITY, "SecurityException")
+        EXCEPTION_TO_STRING(EX_BAD_PARCELABLE, "BadParcelableException")
+        EXCEPTION_TO_STRING(EX_ILLEGAL_ARGUMENT, "IllegalArgumentException")
+        EXCEPTION_TO_STRING(EX_NULL_POINTER, "NullPointerException")
+        EXCEPTION_TO_STRING(EX_ILLEGAL_STATE, "IllegalStateException")
+        EXCEPTION_TO_STRING(EX_NETWORK_MAIN_THREAD, "NetworkMainThreadException")
+        EXCEPTION_TO_STRING(EX_UNSUPPORTED_OPERATION, "UnsupportedOperationException")
+        EXCEPTION_TO_STRING(EX_SERVICE_SPECIFIC, "ServiceSpecificException")
+        EXCEPTION_TO_STRING(EX_PARCELABLE, "ParcelableException")
+        EXCEPTION_TO_STRING(EX_TRANSACTION_FAILED, "TransactionFailedException")
+        default:
+            return "UnknownException";
+    }
+}
+
+using LogFn = std::function<void(const std::string& msg)>;
+
+void binderCallLogFn(const Json::Value& logTransaction, const LogFn& logFn) {
+    using namespace std::string_literals;
+
+    bool hasReturnArgs;
+    std::string output;
+    const Json::Value& returnArgs = logTransaction["_aidl_return"];
+    const Json::Value& inputArgsArray = logTransaction["input_args"];
+
+    hasReturnArgs = !returnArgs.empty();
+    output.append(logTransaction["method_name"].asString() + "("s);
+
+    // input args
+    Json::FastWriter fastWriter;
+    fastWriter.omitEndingLineFeed();
+    for (Json::Value::ArrayIndex i = 0; i < inputArgsArray.size(); ++i) {
+        std::string value = fastWriter.write(inputArgsArray[i]["value"]);
+        output.append(value);
+        if (i != inputArgsArray.size() - 1) {
+            output.append(", "s);
+        }
+    }
+    output.append(")"s);
+
+    const int exceptionCode =
+            TO_EXCEPTION(logTransaction["binder_status"]["exception_code"].asInt());
+
+    if (hasReturnArgs || !IS_BINDER_OK(exceptionCode)) {
+        output.append(" -> "s);
+    }
+
+    // return status
+    if (!IS_BINDER_OK(exceptionCode)) {
+        // an exception occurred
+        const int errCode = logTransaction["binder_status"]["service_specific_error_code"].asInt();
+        output.append(::android::base::StringPrintf(
+                "%s(%d, \"%s\")", exceptionToString(exceptionCode).c_str(),
+                (errCode != 0) ? errCode : exceptionCode,
+                logTransaction["binder_status"]["exception_message"].asString().c_str()));
+    }
+    // return args
+    if (hasReturnArgs) {
+        output.append(::android::base::StringPrintf("{%s}", fastWriter.write(returnArgs).c_str()));
+    }
+    // duration time
+    output.append(
+            ::android::base::StringPrintf(" <%.2fms>", logTransaction["duration_ms"].asFloat()));
+    logFn(output);
+}
+
+#endif /* NETD_INCLUDE_BINDER_UTIL_H */
diff --git a/include/FwmarkCommand.h b/include/FwmarkCommand.h
index 5854233..a78542a 100644
--- a/include/FwmarkCommand.h
+++ b/include/FwmarkCommand.h
@@ -76,6 +76,10 @@
     uint32_t trafficCtrlInfo;  // used in TAG_SOCKET, SET_COUNTERSET and SET_PACIFIER command;
                                // ignored otherwise. Depend on the case, it can be a tag, a
                                // counterSet or a pacifier signal.
+
+    static bool isSupportedFamily(int socketFamily) {
+        return socketFamily == AF_INET || socketFamily == AF_INET6;
+    }
 };
 
 #endif  // NETD_INCLUDE_FWMARK_COMMAND_H
diff --git a/include/NetdClient.h b/include/NetdClient.h
index 2c6edd0..0cfc187 100644
--- a/include/NetdClient.h
+++ b/include/NetdClient.h
@@ -31,6 +31,7 @@
 int setNetworkForSocket(unsigned netId, int socketFd);
 
 unsigned getNetworkForProcess(void);
+
 int setNetworkForProcess(unsigned netId);
 
 int setNetworkForResolv(unsigned netId);
@@ -48,6 +49,16 @@
 int setCounterSet(uint32_t counterSet, uid_t uid);
 
 int deleteTagData(uint32_t tag, uid_t uid);
+
+int resNetworkQuery(unsigned netId, const char* dname, int ns_class, int ns_type, uint32_t flags);
+
+int resNetworkResult(int query_fd, int* rcode, uint8_t* answer, size_t anslen);
+
+int resNetworkSend(unsigned netId, const uint8_t* msg, size_t msglen, uint32_t flags);
+
+void resNetworkCancel(int nsend_fd);
+
+int getNetworkForDns(unsigned* dnsNetId);
 __END_DECLS
 
 #endif  // NETD_INCLUDE_NETD_CLIENT_H
diff --git a/include/NetdPermissions.h b/include/NetdPermissions.h
new file mode 100644
index 0000000..873617f
--- /dev/null
+++ b/include/NetdPermissions.h
@@ -0,0 +1,29 @@
+/*
+ * 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 _NETD_PERMISSIONS_H
+#define _NETD_PERMISSIONS_H
+
+inline constexpr char PERM_CONNECTIVITY_INTERNAL[] = "android.permission.CONNECTIVITY_INTERNAL";
+inline constexpr char PERM_NETWORK_STACK[] = "android.permission.NETWORK_STACK";
+inline constexpr char PERM_MAINLINE_NETWORK_STACK[] = "android.permission.MAINLINE_NETWORK_STACK";
+inline constexpr char PERM_DUMP[] = "android.permission.DUMP";
+inline constexpr char PERM_CONNECTIVITY_USE_RESTRICTED_NETWORKS[] =
+        "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
+inline constexpr char PERM_NETWORK_BYPASS_PRIVATE_DNS[] =
+        "android.permission.NETWORK_BYPASS_PRIVATE_DNS";
+
+#endif  // _NETD_PERMISSIONS_H
diff --git a/include/Permission.h b/include/Permission.h
index 9ed2371..b71593b 100644
--- a/include/Permission.h
+++ b/include/Permission.h
@@ -51,14 +51,4 @@
     }
 }
 
-inline Permission stringToPermission(const char* arg) {
-    if (!strcmp(arg, "NETWORK")) {
-        return PERMISSION_NETWORK;
-    }
-    if (!strcmp(arg, "SYSTEM")) {
-        return PERMISSION_SYSTEM;
-    }
-    return PERMISSION_NONE;
-}
-
 #endif  // NETD_INCLUDE_PERMISSION_H
diff --git a/include/Stopwatch.h b/include/Stopwatch.h
deleted file mode 100644
index 9d3700e..0000000
--- a/include/Stopwatch.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 NETD_INCLUDE_STOPWATCH_H
-#define NETD_INCLUDE_STOPWATCH_H
-
-#include <chrono>
-
-class Stopwatch {
-public:
-    Stopwatch() : mStart(clock::now()) {}
-
-    virtual ~Stopwatch() {};
-
-    float timeTaken() const {
-        return getElapsed(clock::now());
-    }
-
-    float getTimeAndReset() {
-        const auto& now = clock::now();
-        float elapsed = getElapsed(now);
-        mStart = now;
-        return elapsed;
-    }
-
-private:
-    typedef std::chrono::steady_clock clock;
-    typedef std::chrono::time_point<clock> time_point;
-    time_point mStart;
-
-    float getElapsed(const time_point& now) const {
-        using ms = std::chrono::duration<float, std::ratio<1, 1000>>;
-        return (std::chrono::duration_cast<ms>(now - mStart)).count();
-    }
-};
-
-#endif  // NETD_INCLUDE_STOPWATCH_H
diff --git a/include/netid_client.h b/include/netid_client.h
new file mode 100644
index 0000000..4279f89
--- /dev/null
+++ b/include/netid_client.h
@@ -0,0 +1,31 @@
+/*
+ * 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 NETD_CLIENT_NETID_H
+#define NETD_CLIENT_NETID_H
+
+/*
+ * Passing NETID_UNSET as the netId causes system/netd/resolv/DnsProxyListener.cpp to
+ * fill in the appropriate default netId for the query.
+ */
+#define NETID_UNSET 0u
+
+/*
+ * MARK_UNSET represents the default (i.e. unset) value for a socket mark.
+ */
+#define MARK_UNSET 0u
+
+#endif  // NETD_CLIENT_NETID_H
diff --git a/libbpf/Android.bp b/libbpf/Android.bp
deleted file mode 100644
index cf16ae9..0000000
--- a/libbpf/Android.bp
+++ /dev/null
@@ -1,83 +0,0 @@
-//
-// 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.
-//
-
-cc_library_headers {
-    name: "libbpf_headers",
-    vendor_available: false,
-    host_supported: false,
-    export_include_dirs: ["include"],
-    target: {
-        linux_bionic: {
-            enabled: true,
-        },
-    },
-}
-
-cc_library {
-    name: "libbpf",
-    vendor_available: false,
-    host_supported: false,
-    target: {
-        android: {
-            srcs: [
-                "BpfUtils.cpp",
-                "BpfNetworkStats.cpp"
-            ],
-            sanitize: {
-                misc_undefined: ["integer"],
-            },
-        },
-    },
-
-    shared_libs: [
-        "libbase",
-        "libutils",
-        "liblog",
-        "libnetdutils",
-    ],
-    header_libs: [
-        "libbpf_headers"
-    ],
-    export_header_lib_headers: ["libbpf_headers"],
-    local_include_dirs: ["include"],
-
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-Wextra",
-    ],
-}
-
-cc_test {
-    name: "libbpf_test",
-    srcs: [
-        "BpfNetworkStatsTest.cpp",
-        "BpfMapTest.cpp",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-error=unused-variable",
-    ],
-    static_libs: ["libgmock"],
-    shared_libs: [
-        "libbpf",
-        "libbase",
-        "liblog",
-        "libnetdutils",
-        "libutils",
-    ],
-}
diff --git a/libbpf/BpfMapTest.cpp b/libbpf/BpfMapTest.cpp
deleted file mode 100644
index 925117b..0000000
--- a/libbpf/BpfMapTest.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * 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 <netdutils/MockSyscalls.h>
-#include "bpf/BpfMap.h"
-#include "bpf/BpfNetworkStats.h"
-#include "bpf/BpfUtils.h"
-
-using ::testing::_;
-using ::testing::ByMove;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::StrictMock;
-using ::testing::Test;
-
-namespace android {
-namespace bpf {
-
-using base::unique_fd;
-using netdutils::StatusOr;
-
-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() {}
-    int mMapFd;
-
-    void SetUp() {
-        if (!access(PINNED_MAP_PATH, R_OK)) {
-            EXPECT_EQ(0, remove(PINNED_MAP_PATH));
-        }
-        mMapFd = createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint32_t), TEST_MAP_SIZE,
-                           BPF_F_NO_PREALLOC);
-    }
-
-    void TearDown() {
-        if (!access(PINNED_MAP_PATH, R_OK)) {
-            EXPECT_EQ(0, remove(PINNED_MAP_PATH));
-        }
-        close(mMapFd);
-    }
-
-    void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) {
-        EXPECT_FALSE(map.isValid());
-        EXPECT_EQ(-1, map.getMap().get());
-        EXPECT_TRUE(map.getPinnedPath().empty());
-    }
-
-    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_TRUE(isOk(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, StatusOr<uint32_t> value) {
-        ASSERT_TRUE(isOk(value.status()));
-        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_TRUE(isOk(map.writeValue(key, value, BPF_ANY)));
-        }
-    }
-};
-
-TEST_F(BpfMapTest, constructor) {
-    BpfMap<uint32_t, uint32_t> testMap1;
-    checkMapInvalid(testMap1);
-
-    BpfMap<uint32_t, uint32_t> testMap2(mMapFd);
-    checkMapValid(testMap2);
-    EXPECT_TRUE(testMap2.getPinnedPath().empty());
-
-    BpfMap<uint32_t, uint32_t> testMap3(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
-    checkMapValid(testMap3);
-    EXPECT_TRUE(testMap3.getPinnedPath().empty());
-}
-
-TEST_F(BpfMapTest, basicHelpers) {
-    BpfMap<uint32_t, uint32_t> testMap(mMapFd);
-    uint32_t key = TEST_KEY1;
-    uint32_t value_write = TEST_VALUE1;
-    writeToMapAndCheck(testMap, key, value_write);
-    StatusOr<uint32_t> value_read = testMap.readValue(key);
-    checkValueAndStatus(value_write, value_read);
-    StatusOr<uint32_t> key_read = testMap.getFirstKey();
-    checkValueAndStatus(key, key_read);
-    ASSERT_TRUE(isOk(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;
-    testMap.reset(mMapFd);
-    uint32_t key = TEST_KEY1;
-    uint32_t value_write = TEST_VALUE1;
-    writeToMapAndCheck(testMap, key, value_write);
-    testMap.reset();
-    checkMapInvalid(testMap);
-    unique_fd invalidFd(mMapFd);
-    ASSERT_GT(0, findMapEntry(invalidFd, &key, &value_write));
-    ASSERT_EQ(EBADF, errno);
-}
-
-TEST_F(BpfMapTest, moveConstructor) {
-    BpfMap<uint32_t, uint32_t> testMap1(mMapFd);
-    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, iterateEmptyMap) {
-    BpfMap<uint32_t, uint32_t> testMap(mMapFd);
-    auto itr = testMap.begin();
-    ASSERT_NE(testMap.end(), itr);
-    itr.start();
-    ASSERT_EQ(testMap.end(), itr);
-    ASSERT_FALSE(isOk(itr.next()));
-    ASSERT_EQ(testMap.end(), itr);
-}
-
-TEST_F(BpfMapTest, iterator) {
-    BpfMap<uint32_t, uint32_t> testMap(mMapFd);
-    for (uint32_t key = 0; key < TEST_MAP_SIZE; key++) {
-        uint32_t value = key * 10;
-        ASSERT_TRUE(isOk(testMap.writeValue(key, value, BPF_ANY)));
-    }
-    std::vector<uint32_t> valueList;
-    auto itr = testMap.begin();
-    for (itr.start(); itr != testMap.end(); itr.next()) {
-        uint32_t readKey = *itr;
-        StatusOr<uint32_t> readValue = testMap.readValue(readKey);
-        ASSERT_TRUE(isOk(readValue.status()));
-        valueList.push_back(readValue.value());
-    }
-    ASSERT_EQ((size_t)TEST_MAP_SIZE, valueList.size());
-    std::sort(valueList.begin(), valueList.end());
-    for (uint32_t key = 0; key < TEST_MAP_SIZE; key++) {
-        EXPECT_EQ(key * 10, valueList[key]);
-    }
-}
-
-TEST_F(BpfMapTest, twoIterator) {
-    BpfMap<uint32_t, uint32_t> testMap(mMapFd);
-    for (uint32_t key = 0; key < TEST_MAP_SIZE; key++) {
-        uint32_t value = key * 10;
-        ASSERT_TRUE(isOk(testMap.writeValue(key, value, BPF_ANY)));
-    }
-    auto itr1 = testMap.begin();
-    auto itr2 = testMap.begin();
-    ASSERT_EQ(itr1, itr2);
-    ASSERT_TRUE(isOk(itr1.start()));
-    ASSERT_NE(itr1, itr2);
-    ASSERT_TRUE(isOk(itr2.start()));
-    ASSERT_EQ(itr1, itr2);
-    uint32_t count = 0;
-    while (itr1 != testMap.end()) {
-        ASSERT_TRUE(isOk(itr1.next()));
-        count++;
-    }
-    ASSERT_EQ(testMap.end(), itr1);
-    ASSERT_EQ(TEST_MAP_SIZE, count);
-    while (count != 0) {
-        ASSERT_NE(testMap.end(), itr2);
-        count--;
-        ASSERT_TRUE(isOk(itr2.next()));
-    }
-    ASSERT_EQ(itr1, itr2);
-    ASSERT_EQ(testMap.end(), itr2);
-}
-
-TEST_F(BpfMapTest, pinnedToPath) {
-    BpfMap<uint32_t, uint32_t> testMap1(mMapFd);
-    EXPECT_TRUE(isOk(testMap1.pinToPath(PINNED_MAP_PATH)));
-    EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK));
-    EXPECT_EQ(0, testMap1.getPinnedPath().compare(PINNED_MAP_PATH));
-    BpfMap<uint32_t, uint32_t> testMap2(mapRetrieve(PINNED_MAP_PATH, 0));
-    checkMapValid(testMap2);
-    uint32_t key = TEST_KEY1;
-    uint32_t value = TEST_VALUE1;
-    writeToMapAndCheck(testMap1, key, value);
-    StatusOr<uint32_t> value_read = testMap2.readValue(key);
-    checkValueAndStatus(value, value_read);
-}
-
-TEST_F(BpfMapTest, SetUpMap) {
-    BpfMap<uint32_t, uint32_t> testMap1;
-    EXPECT_TRUE(isOk(testMap1.getOrCreate(TEST_MAP_SIZE, PINNED_MAP_PATH, BPF_MAP_TYPE_HASH)));
-    EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK));
-    checkMapValid(testMap1);
-    EXPECT_EQ(0, testMap1.getPinnedPath().compare(PINNED_MAP_PATH));
-    BpfMap<uint32_t, uint32_t> testMap2;
-    testMap2.getOrCreate(TEST_MAP_SIZE, PINNED_MAP_PATH, BPF_MAP_TYPE_HASH);
-    checkMapValid(testMap2);
-    EXPECT_EQ(0, testMap2.getPinnedPath().compare(PINNED_MAP_PATH));
-    uint32_t key = TEST_KEY1;
-    uint32_t value = TEST_VALUE1;
-    writeToMapAndCheck(testMap1, key, value);
-    StatusOr<uint32_t> value_read = testMap2.readValue(key);
-    checkValueAndStatus(value, value_read);
-}
-
-TEST_F(BpfMapTest, iterate) {
-    BpfMap<uint32_t, uint32_t> testMap(mMapFd);
-    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_TRUE(isOk(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);
-    EXPECT_FALSE(isOk(testMap.getFirstKey()));
-}
-
-TEST_F(BpfMapTest, iterateWithValue) {
-    BpfMap<uint32_t, uint32_t> testMap(mMapFd);
-    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_TRUE(isOk(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);
-    EXPECT_FALSE(isOk(testMap.getFirstKey()));
-}
-
-}  // namespace bpf
-}  // namespace android
diff --git a/libbpf/BpfUtils.cpp b/libbpf/BpfUtils.cpp
deleted file mode 100644
index 9a587fb..0000000
--- a/libbpf/BpfUtils.cpp
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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 <linux/bpf.h>
-#include <linux/if_ether.h>
-#include <linux/in.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/utsname.h>
-#include <sstream>
-#include <string>
-
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <netdutils/Slice.h>
-#include <netdutils/StatusOr.h>
-#include "bpf/BpfUtils.h"
-
-using android::base::StringPrintf;
-using android::base::unique_fd;
-using android::base::GetUintProperty;
-using android::netdutils::Slice;
-using android::netdutils::statusFromErrno;
-using android::netdutils::StatusOr;
-
-#define ptr_to_u64(x) ((uint64_t)(uintptr_t)x)
-#define DEFAULT_LOG_LEVEL 1
-
-namespace android {
-namespace bpf {
-
-/*  The bpf_attr is a union which might have a much larger size then the struct we are using, while
- *  The inline initializer only reset the field we are using and leave the reset of the memory as
- *  is. The bpf kernel code will performs a much stricter check to ensure all unused field is 0. So
- *  this syscall will normally fail with E2BIG if we don't do a memset to bpf_attr.
- */
-bool operator==(const StatsKey& lhs, const StatsKey& rhs) {
-    return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.counterSet == rhs.counterSet) &&
-            (lhs.ifaceIndex == rhs.ifaceIndex));
-}
-
-bool operator==(const UidTag& lhs, const UidTag& rhs) {
-    return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag));
-}
-
-bool operator==(const StatsValue& lhs, const StatsValue& rhs) {
-    return ((lhs.rxBytes == rhs.rxBytes) && (lhs.txBytes == rhs.txBytes) &&
-            (lhs.rxPackets == rhs.rxPackets) && (lhs.txPackets == rhs.txPackets));
-}
-
-int bpf(int cmd, Slice bpfAttr) {
-    return syscall(__NR_bpf, cmd, bpfAttr.base(), bpfAttr.size());
-}
-
-int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size, uint32_t max_entries,
-              uint32_t map_flags) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.map_type = map_type;
-    attr.key_size = key_size;
-    attr.value_size = value_size;
-    attr.max_entries = max_entries;
-    attr.map_flags = map_flags;
-
-    return bpf(BPF_MAP_CREATE, Slice(&attr, sizeof(attr)));
-}
-
-int writeToMapEntry(const base::unique_fd& map_fd, void* key, void* value, uint64_t flags) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.map_fd = map_fd.get();
-    attr.key = ptr_to_u64(key);
-    attr.value = ptr_to_u64(value);
-    attr.flags = flags;
-
-    return bpf(BPF_MAP_UPDATE_ELEM, Slice(&attr, sizeof(attr)));
-}
-
-int findMapEntry(const base::unique_fd& map_fd, void* key, void* value) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.map_fd = map_fd.get();
-    attr.key = ptr_to_u64(key);
-    attr.value = ptr_to_u64(value);
-
-    return bpf(BPF_MAP_LOOKUP_ELEM, Slice(&attr, sizeof(attr)));
-}
-
-int deleteMapEntry(const base::unique_fd& map_fd, void* key) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.map_fd = map_fd.get();
-    attr.key = ptr_to_u64(key);
-
-    return bpf(BPF_MAP_DELETE_ELEM, Slice(&attr, sizeof(attr)));
-}
-
-int getNextMapKey(const base::unique_fd& map_fd, void* key, void* next_key) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.map_fd = map_fd.get();
-    attr.key = ptr_to_u64(key);
-    attr.next_key = ptr_to_u64(next_key);
-
-    return bpf(BPF_MAP_GET_NEXT_KEY, Slice(&attr, sizeof(attr)));
-}
-
-int getFirstMapKey(const base::unique_fd& map_fd, void* firstKey) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.map_fd = map_fd.get();
-    attr.key = 0;
-    attr.next_key = ptr_to_u64(firstKey);
-
-    return bpf(BPF_MAP_GET_NEXT_KEY, Slice(&attr, sizeof(attr)));
-}
-
-int bpfProgLoad(bpf_prog_type prog_type, Slice bpf_insns, const char* license,
-                uint32_t kern_version, Slice bpf_log) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.prog_type = prog_type;
-    attr.insns = ptr_to_u64(bpf_insns.base());
-    attr.insn_cnt = bpf_insns.size() / sizeof(struct bpf_insn);
-    attr.license = ptr_to_u64((void*)license);
-    attr.log_buf = ptr_to_u64(bpf_log.base());
-    attr.log_size = bpf_log.size();
-    attr.log_level = DEFAULT_LOG_LEVEL;
-    attr.kern_version = kern_version;
-    int ret = bpf(BPF_PROG_LOAD, Slice(&attr, sizeof(attr)));
-
-    if (ret < 0) {
-        std::string prog_log = netdutils::toString(bpf_log);
-        std::istringstream iss(prog_log);
-        for (std::string line; std::getline(iss, line);) {
-            ALOGE("%s", line.c_str());
-        }
-    }
-    return ret;
-}
-
-int mapPin(const base::unique_fd& map_fd, const char* pathname) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.pathname = ptr_to_u64((void*)pathname);
-    attr.bpf_fd = map_fd.get();
-
-    return bpf(BPF_OBJ_PIN, Slice(&attr, sizeof(attr)));
-}
-
-int mapRetrieve(const char* pathname, uint32_t flag) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.pathname = ptr_to_u64((void*)pathname);
-    attr.file_flags = flag;
-    return bpf(BPF_OBJ_GET, Slice(&attr, sizeof(attr)));
-}
-
-int attachProgram(bpf_attach_type type, uint32_t prog_fd, uint32_t cg_fd) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.target_fd = cg_fd;
-    attr.attach_bpf_fd = prog_fd;
-    attr.attach_type = type;
-
-    return bpf(BPF_PROG_ATTACH, Slice(&attr, sizeof(attr)));
-}
-
-int detachProgram(bpf_attach_type type, uint32_t cg_fd) {
-    bpf_attr attr;
-    memset(&attr, 0, sizeof(attr));
-    attr.target_fd = cg_fd;
-    attr.attach_type = type;
-
-    return bpf(BPF_PROG_DETACH, Slice(&attr, sizeof(attr)));
-}
-
-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;
-}
-
-bool hasBpfSupport() {
-    struct utsname buf;
-    int kernel_version_major;
-    int kernel_version_minor;
-
-    uint64_t api_level = GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
-    if (api_level == 0) {
-        ALOGE("Cannot determine initial API level of the device");
-        api_level = GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
-    }
-
-    int ret = uname(&buf);
-    if (ret) {
-        return false;
-    }
-    char dummy;
-    ret = sscanf(buf.release, "%d.%d%c", &kernel_version_major, &kernel_version_minor, &dummy);
-    if (ret >= 2 && ((kernel_version_major > 4) ||
-                         (kernel_version_major == 4 && kernel_version_minor >= 9))) {
-        // Check if the device is shipped originally with android P.
-        return api_level >= MINIMUM_API_REQUIRED;
-    }
-    return false;
-}
-
-}  // namespace bpf
-}  // namespace android
diff --git a/libbpf/include/bpf/BpfMap.h b/libbpf/include/bpf/BpfMap.h
deleted file mode 100644
index 81d7efe..0000000
--- a/libbpf/include/bpf/BpfMap.h
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * 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 BPF_BPFMAP_H
-#define BPF_BPFMAP_H
-
-#include <linux/bpf.h>
-
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <utils/Log.h>
-#include "bpf/BpfUtils.h"
-#include "netdutils/Status.h"
-#include "netdutils/StatusOr.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 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:
-    class const_iterator {
-      public:
-        netdutils::Status start() {
-            if (mMap == nullptr) {
-                return netdutils::statusFromErrno(EINVAL, "Invalid map iterator");
-            }
-            auto firstKey = mMap->getFirstKey();
-            if (isOk(firstKey)) {
-                mCurKey = firstKey.value();
-            } else if (firstKey.status().code() == ENOENT) {
-                // The map is empty.
-                mMap = nullptr;
-                memset(&mCurKey, 0, sizeof(Key));
-            } else {
-                return firstKey.status();
-            }
-            return netdutils::status::ok;
-        }
-
-        netdutils::StatusOr<Key> next() {
-            if (mMap == nullptr) {
-                return netdutils::statusFromErrno(ENOENT, "Iterating past end of map");
-            }
-            auto nextKey = mMap->getNextKey(mCurKey);
-            if (isOk(nextKey)) {
-                mCurKey = nextKey.value();
-            } else if (nextKey.status().code() == ENOENT) {
-                // iterator reached the end of map
-                mMap = nullptr;
-                memset(&mCurKey, 0, sizeof(Key));
-            } else {
-                return nextKey.status();
-            }
-            return mCurKey;
-        }
-
-        const Key operator*() { return mCurKey; }
-
-        bool operator==(const const_iterator& other) const {
-            return (mMap == other.mMap) && (mCurKey == other.mCurKey);
-        }
-
-        bool operator!=(const const_iterator& other) const { return !(*this == other); }
-
-        const_iterator(const BpfMap<Key, Value>* map) : mMap(map) {
-            memset(&mCurKey, 0, sizeof(Key));
-        }
-
-      private:
-        const BpfMap<Key, Value> * mMap;
-        Key mCurKey;
-    };
-
-    BpfMap<Key, Value>() : mMapFd(-1){};
-    BpfMap<Key, Value>(int fd) : mMapFd(fd){};
-    BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags) {
-        int map_fd = createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags);
-        if (map_fd < 0) {
-            mMapFd.reset(-1);
-        } else {
-            mMapFd.reset(map_fd);
-        }
-    }
-
-    netdutils::Status pinToPath(const std::string path) {
-        int ret = mapPin(mMapFd, path.c_str());
-        if (ret) {
-            return netdutils::statusFromErrno(errno,
-                                              base::StringPrintf("pin to %s failed", path.c_str()));
-        }
-        mPinnedPath = path;
-        return netdutils::status::ok;
-    }
-
-    netdutils::StatusOr<Key> getFirstKey() const {
-        Key firstKey;
-        if (getFirstMapKey(mMapFd, &firstKey)) {
-            return netdutils::statusFromErrno(
-                errno, base::StringPrintf("Get firstKey map %d failed", mMapFd.get()));
-        }
-        return firstKey;
-    }
-
-    netdutils::StatusOr<Key> getNextKey(const Key& key) const {
-        Key nextKey;
-        if (getNextMapKey(mMapFd, const_cast<Key*>(&key), &nextKey)) {
-            return netdutils::statusFromErrno(
-                errno, base::StringPrintf("Get next key of map %d failed", mMapFd.get()));
-        }
-        return nextKey;
-    }
-
-    netdutils::Status writeValue(const Key& key, const Value& value, uint64_t flags) {
-        if (writeToMapEntry(mMapFd, const_cast<Key*>(&key), const_cast<Value*>(&value), flags)) {
-            return netdutils::statusFromErrno(
-                errno, base::StringPrintf("write to map %d failed", mMapFd.get()));
-        }
-        return netdutils::status::ok;
-    }
-
-    netdutils::StatusOr<Value> readValue(const Key key) const {
-        Value value;
-        if (findMapEntry(mMapFd, const_cast<Key*>(&key), &value)) {
-            return netdutils::statusFromErrno(
-                errno, base::StringPrintf("read value of map %d failed", mMapFd.get()));
-        }
-        return value;
-    }
-
-    netdutils::Status deleteValue(const Key& key) {
-        if (deleteMapEntry(mMapFd, const_cast<Key*>(&key))) {
-            return netdutils::statusFromErrno(
-                errno, base::StringPrintf("delete entry from map %d failed", mMapFd.get()));
-        }
-        return netdutils::status::ok;
-    }
-
-    // Function that tries to get map from a pinned path, if the map doesn't
-    // exist yet, create a new one and pinned to the path.
-    netdutils::Status getOrCreate(const uint32_t maxEntries, const char* path,
-                                  const bpf_map_type mapType);
-
-    // Iterate through the map and handle each key retrieved based on the filter
-    // without modification of map content.
-    netdutils::Status iterate(
-        const std::function<netdutils::Status(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.
-    netdutils::Status iterateWithValue(
-        const std::function<netdutils::Status(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
-    netdutils::Status iterate(
-        const std::function<netdutils::Status(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.
-    netdutils::Status iterateWithValue(
-        const std::function<netdutils::Status(const Key& key, const Value& value,
-                                              BpfMap<Key, Value>& map)>& filter);
-
-    const base::unique_fd& getMap() const { return mMapFd; };
-
-    const std::string getPinnedPath() const { return mPinnedPath; };
-
-    // Move constructor
-    void operator=(BpfMap<Key, Value>&& other) {
-        mMapFd = std::move(other.mMapFd);
-        if (!other.mPinnedPath.empty()) {
-            mPinnedPath = other.mPinnedPath;
-        } else {
-            mPinnedPath.clear();
-        }
-        other.reset();
-    }
-
-    void reset(int fd = -1) {
-        mMapFd.reset(fd);
-        mPinnedPath.clear();
-    }
-
-    bool isValid() const { return mMapFd != -1; }
-
-    const_iterator begin() const { return const_iterator(this); }
-
-    const_iterator end() const { return const_iterator(nullptr); }
-
-  private:
-    base::unique_fd mMapFd;
-    std::string mPinnedPath;
-};
-
-template <class Key, class Value>
-netdutils::Status BpfMap<Key, Value>::getOrCreate(const uint32_t maxEntries, const char* path,
-                                                  bpf_map_type mapType) {
-    int ret = access(path, R_OK);
-    /* Check the pinned location first to check if the map is already there.
-     * otherwise create a new one.
-     */
-    if (ret == 0) {
-        mMapFd = base::unique_fd(mapRetrieve(path, 0));
-        if (mMapFd == -1) {
-            reset();
-            return netdutils::statusFromErrno(
-                errno,
-                base::StringPrintf("pinned map not accessible or does not exist: (%s)\n", path));
-        }
-        mPinnedPath = path;
-    } else if (ret == -1 && errno == ENOENT) {
-        mMapFd = base::unique_fd(
-            createMap(mapType, sizeof(Key), sizeof(Value), maxEntries, BPF_F_NO_PREALLOC));
-        if (mMapFd == -1) {
-            reset();
-            return netdutils::statusFromErrno(errno,
-                                              base::StringPrintf("map create failed!: %s", path));
-        }
-        netdutils::Status pinStatus = pinToPath(path);
-        if (!isOk(pinStatus)) {
-            reset();
-            return pinStatus;
-        }
-        mPinnedPath = path;
-    } else {
-        return netdutils::statusFromErrno(
-            errno, base::StringPrintf("pinned map not accessible: %s", path));
-    }
-    return netdutils::status::ok;
-}
-
-template <class Key, class Value>
-netdutils::Status BpfMap<Key, Value>::iterate(
-    const std::function<netdutils::Status(const Key& key, const BpfMap<Key, Value>& map)>& filter)
-    const {
-    const_iterator itr = this->begin();
-    RETURN_IF_NOT_OK(itr.start());
-    while (itr != this->end()) {
-        Key prevKey = *itr;
-        netdutils::Status advanceStatus = itr.next();
-        RETURN_IF_NOT_OK(filter(prevKey, *this));
-        RETURN_IF_NOT_OK(advanceStatus);
-    }
-    return netdutils::status::ok;
-}
-
-template <class Key, class Value>
-netdutils::Status BpfMap<Key, Value>::iterateWithValue(
-    const std::function<netdutils::Status(const Key& key, const Value& value,
-                                          const BpfMap<Key, Value>& map)>& filter) const {
-    const_iterator itr = this->begin();
-    RETURN_IF_NOT_OK(itr.start());
-    while (itr != this->end()) {
-        Key prevKey = *itr;
-        Value prevValue;
-        ASSIGN_OR_RETURN(prevValue, this->readValue(prevKey));
-        netdutils::Status advanceStatus = itr.next();
-        RETURN_IF_NOT_OK(filter(prevKey, prevValue, *this));
-        RETURN_IF_NOT_OK(advanceStatus);
-    }
-    return netdutils::status::ok;
-}
-
-template <class Key, class Value>
-netdutils::Status BpfMap<Key, Value>::iterate(
-    const std::function<netdutils::Status(const Key& key, BpfMap<Key, Value>& map)>& filter) {
-    const_iterator itr = this->begin();
-    RETURN_IF_NOT_OK(itr.start());
-    while (itr != this->end()) {
-        Key prevKey = *itr;
-        netdutils::Status advanceStatus = itr.next();
-        RETURN_IF_NOT_OK(filter(prevKey, *this));
-        RETURN_IF_NOT_OK(advanceStatus);
-    }
-    return netdutils::status::ok;
-}
-
-template <class Key, class Value>
-netdutils::Status BpfMap<Key, Value>::iterateWithValue(
-    const std::function<netdutils::Status(const Key& key, const Value& value,
-                                          BpfMap<Key, Value>& map)>& filter) {
-    const_iterator itr = this->begin();
-    RETURN_IF_NOT_OK(itr.start());
-    while (itr != this->end()) {
-        Key prevKey = *itr;
-        Value prevValue;
-        ASSIGN_OR_RETURN(prevValue, this->readValue(prevKey));
-        netdutils::Status advanceStatus = itr.next();
-        RETURN_IF_NOT_OK(filter(prevKey, prevValue, *this));
-        RETURN_IF_NOT_OK(advanceStatus);
-    }
-    return netdutils::status::ok;
-}
-
-}  // namespace bpf
-}  // namespace android
-
-#endif
diff --git a/libbpf/include/bpf/BpfUtils.h b/libbpf/include/bpf/BpfUtils.h
deleted file mode 100644
index ac107df..0000000
--- a/libbpf/include/bpf/BpfUtils.h
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 BPF_BPFUTILS_H
-#define BPF_BPFUTILS_H
-
-#include <linux/bpf.h>
-#include <linux/if_ether.h>
-#include <linux/in.h>
-#include <linux/unistd.h>
-#include <net/if.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-
-#include "android-base/unique_fd.h"
-#include "netdutils/Slice.h"
-#include "netdutils/StatusOr.h"
-
-#define BPF_PASS 1
-#define BPF_DROP 0
-
-namespace android {
-namespace bpf {
-
-struct UidTag {
-    uint32_t uid;
-    uint32_t tag;
-};
-
-struct StatsKey {
-    uint32_t uid;
-    uint32_t tag;
-    uint32_t counterSet;
-    uint32_t ifaceIndex;
-};
-
-struct StatsValue {
-    uint64_t rxPackets;
-    uint64_t rxBytes;
-    uint64_t txPackets;
-    uint64_t txBytes;
-};
-
-struct Stats {
-    uint64_t rxBytes;
-    uint64_t rxPackets;
-    uint64_t txBytes;
-    uint64_t txPackets;
-    uint64_t tcpRxPackets;
-    uint64_t tcpTxPackets;
-};
-
-struct IfaceValue {
-    char name[IFNAMSIZ];
-};
-
-#ifndef DEFAULT_OVERFLOWUID
-#define DEFAULT_OVERFLOWUID 65534
-#endif
-
-#define BPF_PATH "/sys/fs/bpf"
-
-// Since we cannot garbage collect the stats map since device boot, we need to make these maps as
-// large as possible. The maximum size of number of map entries we can have is depend on the rlimit
-// of MEM_LOCK granted to netd. The memory space needed by each map can be calculated by the
-// following fomula:
-//      elem_size = 40 + roundup(key_size, 8) + roundup(value_size, 8)
-//      cost = roundup_pow_of_two(max_entries) * 16 + elem_size * max_entries +
-//              elem_size * number_of_CPU
-// And the cost of each map currently used is(assume the device have 8 CPUs):
-// cookie_tag_map:      key:  8 bytes, value:  8 bytes, cost:  822592 bytes    =   823Kbytes
-// uid_counter_set_map: key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
-// app_uid_stats_map:   key:  4 bytes, value: 32 bytes, cost: 1062784 bytes    =  1063Kbytes
-// uid_stats_map:       key: 16 bytes, value: 32 bytes, cost: 1142848 bytes    =  1143Kbytes
-// tag_stats_map:       key: 16 bytes, value: 32 bytes, cost: 1142848 bytes    =  1143Kbytes
-// iface_index_name_map:key:  4 bytes, value: 16 bytes, cost:   80896 bytes    =    81Kbytes
-// iface_stats_map:     key:  4 bytes, value: 32 bytes, cost:   97024 bytes    =    97Kbytes
-// dozable_uid_map:     key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
-// standby_uid_map:     key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
-// powersave_uid_map:   key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
-// total:                                                                         4930Kbytes
-// It takes maximum 4.9MB kernel memory space if all maps are full, which requires any devices
-// running this module to have a memlock rlimit to be larger then 5MB. In the old qtaguid module,
-// we don't have a total limit for data entries but only have limitation of tags each uid can have.
-// (default is 1024 in kernel);
-
-constexpr const int COOKIE_UID_MAP_SIZE = 10000;
-constexpr const int UID_COUNTERSET_MAP_SIZE = 2000;
-constexpr const int UID_STATS_MAP_SIZE = 10000;
-constexpr const int TAG_STATS_MAP_SIZE = 10000;
-constexpr const int IFACE_INDEX_NAME_MAP_SIZE = 1000;
-constexpr const int IFACE_STATS_MAP_SIZE = 1000;
-constexpr const int UID_OWNER_MAP_SIZE = 2000;
-
-constexpr const char* BPF_EGRESS_PROG_PATH = BPF_PATH "/egress_prog";
-constexpr const char* BPF_INGRESS_PROG_PATH = BPF_PATH "/ingress_prog";
-constexpr const char* XT_BPF_INGRESS_PROG_PATH = BPF_PATH "/xt_bpf_ingress_prog";
-constexpr const char* XT_BPF_EGRESS_PROG_PATH = BPF_PATH "/xt_bpf_egress_prog";
-
-constexpr const char* CGROUP_ROOT_PATH = "/dev/cg2_bpf";
-
-constexpr const char* COOKIE_TAG_MAP_PATH = BPF_PATH "/traffic_cookie_tag_map";
-constexpr const char* UID_COUNTERSET_MAP_PATH = BPF_PATH "/traffic_uid_counterSet_map";
-constexpr const char* APP_UID_STATS_MAP_PATH = BPF_PATH "/traffic_app_uid_stats_map";
-constexpr const char* UID_STATS_MAP_PATH = BPF_PATH "/traffic_uid_stats_map";
-constexpr const char* TAG_STATS_MAP_PATH = BPF_PATH "/traffic_tag_stats_map";
-constexpr const char* IFACE_INDEX_NAME_MAP_PATH = BPF_PATH "/traffic_iface_index_name_map";
-constexpr const char* IFACE_STATS_MAP_PATH = BPF_PATH "/traffic_iface_stats_map";
-constexpr const char* DOZABLE_UID_MAP_PATH = BPF_PATH "/traffic_dozable_uid_map";
-constexpr const char* STANDBY_UID_MAP_PATH = BPF_PATH "/traffic_standby_uid_map";
-constexpr const char* POWERSAVE_UID_MAP_PATH = BPF_PATH "/traffic_powersave_uid_map";
-
-constexpr const int OVERFLOW_COUNTERSET = 2;
-
-constexpr const uint64_t NONEXISTENT_COOKIE = 0;
-
-constexpr const int MINIMUM_API_REQUIRED = 28;
-
-int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
-              uint32_t max_entries, uint32_t map_flags);
-int writeToMapEntry(const base::unique_fd& map_fd, void* key, void* value, uint64_t flags);
-int findMapEntry(const base::unique_fd& map_fd, void* key, void* value);
-int deleteMapEntry(const base::unique_fd& map_fd, void* key);
-int getNextMapKey(const base::unique_fd& map_fd, void* key, void* next_key);
-int getFirstMapKey(const base::unique_fd& map_fd, void* firstKey);
-int bpfProgLoad(bpf_prog_type prog_type, netdutils::Slice bpf_insns, const char* license,
-                uint32_t kern_version, netdutils::Slice bpf_log);
-int mapPin(const base::unique_fd& map_fd, const char* pathname);
-int mapRetrieve(const char* pathname, uint32_t flags);
-int attachProgram(bpf_attach_type type, uint32_t prog_fd, uint32_t cg_fd);
-int detachProgram(bpf_attach_type type, uint32_t cg_fd);
-uint64_t getSocketCookie(int sockFd);
-bool hasBpfSupport();
-
-#define SKIP_IF_BPF_NOT_SUPPORTED     \
-    do {                              \
-        if (!hasBpfSupport()) return; \
-    } while (0)
-
-constexpr int BPF_CONTINUE = 0;
-constexpr int BPF_DELETED = 1;
-
-bool operator==(const StatsValue& lhs, const StatsValue& rhs);
-bool operator==(const UidTag& lhs, const UidTag& rhs);
-bool operator==(const StatsKey& lhs, const StatsKey& rhs);
-}  // namespace bpf
-}  // namespace android
-
-#endif
diff --git a/libbpf/include/bpf/bpf_shared.h b/libbpf/include/bpf/bpf_shared.h
deleted file mode 100644
index 217b76f..0000000
--- a/libbpf/include/bpf/bpf_shared.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-// const values shared by both kernel program and userspace bpfloader
-
-#define BPF_CGROUP_INGRESS_PROG_NAME "cgroup_ingress_prog"
-#define BPF_CGROUP_EGRESS_PROG_NAME "cgroup_egress_prog"
-#define XT_BPF_INGRESS_PROG_NAME "xt_ingress_prog"
-#define XT_BPF_EGRESS_PROG_NAME "xt_egress_prog"
-
-#define COOKIE_TAG_MAP 0xbfceaaffffffffff
-#define UID_COUNTERSET_MAP 0xbfdceeafffffffff
-#define APP_UID_STATS_MAP 0xbfa1daafffffffff
-#define UID_STATS_MAP 0xbfdaafffffffffff
-#define TAG_STATS_MAP 0xbfaaafffffffffff
-#define IFACE_STATS_MAP 0xbf1faceaafffffff
-#define DOZABLE_UID_MAP 0Xbfd0ab1e1dafffff
-#define STANDBY_UID_MAP 0Xbfadb1daffffffff
-#define POWERSAVE_UID_MAP 0Xbf0eae1dafffffff
-// These are also defined in NetdConstants.h, but we want to minimize the number of headers
-// included by the BPF kernel program.
-// TODO: refactor the the following constant into a seperate file so
-// NetdConstants.h can also include it from there.
-#define MIN_SYSTEM_UID 0
-#define MAX_SYSTEM_UID 9999
-#define UID_MAP_ENABLED UINT32_MAX
diff --git a/libnetdbpf/Android.bp b/libnetdbpf/Android.bp
new file mode 100644
index 0000000..bd25535
--- /dev/null
+++ b/libnetdbpf/Android.bp
@@ -0,0 +1,51 @@
+//
+// 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.
+//
+
+cc_library {
+    name: "libnetdbpf",
+    vendor_available: false,
+    host_supported: false,
+    srcs: [
+        "BpfNetworkStats.cpp"
+    ],
+    shared_libs: [
+        "libbase",
+        "libbpf_android",
+        "liblog",
+        "libnetdutils",
+        "libutils",
+    ],
+    export_include_dirs: ["include"],
+    defaults: ["netd_defaults"],
+}
+
+cc_test {
+    name: "libnetdbpf_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "BpfNetworkStatsTest.cpp",
+    ],
+    defaults: ["netd_defaults"],
+    static_libs: ["libgmock"],
+    shared_libs: [
+        "libbase",
+        "libbpf_android",
+        "liblog",
+        "libnetdbpf",
+        "libnetdutils",
+        "libutils",
+    ],
+}
diff --git a/libbpf/BpfNetworkStats.cpp b/libnetdbpf/BpfNetworkStats.cpp
similarity index 66%
rename from libbpf/BpfNetworkStats.cpp
rename to libnetdbpf/BpfNetworkStats.cpp
index eafbb5e..fe86cc1 100644
--- a/libbpf/BpfNetworkStats.cpp
+++ b/libnetdbpf/BpfNetworkStats.cpp
@@ -26,7 +26,8 @@
 #include "android-base/strings.h"
 #include "android-base/unique_fd.h"
 #include "bpf/BpfMap.h"
-#include "bpf/BpfNetworkStats.h"
+#include "netdbpf/BpfNetworkStats.h"
+#include "netdbpf/bpf_shared.h"
 
 #ifdef LOG_TAG
 #undef LOG_TAG
@@ -39,6 +40,10 @@
 
 using netdutils::Status;
 
+// The target map for stats reading should be the inactive map, which is oppsite
+// from the config value.
+static constexpr char const* STATS_MAP_PATH[] = {STATS_MAP_B_PATH, STATS_MAP_A_PATH};
+
 static constexpr uint32_t BPF_OPEN_FLAGS = BPF_F_RDONLY;
 
 int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
@@ -50,7 +55,7 @@
         stats->rxBytes = statsEntry.value().rxBytes;
         stats->txBytes = statsEntry.value().txBytes;
     }
-    return -statsEntry.status().code();
+    return statsEntry.status().code() == ENOENT ? 0 : -statsEntry.status().code();
 }
 
 int bpfGetUidStats(uid_t uid, Stats* stats) {
@@ -59,7 +64,7 @@
 
     if (!appUidStatsMap.isValid()) {
         int ret = -errno;
-        ALOGE("Opening appUidStatsMap(%s) failed: %s", UID_STATS_MAP_PATH, strerror(errno));
+        ALOGE("Opening appUidStatsMap(%s) failed: %s", APP_UID_STATS_MAP_PATH, strerror(errno));
         return ret;
     }
     return bpfGetUidStatsInternal(uid, stats, appUidStatsMap);
@@ -161,47 +166,67 @@
               strerror(res.code()));
         return -res.code();
     }
+
+    // Since eBPF use hash map to record stats, network stats collected from
+    // eBPF will be out of order. And the performance of findIndexHinted in
+    // NetworkStats will also be impacted.
+    //
+    // Furthermore, since the StatsKey contains iface index, the network stats
+    // reported to framework would create items with the same iface, uid, tag
+    // and set, which causes NetworkStats maps wrong item to subtract.
+    //
+    // Thus, the stats needs to be properly sorted and grouped before reported.
+    groupNetworkStats(lines);
     return 0;
 }
 
 int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
                                const std::vector<std::string>& limitIfaces, int limitTag,
                                int limitUid) {
-    int ret = 0;
     BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(
         mapRetrieve(IFACE_INDEX_NAME_MAP_PATH, BPF_OPEN_FLAGS));
     if (!ifaceIndexNameMap.isValid()) {
-        ret = -errno;
+        int ret = -errno;
         ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
         return ret;
     }
 
-    // If the caller did not pass in TAG_NONE, read tag data.
-    if (limitTag != TAG_NONE) {
-        BpfMap<StatsKey, StatsValue> tagStatsMap(mapRetrieve(TAG_STATS_MAP_PATH, BPF_OPEN_FLAGS));
-        if (!tagStatsMap.isValid()) {
-            ret = -errno;
-            ALOGE("get tagStats map fd failed: %s", strerror(errno));
-            return ret;
-        }
-        ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid,
-                                                 tagStatsMap, ifaceIndexNameMap);
-        if (ret) return ret;
+    BpfMap<uint32_t, uint8_t> configurationMap(mapRetrieve(CONFIGURATION_MAP_PATH, 0));
+    if (!configurationMap.isValid()) {
+        int ret = -errno;
+        ALOGE("get configuration map fd failed: %s", strerror(errno));
+        return ret;
+    }
+    auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+    if (!isOk(configuration)) {
+        ALOGE("Cannot read the old configuration from map: %s",
+              configuration.status().msg().c_str());
+        return -configuration.status().code();
+    }
+    const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
+    BpfMap<StatsKey, StatsValue> statsMap(mapRetrieve(statsMapPath, 0));
+    if (!statsMap.isValid()) {
+        int ret = -errno;
+        ALOGE("get stats map fd failed: %s, path: %s", strerror(errno), statsMapPath);
+        return ret;
     }
 
-    // If the caller did not pass in a specific tag (i.e., if limitTag is TAG_NONE(0) or
-    // TAG_ALL(-1)) read UID data.
-    if (limitTag == TAG_NONE || limitTag == TAG_ALL) {
-        BpfMap<StatsKey, StatsValue> uidStatsMap(mapRetrieve(UID_STATS_MAP_PATH, BPF_OPEN_FLAGS));
-        if (!uidStatsMap.isValid()) {
-            ret = -errno;
-            ALOGE("Opening map fd from %s failed: %s", UID_STATS_MAP_PATH, strerror(errno));
-            return ret;
-        }
-        ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid,
-                                                 uidStatsMap, ifaceIndexNameMap);
+    // It is safe to read and clear the old map now since the
+    // networkStatsFactory should call netd to swap the map in advance already.
+    int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid, statsMap,
+                                                 ifaceIndexNameMap);
+    if (ret) {
+        ALOGE("parse detail network stats failed: %s", strerror(errno));
+        return ret;
     }
-    return ret;
+
+    Status res = statsMap.clear();
+    if (!isOk(res)) {
+        ALOGE("Clean up current stats map failed: %s", strerror(res.code()));
+        return -res.code();
+    }
+
+    return 0;
 }
 
 int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
@@ -226,6 +251,8 @@
               strerror(res.code()));
         return -res.code();
     }
+
+    groupNetworkStats(lines);
     return 0;
 }
 
@@ -252,5 +279,66 @@
     return (uint64_t)uid << 32 | tag;
 }
 
+void groupNetworkStats(std::vector<stats_line>* lines) {
+    if (lines->size() <= 1) return;
+    std::sort(lines->begin(), lines->end());
+
+    // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
+    size_t nextOutput = 0;
+    for (size_t i = 1; i < lines->size(); i++) {
+        if (lines->at(nextOutput) == lines->at(i)) {
+            lines->at(nextOutput) += lines->at(i);
+        } else {
+            nextOutput++;
+            if (nextOutput != i) {
+                lines->at(nextOutput) = lines->at(i);
+            }
+        }
+    }
+
+    if (lines->size() != nextOutput + 1) {
+        lines->resize(nextOutput + 1);
+    }
+}
+
+// True if lhs equals to rhs, only compare iface, uid, tag and set.
+bool operator==(const stats_line& lhs, const stats_line& rhs) {
+    return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) &&
+            !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)));
+}
+
+// True if lhs is smaller then rhs, only compare iface, uid, tag and set.
+bool operator<(const stats_line& lhs, const stats_line& rhs) {
+    int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
+    if (ret != 0) return ret < 0;
+    if (lhs.uid < rhs.uid) return true;
+    if (lhs.uid > rhs.uid) return false;
+    if (lhs.tag < rhs.tag) return true;
+    if (lhs.tag > rhs.tag) return false;
+    if (lhs.set < rhs.set) return true;
+    if (lhs.set > rhs.set) return false;
+    return false;
+}
+
+stats_line& stats_line::operator=(const stats_line& rhs) {
+    strlcpy(iface, rhs.iface, sizeof(iface));
+    uid = rhs.uid;
+    set = rhs.set;
+    tag = rhs.tag;
+    rxPackets = rhs.rxPackets;
+    txPackets = rhs.txPackets;
+    rxBytes = rhs.rxBytes;
+    txBytes = rhs.txBytes;
+    return *this;
+}
+
+stats_line& stats_line::operator+=(const stats_line& rhs) {
+    rxPackets += rhs.rxPackets;
+    txPackets += rhs.txPackets;
+    rxBytes += rhs.rxBytes;
+    txBytes += rhs.txBytes;
+    return *this;
+}
+
 }  // namespace bpf
 }  // namespace android
diff --git a/libbpf/BpfNetworkStatsTest.cpp b/libnetdbpf/BpfNetworkStatsTest.cpp
similarity index 65%
rename from libbpf/BpfNetworkStatsTest.cpp
rename to libnetdbpf/BpfNetworkStatsTest.cpp
index 8fd4943..8153127 100644
--- a/libbpf/BpfNetworkStatsTest.cpp
+++ b/libnetdbpf/BpfNetworkStatsTest.cpp
@@ -35,16 +35,9 @@
 
 #include <netdutils/MockSyscalls.h>
 #include "bpf/BpfMap.h"
-#include "bpf/BpfNetworkStats.h"
 #include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
 
-using namespace android::bpf;
-
-using ::testing::_;
-using ::testing::ByMove;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::StrictMock;
 using ::testing::Test;
 
 namespace android {
@@ -79,12 +72,14 @@
     BpfNetworkStatsHelperTest() {}
     BpfMap<uint64_t, UidTag> mFakeCookieTagMap;
     BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
-    BpfMap<StatsKey, StatsValue> mFakeUidStatsMap;
-    BpfMap<StatsKey, StatsValue> mFakeTagStatsMap;
+    BpfMap<StatsKey, StatsValue> mFakeStatsMap;
     BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
     BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
 
     void SetUp() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+        ASSERT_EQ(0, setrlimitForTest());
+
         mFakeCookieTagMap = BpfMap<uint64_t, UidTag>(createMap(
             BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(struct UidTag), TEST_MAP_SIZE, 0));
         ASSERT_LE(0, mFakeCookieTagMap.getMap());
@@ -93,15 +88,10 @@
             BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
         ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
 
-        mFakeUidStatsMap =
-            BpfMap<StatsKey, StatsValue>(createMap(BPF_MAP_TYPE_HASH, sizeof(struct StatsKey),
-                                                   sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeUidStatsMap.getMap());
-
-        mFakeTagStatsMap =
-            BpfMap<StatsKey, StatsValue>(createMap(BPF_MAP_TYPE_HASH, sizeof(struct StatsKey),
-                                                   sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeTagStatsMap.getMap());
+        mFakeStatsMap = BpfMap<StatsKey, StatsValue>(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(struct StatsKey), sizeof(struct StatsValue),
+                          TEST_MAP_SIZE, 0));
+        ASSERT_LE(0, mFakeStatsMap.getMap());
 
         mFakeIfaceIndexNameMap = BpfMap<uint32_t, IfaceValue>(
             createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(IfaceValue), TEST_MAP_SIZE, 0));
@@ -143,7 +133,7 @@
                               int counterSet, uint32_t tag, const stats_line& result) {
         EXPECT_EQ(0, strcmp(iface, result.iface));
         EXPECT_EQ(uid, (uint32_t)result.uid);
-        EXPECT_EQ(counterSet, result.set);
+        EXPECT_EQ((uint32_t) counterSet, result.set);
         EXPECT_EQ(tag, (uint32_t)result.tag);
         EXPECT_EQ(target.rxPackets, (uint64_t)result.rxPackets);
         EXPECT_EQ(target.rxBytes, (uint64_t)result.rxBytes);
@@ -207,6 +197,20 @@
     EXPECT_EQ(1 + 2 + 3 + 4 + 5, totalSum);
 }
 
+TEST_F(BpfNetworkStatsHelperTest, TestUidStatsNoTraffic) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    StatsValue value1 = {
+            .rxBytes = 0,
+            .rxPackets = 0,
+            .txBytes = 0,
+            .txPackets = 0,
+    };
+    Stats result1 = {};
+    ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap));
+    expectStatsEqual(value1, result1);
+}
+
 TEST_F(BpfNetworkStatsHelperTest, TestGetUidStatsTotal) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
@@ -234,15 +238,15 @@
     expectStatsEqual(value2, result2);
     std::vector<stats_line> lines;
     std::vector<std::string> ifaces;
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeUidStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)2, lines.size());
     lines.clear();
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME3, TEST_UID2, TEST_COUNTERSET1, 0, lines.front());
 }
@@ -301,28 +305,28 @@
                          .rxPackets = TEST_PACKET0,
                          .txBytes = TEST_BYTES1,
                          .txPackets = TEST_PACKET1,};
-    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeTagStatsMap);
-    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeTagStatsMap);
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value1,
-                      mFakeTagStatsMap);
-    populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeTagStatsMap);
+                      mFakeStatsMap);
+    populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
     std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
-                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
     lines.clear();
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)3, lines.size());
     lines.clear();
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
-                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)2, lines.size());
     lines.clear();
     ifaces.push_back(std::string(IFACE_NAME1));
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
-                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines.front());
 }
@@ -336,29 +340,29 @@
                          .rxPackets = TEST_PACKET0,
                          .txBytes = TEST_BYTES1,
                          .txPackets = TEST_PACKET1,};
-    populateFakeStats(0, 0, 0, OVERFLOW_COUNTERSET, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeUidStatsMap);
+    populateFakeStats(0, 0, 0, OVERFLOW_COUNTERSET, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
     std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
     lines.clear();
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)3, lines.size());
     lines.clear();
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines.front());
     lines.clear();
     ifaces.push_back(std::string(IFACE_NAME1));
     ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)2, lines.size());
 }
 
@@ -371,31 +375,31 @@
                          .txBytes = TEST_BYTES1 * 20,
                          .txPackets = TEST_PACKET1,};
     uint32_t ifaceIndex = UNKNOWN_IFACE;
-    populateFakeStats(TEST_UID1, 0, ifaceIndex, TEST_COUNTERSET0, value1, mFakeUidStatsMap);
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeUidStatsMap);
+    populateFakeStats(TEST_UID1, 0, ifaceIndex, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     StatsValue value2 = {.rxBytes = TEST_BYTES0 * 40,
                          .rxPackets = TEST_PACKET0,
                          .txBytes = TEST_BYTES1 * 40,
                          .txPackets = TEST_PACKET1,};
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeUidStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
     StatsKey curKey = {.uid = TEST_UID1,
                        .tag = 0,
                        .ifaceIndex = ifaceIndex,
                        .counterSet = TEST_COUNTERSET0};
     char ifname[IFNAMSIZ];
     int64_t unknownIfaceBytesTotal = 0;
-    ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeUidStatsMap, ifaceIndex,
+    ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
                                            ifname, curKey, &unknownIfaceBytesTotal));
     ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal);
     curKey.ifaceIndex = IFACE_INDEX2;
-    ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeUidStatsMap, ifaceIndex,
+    ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
                                            ifname, curKey, &unknownIfaceBytesTotal));
     ASSERT_EQ(-1, unknownIfaceBytesTotal);
     std::vector<stats_line> lines;
     std::vector<std::string> ifaces;
     // TODO: find a way to test the total of unknown Iface Bytes go above limit.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
-                                                    mFakeUidStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
 }
@@ -431,14 +435,152 @@
     ASSERT_EQ(0,
               parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
-    std::sort(lines.begin(), lines.end(), [](const auto& line1, const auto& line2)-> bool {
-        return strcmp(line1.iface, line2.iface) < 0;
-    });
+
     expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
     expectStatsLineEqual(value1, IFACE_NAME3, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
     expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[2]);
     ASSERT_EQ(0, strcmp(TRUNCATED_IFACE_NAME, lines[3].iface));
     expectStatsLineEqual(value2, TRUNCATED_IFACE_NAME, UID_ALL, SET_ALL, TAG_NONE, lines[3]);
 }
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    // Create iface indexes with duplicate iface name.
+    updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+    updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+    updateIfaceMap(IFACE_NAME1, IFACE_INDEX3);  // Duplicate!
+
+    StatsValue value1 = {
+            .rxBytes = TEST_BYTES0,
+            .rxPackets = TEST_PACKET0,
+            .txBytes = TEST_BYTES1,
+            .txPackets = TEST_PACKET1,
+    };
+    StatsValue value2 = {
+            .rxBytes = TEST_BYTES1,
+            .rxPackets = TEST_PACKET1,
+            .txBytes = TEST_BYTES0,
+            .txPackets = TEST_PACKET0,
+    };
+    StatsValue value3 = {
+            .rxBytes = TEST_BYTES0 * 2,
+            .rxPackets = TEST_PACKET0 * 2,
+            .txBytes = TEST_BYTES1 * 2,
+            .txPackets = TEST_PACKET1 * 2,
+    };
+
+    std::vector<stats_line> lines;
+    std::vector<std::string> ifaces;
+
+    // Test empty stats.
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 0, lines.size());
+    lines.clear();
+
+    // Test 1 line stats.
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 1, lines.size());
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+    lines.clear();
+
+    // These items should not be grouped.
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET1, value2, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
+                      mFakeStatsMap);
+    populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 5, lines.size());
+    lines.clear();
+
+    // These items should be grouped.
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 5, lines.size());
+
+    // Verify Sorted & Grouped.
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]);
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]);
+    expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]);
+    lines.clear();
+
+    // Perform test on IfaceStats.
+    uint32_t ifaceStatsKey = IFACE_INDEX2;
+    EXPECT_TRUE(isOk(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY)));
+    ifaceStatsKey = IFACE_INDEX1;
+    EXPECT_TRUE(isOk(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)));
+
+    // This should be grouped.
+    ifaceStatsKey = IFACE_INDEX3;
+    EXPECT_TRUE(isOk(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)));
+
+    ASSERT_EQ(0,
+              parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 2, lines.size());
+
+    expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+    expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+    lines.clear();
+}
+
+// Test to verify that subtract overflow will not be triggered by the compare function invoked from
+// sorting. See http:/b/119193941.
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+
+    StatsValue value1 = {
+            .rxBytes = TEST_BYTES0,
+            .rxPackets = TEST_PACKET0,
+            .txBytes = TEST_BYTES1,
+            .txPackets = TEST_PACKET1,
+    };
+
+    // Mutate uid, 0 < TEST_UID1 < INT_MAX < INT_MIN < UINT_MAX.
+    populateFakeStats(0, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(UINT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(INT_MIN, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(INT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+    // Mutate tag, 0 < TEST_TAG < INT_MAX < INT_MIN < UINT_MAX.
+    populateFakeStats(TEST_UID1, INT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, INT_MIN, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, UINT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+    // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
+
+    std::vector<stats_line> lines;
+    std::vector<std::string> ifaces;
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 8, lines.size());
+
+    // Uid 0 first
+    expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+
+    // Test uid, mutate tag.
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[1]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[2]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[3]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[4]);
+
+    // Mutate uid.
+    expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[5]);
+    expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[6]);
+    expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]);
+    lines.clear();
+}
 }  // namespace bpf
 }  // namespace android
diff --git a/libbpf/include/bpf/BpfNetworkStats.h b/libnetdbpf/include/netdbpf/BpfNetworkStats.h
similarity index 88%
rename from libbpf/include/bpf/BpfNetworkStats.h
rename to libnetdbpf/include/netdbpf/BpfNetworkStats.h
index 80dff88..503f090 100644
--- a/libbpf/include/bpf/BpfNetworkStats.h
+++ b/libnetdbpf/include/netdbpf/BpfNetworkStats.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#ifndef _BPF_NETWORKSTATS_H
+#define _BPF_NETWORKSTATS_H
+
 #include <bpf/BpfMap.h>
 
 namespace android {
@@ -31,19 +34,26 @@
 // The limit for stats received by a unknown interface;
 constexpr const int64_t MAX_UNKNOWN_IFACE_BYTES = 100 * 1000;
 
-// This is a JNI ABI and is used by
-// framework/base/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+// This is used by
+// frameworks/base/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
 // make sure it is consistent with the JNI code before changing this.
 struct stats_line {
     char iface[32];
-    int32_t uid;
-    int32_t set;
-    int32_t tag;
+    uint32_t uid;
+    uint32_t set;
+    uint32_t tag;
     int64_t rxBytes;
     int64_t rxPackets;
     int64_t txBytes;
     int64_t txPackets;
+
+    stats_line& operator=(const stats_line& rhs);
+    stats_line& operator+=(const stats_line& rhs);
 };
+
+bool operator==(const stats_line& lhs, const stats_line& rhs);
+bool operator<(const stats_line& lhs, const stats_line& rhs);
+
 // For test only
 int bpfGetUidStatsInternal(uid_t uid, struct Stats* stats,
                            const BpfMap<uint32_t, StatsValue>& appUidStatsMap);
@@ -107,6 +117,9 @@
                                int limitUid);
 
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>* lines);
 int cleanStatsMap();
 }  // namespace bpf
 }  // namespace android
+
+#endif  // _BPF_NETWORKSTATS_H
diff --git a/libnetdbpf/include/netdbpf/bpf_shared.h b/libnetdbpf/include/netdbpf/bpf_shared.h
new file mode 100644
index 0000000..66d20ec
--- /dev/null
+++ b/libnetdbpf/include/netdbpf/bpf_shared.h
@@ -0,0 +1,136 @@
+/*
+ * 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 NETDBPF_BPF_SHARED_H
+#define NETDBPF_BPF_SHARED_H
+
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <netdutils/UidConstants.h>
+// const values shared by bpf kernel program bpfloader and netd
+
+
+// Since we cannot garbage collect the stats map since device boot, we need to make these maps as
+// large as possible. The maximum size of number of map entries we can have is depend on the rlimit
+// of MEM_LOCK granted to netd. The memory space needed by each map can be calculated by the
+// following fomula:
+//      elem_size = 40 + roundup(key_size, 8) + roundup(value_size, 8)
+//      cost = roundup_pow_of_two(max_entries) * 16 + elem_size * max_entries +
+//              elem_size * number_of_CPU
+// And the cost of each map currently used is(assume the device have 8 CPUs):
+// cookie_tag_map:      key:  8 bytes, value:  8 bytes, cost:  822592 bytes    =   823Kbytes
+// uid_counter_set_map: key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
+// app_uid_stats_map:   key:  4 bytes, value: 32 bytes, cost: 1062784 bytes    =  1063Kbytes
+// uid_stats_map:       key: 16 bytes, value: 32 bytes, cost: 1142848 bytes    =  1143Kbytes
+// tag_stats_map:       key: 16 bytes, value: 32 bytes, cost: 1142848 bytes    =  1143Kbytes
+// iface_index_name_map:key:  4 bytes, value: 16 bytes, cost:   80896 bytes    =    81Kbytes
+// iface_stats_map:     key:  4 bytes, value: 32 bytes, cost:   97024 bytes    =    97Kbytes
+// dozable_uid_map:     key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
+// standby_uid_map:     key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
+// powersave_uid_map:   key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
+// total:                                                                         4930Kbytes
+// It takes maximum 4.9MB kernel memory space if all maps are full, which requires any devices
+// running this module to have a memlock rlimit to be larger then 5MB. In the old qtaguid module,
+// we don't have a total limit for data entries but only have limitation of tags each uid can have.
+// (default is 1024 in kernel);
+
+const int COOKIE_UID_MAP_SIZE = 10000;
+const int UID_COUNTERSET_MAP_SIZE = 2000;
+const int APP_STATS_MAP_SIZE = 10000;
+const int STATS_MAP_SIZE = 5000;
+const int IFACE_INDEX_NAME_MAP_SIZE = 1000;
+const int IFACE_STATS_MAP_SIZE = 1000;
+const int CONFIGURATION_MAP_SIZE = 2;
+const int UID_OWNER_MAP_SIZE = 2000;
+
+#define BPF_PATH "/sys/fs/bpf"
+
+#define BPF_EGRESS_PROG_PATH BPF_PATH "/prog_netd_cgroupskb_egress_stats"
+#define BPF_INGRESS_PROG_PATH BPF_PATH "/prog_netd_cgroupskb_ingress_stats"
+#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "/prog_netd_skfilter_ingress_xtbpf"
+#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "/prog_netd_skfilter_egress_xtbpf"
+#define XT_BPF_WHITELIST_PROG_PATH BPF_PATH "/prog_netd_skfilter_whitelist_xtbpf"
+#define XT_BPF_BLACKLIST_PROG_PATH BPF_PATH "/prog_netd_skfilter_blacklist_xtbpf"
+#define CGROUP_SOCKET_PROG_PATH BPF_PATH "/prog_netd_cgroupsock_inet_create"
+
+#define COOKIE_TAG_MAP_PATH BPF_PATH "/map_netd_cookie_tag_map"
+#define UID_COUNTERSET_MAP_PATH BPF_PATH "/map_netd_uid_counterset_map"
+#define APP_UID_STATS_MAP_PATH BPF_PATH "/map_netd_app_uid_stats_map"
+#define STATS_MAP_A_PATH BPF_PATH "/map_netd_stats_map_A"
+#define STATS_MAP_B_PATH BPF_PATH "/map_netd_stats_map_B"
+#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "/map_netd_iface_index_name_map"
+#define IFACE_STATS_MAP_PATH BPF_PATH "/map_netd_iface_stats_map"
+#define CONFIGURATION_MAP_PATH BPF_PATH "/map_netd_configuration_map"
+#define UID_OWNER_MAP_PATH BPF_PATH "/map_netd_uid_owner_map"
+#define UID_PERMISSION_MAP_PATH BPF_PATH "/map_netd_uid_permission_map"
+
+enum UidOwnerMatchType {
+    NO_MATCH = 0,
+    HAPPY_BOX_MATCH = (1 << 0),
+    PENALTY_BOX_MATCH = (1 << 1),
+    DOZABLE_MATCH = (1 << 2),
+    STANDBY_MATCH = (1 << 3),
+    POWERSAVE_MATCH = (1 << 4),
+    IIF_MATCH = (1 << 5),
+};
+
+enum BpfPemissionMatch {
+    BPF_PERMISSION_INTERNET = 1 << 2,
+    BPF_PERMISSION_UPDATE_DEVICE_STATS = 1 << 3,
+};
+// In production we use two identical stats maps to record per uid stats and
+// do swap and clean based on the configuration specified here. The statsMapType
+// value in configuration map specified which map is currently in use.
+enum StatsMapType {
+    SELECT_MAP_A,
+    SELECT_MAP_B,
+};
+
+// TODO: change the configuration object from an 8-bit bitmask to an object with clearer
+// semantics, like a struct.
+typedef uint8_t BpfConfig;
+const BpfConfig DEFAULT_CONFIG = 0;
+
+typedef struct {
+    // Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask above.
+    uint32_t iif;
+    // A bitmask of enum values in UidOwnerMatchType.
+    uint8_t rule;
+} UidOwnerValue;
+
+#define UID_RULES_CONFIGURATION_KEY 1
+#define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
+
+#define CLAT_INGRESS_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress_clat_rawip"
+#define CLAT_INGRESS_PROG_ETHER_NAME "prog_clatd_schedcls_ingress_clat_ether"
+
+#define CLAT_INGRESS_PROG_RAWIP_PATH BPF_PATH "/" CLAT_INGRESS_PROG_RAWIP_NAME
+#define CLAT_INGRESS_PROG_ETHER_PATH BPF_PATH "/" CLAT_INGRESS_PROG_ETHER_NAME
+
+#define CLAT_INGRESS_MAP_PATH BPF_PATH "/map_clatd_clat_ingress_map"
+
+typedef struct {
+    uint32_t iif;            // The input interface index
+    struct in6_addr pfx96;   // The source /96 nat64 prefix, bottom 32 bits must be 0
+    struct in6_addr local6;  // The full 128-bits of the destination IPv6 address
+} ClatIngressKey;
+
+typedef struct {
+    uint32_t oif;           // The output interface to redirect to (0 means don't redirect)
+    struct in_addr local4;  // The destination IPv4 address
+} ClatIngressValue;
+
+#endif  // NETDBPF_BPF_SHARED_H
diff --git a/libnetdutils/Android.bp b/libnetdutils/Android.bp
index 9dd6cfb..b3fcfc7 100644
--- a/libnetdutils/Android.bp
+++ b/libnetdutils/Android.bp
@@ -1,7 +1,10 @@
 cc_library {
     name: "libnetdutils",
     srcs: [
+        "DumpWriter.cpp",
         "Fd.cpp",
+        "InternetAddresses.cpp",
+        "Log.cpp",
         "Netfilter.cpp",
         "Netlink.cpp",
         "Slice.cpp",
@@ -12,10 +15,10 @@
         "UniqueFd.cpp",
         "UniqueFile.cpp",
     ],
+    defaults: ["netd_defaults"],
     cflags: ["-Wall", "-Werror"],
     shared_libs: [
         "libbase",
-        "libbinder",
         "liblog",
     ],
     export_shared_lib_headers: [
@@ -29,20 +32,22 @@
     srcs: [
         "BackoffSequenceTest.cpp",
         "FdTest.cpp",
+        "InternetAddressesTest.cpp",
+        "LogTest.cpp",
         "MemBlockTest.cpp",
         "OperationLimiterTest.cpp",
         "SliceTest.cpp",
         "StatusTest.cpp",
         "SyscallsTest.cpp",
+        "ThreadUtilTest.cpp",
     ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-error=unused-variable",
+    defaults: ["netd_defaults"],
+    test_suites: ["device-tests"],
+    static_libs: [
+        "libgmock",
+        "libnetdutils",
     ],
-    static_libs: ["libgmock"],
     shared_libs: [
         "libbase",
-        "libnetdutils",
     ],
 }
diff --git a/libnetdutils/AndroidTest.xml b/libnetdutils/AndroidTest.xml
new file mode 100644
index 0000000..dc13361
--- /dev/null
+++ b/libnetdutils/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for netdutils_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="netdutils_test->/data/local/tmp/netdutils_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="netdutils_test" />
+    </test>
+</configuration>
diff --git a/server/DumpWriter.cpp b/libnetdutils/DumpWriter.cpp
similarity index 74%
rename from server/DumpWriter.cpp
rename to libnetdutils/DumpWriter.cpp
index 9a7e352..092ddba 100644
--- a/server/DumpWriter.cpp
+++ b/libnetdutils/DumpWriter.cpp
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 
-#include "DumpWriter.h"
+#include "netdutils/DumpWriter.h"
+
+#include <unistd.h>
+#include <limits>
 
 #include <android-base/stringprintf.h>
 #include <utils/String8.h>
 
 using android::base::StringAppendV;
-using android::String16;
-using android::Vector;
 
 namespace android {
-namespace net {
+namespace netdutils {
 
 namespace {
 
@@ -33,17 +34,16 @@
 
 }  // namespace
 
-
 DumpWriter::DumpWriter(int fd) : mIndentLevel(0), mFd(fd) {}
 
 void DumpWriter::incIndent() {
-    if (mIndentLevel < 255) {
+    if (mIndentLevel < std::numeric_limits<decltype(mIndentLevel)>::max()) {
         mIndentLevel++;
     }
 }
 
 void DumpWriter::decIndent() {
-    if (mIndentLevel > 0) {
+    if (mIndentLevel > std::numeric_limits<decltype(mIndentLevel)>::min()) {
         mIndentLevel--;
     }
 }
@@ -51,13 +51,14 @@
 void DumpWriter::println(const std::string& line) {
     if (!line.empty()) {
         for (int i = 0; i < mIndentLevel; i++) {
-            write(mFd, kIndentString, kIndentStringLen);
+            ::write(mFd, kIndentString, kIndentStringLen);
         }
-        write(mFd, line.c_str(), line.size());
+        ::write(mFd, line.c_str(), line.size());
     }
-    write(mFd, "\n", 1);
+    ::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;
@@ -67,5 +68,5 @@
     println(line);
 }
 
-}  // namespace net
+}  // namespace netdutils
 }  // namespace android
diff --git a/libnetdutils/FdTest.cpp b/libnetdutils/FdTest.cpp
index 2deddd2..889c1b7 100644
--- a/libnetdutils/FdTest.cpp
+++ b/libnetdutils/FdTest.cpp
@@ -41,10 +41,10 @@
     // Expect the following lines to compile
     Fd fd1(1);
     Fd fd2(fd1);
-    Fd fd3 = fd1;
+    Fd fd3 = fd2;
     const Fd fd4(8);
     const Fd fd5(fd4);
-    const Fd fd6 = fd4;
+    const Fd fd6 = fd5;
     EXPECT_TRUE(isWellFormed(fd3));
     EXPECT_TRUE(isWellFormed(fd6));
 
diff --git a/libnetdutils/InternetAddresses.cpp b/libnetdutils/InternetAddresses.cpp
new file mode 100644
index 0000000..944ed91
--- /dev/null
+++ b/libnetdutils/InternetAddresses.cpp
@@ -0,0 +1,142 @@
+/*
+ * 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 <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);
+    // TODO: move ScopedAddrinfo into libnetdutils and use it here.
+    if (ret != 0) {
+        freeaddrinfo(res);
+        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;
+    }
+
+    freeaddrinfo(res);
+    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;
+}
+
+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/libnetdutils/InternetAddressesTest.cpp b/libnetdutils/InternetAddressesTest.cpp
new file mode 100644
index 0000000..2129ca7
--- /dev/null
+++ b/libnetdutils/InternetAddressesTest.cpp
@@ -0,0 +1,504 @@
+/*
+ * 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, 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/libnetdutils/Log.cpp b/libnetdutils/Log.cpp
new file mode 100644
index 0000000..d2ce98f
--- /dev/null
+++ b/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/libnetdutils/LogTest.cpp b/libnetdutils/LogTest.cpp
new file mode 100644
index 0000000..1270560
--- /dev/null
+++ b/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/libnetdutils/Status.cpp b/libnetdutils/Status.cpp
index 859a08d..acd8f11 100644
--- a/libnetdutils/Status.cpp
+++ b/libnetdutils/Status.cpp
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-#include <sstream>
 #include "netdutils/Status.h"
+
+#include <sstream>
+
 #include "android-base/stringprintf.h"
 
 namespace android {
 namespace netdutils {
 
-void expectOk(const Status&) {
-    // TODO: put something here, for now this function serves solely as documentation.
-}
-
 Status statusFromErrno(int err, const std::string& msg) {
     return Status(err, base::StringPrintf("[%s] : %s", strerror(err), msg.c_str()));
 }
@@ -33,13 +31,6 @@
     return status.code() == err;
 }
 
-binder::Status asBinderStatus(const netdutils::Status& status) {
-    if (isOk(status)) {
-        return binder::Status::ok();
-    }
-    return binder::Status::fromServiceSpecificError(status.code(), status.msg().c_str());
-}
-
 std::string toString(const Status& status) {
     std::stringstream ss;
     ss << status;
diff --git a/libnetdutils/StatusTest.cpp b/libnetdutils/StatusTest.cpp
index 242cb9f..4cfc3bb 100644
--- a/libnetdutils/StatusTest.cpp
+++ b/libnetdutils/StatusTest.cpp
@@ -14,27 +14,74 @@
  * limitations under the License.
  */
 
-#include <cstdint>
-
-#include <gtest/gtest.h>
-
 #include "netdutils/Status.h"
 #include "netdutils/StatusOr.h"
 
+#include <sstream>
+
+#include <gtest/gtest.h>
+
 namespace android {
 namespace netdutils {
+namespace {
 
-TEST(StatusTest, smoke) {
-    // Expect the following lines to compile
-    Status status1(1);
-    Status status2(status1);
-    Status status3 = status1;
-    const Status status4(8);
-    const Status status5(status4);
-    const Status status6 = status4;
-
+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) {
@@ -42,15 +89,17 @@
       StatusOr<int> so(11);
       std::stringstream ss;
       ss << so;
-      EXPECT_EQ("StatusOr[status: Status[code: 0, msg: ], value: 11]", ss.str());
+      // 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());
+      EXPECT_EQ("StatusOr[status: Status[code: 2147483647, msg: \"undefined\"]]", ss.str());
     }
 }
 
+}  // namespace
 }  // namespace netdutils
 }  // namespace android
diff --git a/libnetdutils/Syscalls.cpp b/libnetdutils/Syscalls.cpp
index 9a05e3b..9f653f7 100644
--- a/libnetdutils/Syscalls.cpp
+++ b/libnetdutils/Syscalls.cpp
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
+#include "netdutils/Syscalls.h"
+
 #include <atomic>
 #include <type_traits>
 #include <utility>
 
-#include "netdutils/Syscalls.h"
-
 namespace android {
 namespace netdutils {
 namespace {
@@ -99,6 +99,14 @@
         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)) {
@@ -181,7 +189,7 @@
 
     StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const override {
         UniqueFile file(::fopen(path.c_str(), mode.c_str()));
-        if (file == NULL) {
+        if (file == nullptr) {
             return statusFromErrno(errno, "fopen(\"" + path + "\", \"" + mode + "\") failed");
         }
         return file;
diff --git a/libnetdutils/SyscallsTest.cpp b/libnetdutils/SyscallsTest.cpp
index da93b76..78ffab5 100644
--- a/libnetdutils/SyscallsTest.cpp
+++ b/libnetdutils/SyscallsTest.cpp
@@ -30,13 +30,11 @@
 #include "netdutils/StatusOr.h"
 #include "netdutils/Syscalls.h"
 
+using testing::_;
 using testing::ByMove;
-using testing::DoAll;
 using testing::Invoke;
-using testing::Mock;
 using testing::Return;
 using testing::StrictMock;
-using testing::_;
 
 namespace android {
 namespace netdutils {
@@ -62,6 +60,7 @@
     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());
@@ -187,11 +186,12 @@
 
     // Success
     EXPECT_CALL(mSyscalls, recvfrom(kFd, dst, kFlags, _, _))
-        .WillOnce(Invoke([expected, used](Fd, const Slice, int, sockaddr* src, socklen_t* srclen) {
-            memcpy(src, &expected, sizeof(src));
-            *srclen = sizeof(expected);
-            return used;
-        }));
+            .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);
diff --git a/libnetdutils/ThreadUtilTest.cpp b/libnetdutils/ThreadUtilTest.cpp
new file mode 100644
index 0000000..2fe63b7
--- /dev/null
+++ b/libnetdutils/ThreadUtilTest.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "netdutils/ThreadUtil.h"
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+class NoopRun {
+  public:
+    NoopRun() { instanceNum++; }
+    ~NoopRun() { instanceNum--; }
+
+    void run() {}
+
+    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;
+};
+
+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);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/UniqueFile.cpp b/libnetdutils/UniqueFile.cpp
index eb32abd..21e8779 100644
--- a/libnetdutils/UniqueFile.cpp
+++ b/libnetdutils/UniqueFile.cpp
@@ -22,9 +22,9 @@
 namespace android {
 namespace netdutils {
 
-void UniqueFileDtor::operator()(FILE* file) {
+void UniqueFileDtor::operator()(FILE* file) const {
     const auto& sys = sSyscalls.get();
-    sys.fclose(file);
+    sys.fclose(file).ignoreError();
 }
 
 }  // namespace netdutils
diff --git a/libnetdutils/include/netdutils/DumpWriter.h b/libnetdutils/include/netdutils/DumpWriter.h
new file mode 100644
index 0000000..a50b5e6
--- /dev/null
+++ b/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/libnetdutils/include/netdutils/InternetAddresses.h b/libnetdutils/include/netdutils/InternetAddresses.h
new file mode 100644
index 0000000..07a6e90
--- /dev/null
+++ b/libnetdutils/include/netdutils/InternetAddresses.h
@@ -0,0 +1,295 @@
+/*
+ * 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_INTERNETADDRESSES_H_
+#define NETDUTILS_INTERNETADDRESSES_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_
+
+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:
+    // TODO: "static forString(...)" using NetdConstants' parsePrefix().
+
+    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
+
+    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
+
+#endif  // NETDUTILS_INTERNETADDRESSES_H_
diff --git a/libnetdutils/include/netdutils/Log.h b/libnetdutils/include/netdutils/Log.h
new file mode 100644
index 0000000..77ae649
--- /dev/null
+++ b/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/libnetdutils/include/netdutils/MemBlock.h b/libnetdutils/include/netdutils/MemBlock.h
index 1b6bd7d..fd4d612 100644
--- a/libnetdutils/include/netdutils/MemBlock.h
+++ b/libnetdutils/include/netdutils/MemBlock.h
@@ -32,11 +32,11 @@
 class MemBlock {
   public:
     MemBlock() : MemBlock(0U) {}
-    MemBlock(size_t len)
+    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.
-    MemBlock(Slice src) : MemBlock(src.size()) {
+    explicit MemBlock(Slice src) : MemBlock(src.size()) {
         copy(get(), src);
     }
 
@@ -53,7 +53,8 @@
     Slice get() const noexcept { return Slice(mData.get(), mLen); }
 
     // Implicit cast to Slice.
-    const operator Slice() const noexcept { return get(); }
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    operator const Slice() const noexcept { return get(); }
 
   private:
     std::unique_ptr<uint8_t[]> mData;
diff --git a/libnetdutils/include/netdutils/Misc.h b/libnetdutils/include/netdutils/Misc.h
index 41f5778..d344f81 100644
--- a/libnetdutils/include/netdutils/Misc.h
+++ b/libnetdutils/include/netdutils/Misc.h
@@ -37,12 +37,13 @@
 class Cleanup {
   public:
     Cleanup() = delete;
-    Cleanup(FnT fn) : mFn(fn) {}
-    ~Cleanup() { mFn(); }
+    explicit Cleanup(FnT fn) : mFn(fn) {}
+    ~Cleanup() { if (!mReleased) mFn(); }
 
-    void release() { mFn = {}; }
+    void release() { mReleased = true; }
 
   private:
+    bool mReleased{false};
     FnT mFn;
 };
 
diff --git a/libnetdutils/include/netdutils/MockSyscalls.h b/libnetdutils/include/netdutils/MockSyscalls.h
index 06ca859..f57b55c 100644
--- a/libnetdutils/include/netdutils/MockSyscalls.h
+++ b/libnetdutils/include/netdutils/MockSyscalls.h
@@ -44,6 +44,7 @@
 
     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));
diff --git a/libnetdutils/include/netdutils/NetworkConstants.h b/libnetdutils/include/netdutils/NetworkConstants.h
new file mode 100644
index 0000000..682ceaf
--- /dev/null
+++ b/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.
+ */
+
+#ifndef NETDUTILS_NETWORKCONSTANTS_H_
+#define NETDUTILS_NETWORKCONSTANTS_H_
+
+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;
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_NETWORKCONSTANTS_H_
diff --git a/libnetdutils/include/netdutils/OperationLimiter.h b/libnetdutils/include/netdutils/OperationLimiter.h
index 633536b..992a849 100644
--- a/libnetdutils/include/netdutils/OperationLimiter.h
+++ b/libnetdutils/include/netdutils/OperationLimiter.h
@@ -60,7 +60,7 @@
     // Note: each successful start(key) must be matched by exactly one call to
     // finish(key).
     bool start(KeyType key) EXCLUDES(mMutex) {
-        std::lock_guard<std::mutex> lock(mMutex);
+        std::lock_guard lock(mMutex);
         auto& cnt = mCounters[key];  // operator[] creates new entries as needed.
         if (cnt >= mLimitPerKey) {
             // Oh, no!
@@ -73,7 +73,7 @@
     // Decrements the number of operations in progress accounted to |key|.
     // See usage notes on start().
     void finish(KeyType key) EXCLUDES(mMutex) {
-        std::lock_guard<std::mutex> lock(mMutex);
+        std::lock_guard lock(mMutex);
         auto it = mCounters.find(key);
         if (it == mCounters.end()) {
             LOG(FATAL_WITHOUT_ABORT) << "Decremented non-existent counter for key=" << key;
diff --git a/libnetdutils/include/netdutils/ResponseCode.h b/libnetdutils/include/netdutils/ResponseCode.h
new file mode 100644
index 0000000..c170684
--- /dev/null
+++ b/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/libnetdutils/include/netdutils/Slice.h b/libnetdutils/include/netdutils/Slice.h
index f194514..717fbd1 100644
--- a/libnetdutils/include/netdutils/Slice.h
+++ b/libnetdutils/include/netdutils/Slice.h
@@ -135,7 +135,7 @@
 
 // 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);
+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());
diff --git a/libnetdutils/include/netdutils/Status.h b/libnetdutils/include/netdutils/Status.h
index 6104a38..503eea3 100644
--- a/libnetdutils/include/netdutils/Status.h
+++ b/libnetdutils/include/netdutils/Status.h
@@ -17,8 +17,8 @@
 #ifndef NETUTILS_STATUS_H
 #define NETUTILS_STATUS_H
 
-#include "binder/Status.h"
 #include <cassert>
+#include <limits>
 #include <ostream>
 
 namespace android {
@@ -28,13 +28,16 @@
 // or moderate performance code. This can definitely be improved but
 // for now short string optimization is expected to keep the common
 // success case fast.
-class Status {
+//
+// 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) {}
 
-    Status(int code, const std::string& msg) : mCode(code), mMsg(msg) { assert(!ok()); }
+    // Constructs an error Status, |code| must be non-zero.
+    Status(int code, std::string msg) : mCode(code), mMsg(std::move(msg)) { assert(!ok()); }
 
     int code() const { return mCode; }
 
@@ -42,6 +45,9 @@
 
     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); }
 
@@ -67,10 +73,14 @@
     return status.ok();
 }
 
-// Document that status is expected to be ok. This function may log
-// (or assert when running in debug mode) if status has an unexpected
-// value.
-void expectOk(const Status& status);
+// For use only in tests.
+#define EXPECT_OK(status) EXPECT_TRUE((status).ok())
+
+// 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
@@ -81,9 +91,6 @@
 // value in the errno space.
 bool equalToErrno(const Status& status, int err);
 
-// Converts netdutils Status into binder Status.
-binder::Status asBinderStatus(const netdutils::Status& status);
-
 // Helper that converts Status-like object (notably StatusOr) to a
 // message.
 std::string toString(const Status& status);
diff --git a/libnetdutils/include/netdutils/StatusOr.h b/libnetdutils/include/netdutils/StatusOr.h
index d68cced..c7aa4e4 100644
--- a/libnetdutils/include/netdutils/StatusOr.h
+++ b/libnetdutils/include/netdutils/StatusOr.h
@@ -26,36 +26,56 @@
 // Wrapper around a combination of Status and application value type.
 // T may be any copyable or movable type.
 template <typename T>
-class StatusOr {
+class [[nodiscard]] StatusOr {
   public:
-    StatusOr() = default;
-    StatusOr(const Status status) : mStatus(status) { assert(!isOk(status)); }
+    // 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&&) = default;
+    StatusOr(StatusOr&&) noexcept = default;
     // Move assignment ok (if T supports move)
-    StatusOr& operator=(StatusOr&&) = default;
+    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;
 
-    // Return const references to wrapped type
+    // 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; }
 
-    // Return rvalue references to wrapped type
+    // 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; }
 
-    // Return status assigned in constructor
+    // Returns the Status object assigned at construction time.
     const Status status() const { return mStatus; }
 
-    // Implict cast to Status
+    // 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:
diff --git a/libnetdutils/include/netdutils/Stopwatch.h b/libnetdutils/include/netdutils/Stopwatch.h
new file mode 100644
index 0000000..18e2050
--- /dev/null
+++ b/libnetdutils/include/netdutils/Stopwatch.h
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+    float timeTaken() const { return getElapsed(clock::now()); }
+
+    int64_t timeTakenUs() const { return getElapsedUs(clock::now()); }
+
+    float getTimeAndReset() {
+        const auto& now = clock::now();
+        float elapsed = getElapsed(now);
+        mStart = now;
+        return elapsed;
+    }
+    float getTimeAndResetUs() {
+        const auto& now = clock::now();
+        float elapsed = getElapsedUs(now);
+        mStart = now;
+        return elapsed;
+    }
+
+  private:
+    time_point mStart;
+
+    float getElapsed(const time_point& now) const {
+        using ms = std::chrono::duration<float, std::ratio<1, 1000>>;
+        return (std::chrono::duration_cast<ms>(now - mStart)).count();
+    }
+    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/libnetdutils/include/netdutils/Syscalls.h b/libnetdutils/include/netdutils/Syscalls.h
index 4c9a004..36fcd85 100644
--- a/libnetdutils/include/netdutils/Syscalls.h
+++ b/libnetdutils/include/netdutils/Syscalls.h
@@ -19,13 +19,15 @@
 
 #include <memory>
 
+#include <net/if.h>
 #include <poll.h>
-#include <unistd.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"
@@ -57,6 +59,8 @@
 
     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;
diff --git a/server/thread_util.h b/libnetdutils/include/netdutils/ThreadUtil.h
similarity index 72%
rename from server/thread_util.h
rename to libnetdutils/include/netdutils/ThreadUtil.h
index f32aacd..2ef97ef 100644
--- a/server/thread_util.h
+++ b/libnetdutils/include/netdutils/ThreadUtil.h
@@ -14,53 +14,57 @@
  * limitations under the License.
  */
 
-#ifndef NETD_SERVER_THREAD_UTIL_H
-#define NETD_SERVER_THREAD_UTIL_H
+#ifndef NETDUTILS_THREADUTIL_H
+#define NETDUTILS_THREADUTIL_H
 
 #include <pthread.h>
 #include <memory>
 
+#include <android-base/logging.h>
+
 namespace android {
-namespace net {
+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);
-    }
+    int detach() { return -pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); }
 
     pthread_attr_t attr;
 };
 
-template<typename T>
+template <typename T>
 inline void* runAndDelete(void* obj) {
     std::unique_ptr<T> handler(reinterpret_cast<T*>(obj));
     handler->run();
     return nullptr;
 }
 
-template<typename T>
+template <typename T>
 inline int threadLaunch(T* obj) {
-    if (obj == nullptr) { return -EINVAL;}
+    if (obj == nullptr) {
+        return -EINVAL;
+    }
 
     scoped_pthread_attr scoped_attr;
 
     int rval = scoped_attr.detach();
-    if (rval != 0) { return rval; }
+    if (rval != 0) {
+        return rval;
+    }
 
     pthread_t thread;
     rval = pthread_create(&thread, &scoped_attr.attr, &runAndDelete<T>, obj);
     if (rval != 0) {
-        ALOGW("pthread_create failed: %d", rval);
+        LOG(WARNING) << __func__ << ": pthread_create failed: " << rval;
         return -rval;
     }
 
     return rval;
 }
 
-}  // namespace net
+}  // namespace netdutils
 }  // namespace android
 
-#endif  // NETD_SERVER_THREAD_UTIL_H
+#endif  // NETDUTILS_THREADUTIL_H
diff --git a/libnetdutils/include/netdutils/UidConstants.h b/libnetdutils/include/netdutils/UidConstants.h
new file mode 100644
index 0000000..42c1090
--- /dev/null
+++ b/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/libnetdutils/include/netdutils/UniqueFd.h b/libnetdutils/include/netdutils/UniqueFd.h
index 3b63f34..61101f9 100644
--- a/libnetdutils/include/netdutils/UniqueFd.h
+++ b/libnetdutils/include/netdutils/UniqueFd.h
@@ -57,7 +57,7 @@
     void reset(Fd fd = Fd());
 
     // Implict cast to Fd
-    const operator Fd() const { return mFd; }
+    operator const Fd &() const { return mFd; }
 
   private:
     Fd mFd;
diff --git a/libnetdutils/include/netdutils/UniqueFile.h b/libnetdutils/include/netdutils/UniqueFile.h
index 53552d2..6dd6d67 100644
--- a/libnetdutils/include/netdutils/UniqueFile.h
+++ b/libnetdutils/include/netdutils/UniqueFile.h
@@ -20,13 +20,11 @@
 #include <stdio.h>
 #include <memory>
 
-#include "netdutils/Fd.h"
-
 namespace android {
 namespace netdutils {
 
 struct UniqueFileDtor {
-    void operator()(FILE* file);
+    void operator()(FILE* file) const;
 };
 
 using UniqueFile = std::unique_ptr<FILE, UniqueFileDtor>;
diff --git a/netutils_wrappers/Android.bp b/netutils_wrappers/Android.bp
new file mode 100644
index 0000000..5074071
--- /dev/null
+++ b/netutils_wrappers/Android.bp
@@ -0,0 +1,37 @@
+cc_binary {
+    name: "netutils-wrapper-1.0",
+    defaults: ["netd_defaults"],
+    srcs: [
+        "NetUtilsWrapper-1.0.cpp",
+        "main.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    symlinks: [
+        "iptables-wrapper-1.0",
+        "ip6tables-wrapper-1.0",
+        "ndc-wrapper-1.0",
+        "tc-wrapper-1.0",
+        "ip-wrapper-1.0",
+    ],
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wextra",
+    ],
+}
+
+cc_test {
+    name: "netutils_wrapper_test",
+    defaults: ["netd_defaults"],
+    srcs: [
+        "NetUtilsWrapper-1.0.cpp",
+        "NetUtilsWrapperTest-1.0.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+}
diff --git a/netutils_wrappers/Android.mk b/netutils_wrappers/Android.mk
deleted file mode 100644
index ed1af34..0000000
--- a/netutils_wrappers/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# 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.
-
-LOCAL_PATH := $(call my-dir)
-
-###
-### Wrapper binary.
-###
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CLANG := true
-LOCAL_MODULE := netutils-wrapper-1.0
-LOCAL_SHARED_LIBRARIES := libbase liblog
-LOCAL_SRC_FILES := NetUtilsWrapper-1.0.cpp main.cpp
-LOCAL_MODULE_SYMLINKS := \
-    iptables-wrapper-1.0 \
-    ip6tables-wrapper-1.0 \
-    ndc-wrapper-1.0 \
-    tc-wrapper-1.0 \
-    ip-wrapper-1.0
-
-include $(BUILD_EXECUTABLE)
-
-###
-### Wrapper unit tests.
-###
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CLANG := true
-LOCAL_MODULE := netutils_wrapper_test
-LOCAL_SHARED_LIBRARIES := libbase liblog
-LOCAL_SRC_FILES := NetUtilsWrapper-1.0.cpp NetUtilsWrapperTest-1.0.cpp
-
-include $(BUILD_NATIVE_TEST)
diff --git a/netutils_wrappers/NetUtilsWrapper-1.0.cpp b/netutils_wrappers/NetUtilsWrapper-1.0.cpp
index ccf32ad..cdc454e 100644
--- a/netutils_wrappers/NetUtilsWrapper-1.0.cpp
+++ b/netutils_wrappers/NetUtilsWrapper-1.0.cpp
@@ -26,7 +26,7 @@
 #include <android-base/strings.h>
 
 #define LOG_TAG "NetUtilsWrapper"
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include "NetUtilsWrapper.h"
 
@@ -34,7 +34,8 @@
 
 #define OEM_IFACE "[^ ]*oem[0-9]+"
 #define RMNET_IFACE "(r_)?rmnet_(data)?[0-9]+"
-#define VENDOR_IFACE "(" OEM_IFACE "|" RMNET_IFACE ")"
+#define CCMNI_IFACE "cc(3)?mni[0-9]+"
+#define VENDOR_IFACE "(" OEM_IFACE "|" RMNET_IFACE "|" CCMNI_IFACE ")"
 #define VENDOR_CHAIN "(oem_.*|nm_.*|qcom_.*)"
 
 // List of net utils wrapped by this program
@@ -45,7 +46,7 @@
     "ndc",
     "tc",
     "ip",
-    NULL,
+    nullptr,
 };
 
 // List of regular expressions of expected commands.
@@ -105,7 +106,7 @@
 // This is the only gateway for vendor programs to reach net utils.
 int doMain(int argc, char **argv) {
     char *progname = argv[0];
-    char *basename = NULL;
+    char *basename = nullptr;
 
     basename = strrchr(progname, '/');
     basename = basename ? basename + 1 : progname;
diff --git a/resolv/Android.bp b/resolv/Android.bp
new file mode 100644
index 0000000..06f7f85
--- /dev/null
+++ b/resolv/Android.bp
@@ -0,0 +1,220 @@
+cc_library_headers {
+    name: "libnetd_resolv_headers",
+    export_include_dirs: ["include"],
+}
+
+aidl_interface {
+    name: "dnsresolver_aidl_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/IDnsResolver.aidl",
+        "binder/android/net/ResolverParamsParcel.aidl",
+    ],
+    imports: [
+        "netd_event_listener_interface",
+    ],
+    backend: {
+        ndk: {
+            gen_log: true,
+        },
+    },
+    api_dir: "aidl/dnsresolver",
+    versions: [
+        "1",
+        "2",
+    ],
+}
+
+cc_test_library {
+    name: "libnetd_test_metrics_listener",
+    defaults: ["netd_defaults"],
+    srcs: [
+        "tests/BaseTestMetricsListener.cpp",
+        "tests/TestMetrics.cpp",
+    ],
+    include_dirs: [
+        "system/netd/include",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+        "netd_event_listener_interface-V1-cpp",
+    ],
+}
+
+cc_library {
+    name: "libnetd_resolv",
+    version_script: "libnetd_resolv.map.txt",
+    defaults: ["netd_defaults"],
+    srcs: [
+        "getaddrinfo.cpp",
+        "gethnamaddr.cpp",
+        "sethostent.cpp",
+        "res_cache.cpp",
+        "res_comp.cpp",
+        "res_debug.cpp",
+        "res_init.cpp",
+        "res_mkquery.cpp",
+        "res_query.cpp",
+        "res_send.cpp",
+        "res_state.cpp",
+        "res_stats.cpp",
+        "Dns64Configuration.cpp",
+        "DnsProxyListener.cpp",
+        "DnsResolver.cpp",
+        "DnsResolverService.cpp",
+        "DnsTlsDispatcher.cpp",
+        "DnsTlsQueryMap.cpp",
+        "DnsTlsTransport.cpp",
+        "DnsTlsServer.cpp",
+        "DnsTlsSessionCache.cpp",
+        "DnsTlsSocket.cpp",
+        "PrivateDnsConfiguration.cpp",
+        "ResolverController.cpp",
+        "ResolverEventReporter.cpp",
+    ],
+    // Link everything statically (except for libc) to minimize our dependence
+    // on system ABIs
+    stl: "libc++_static",
+    static_libs: [
+        "libbase",
+        "libcrypto",
+        "libcutils",
+        "libjsoncpp",
+        "liblog",
+        "libnetdutils",
+        "libssl",
+        "libstatslog_resolv",
+        "libstatssocket",
+        "libsysutils",
+        "libutils",
+        "netd_event_listener_interface-ndk_platform",
+        "dnsresolver_aidl_interface-ndk_platform",
+        "server_configurable_flags",
+        "stats_proto",
+        "libprotobuf-cpp-lite",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
+    include_dirs: [
+        "system/netd/include",
+        "system/netd/server",
+    ],
+    export_include_dirs: ["include"],
+    // TODO: pie in the sky: make this code clang-tidy clean
+    tidy: false,
+    product_variables: {
+        debuggable: {
+            cppflags: [
+                "-DRESOLV_ALLOW_VERBOSE_LOGGING=1",
+            ],
+        },
+    },
+}
+
+cc_library_static {
+    name: "stats_proto",
+    defaults: ["netd_defaults"],
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+    },
+    srcs: [
+        "stats.proto",
+    ],
+}
+
+genrule {
+    name: "statslog_resolv.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_resolv.h --module resolv --namespace android,net,stats",
+    out: [
+        "statslog_resolv.h",
+    ],
+}
+
+genrule {
+    name: "statslog_resolv.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_resolv.cpp --module resolv --namespace android,net,stats --importHeader statslog_resolv.h",
+    out: [
+        "statslog_resolv.cpp",
+    ],
+}
+
+cc_library_static {
+    name: "libstatslog_resolv",
+    generated_sources: ["statslog_resolv.cpp"],
+    generated_headers: ["statslog_resolv.h"],
+    defaults: ["netd_defaults"],
+    export_generated_headers: ["statslog_resolv.h"],
+    static_libs: [
+        "libcutils",
+        "liblog",
+        "libstatssocket",
+        "libutils",
+    ],
+}
+
+cc_test {
+    name: "resolv_integration_test",
+    test_suites: ["device-tests"],
+    defaults: ["netd_defaults"],
+    srcs: [
+        "dns_responder/dns_responder.cpp",
+        "resolver_test.cpp",
+        "dnsresolver_binder_test.cpp",
+    ],
+    include_dirs: [
+        "system/netd/resolv/include",
+        "system/netd/server",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libcrypto",
+        "liblog",
+        "libnetd_client",
+        "libssl",
+        "libutils",
+    ],
+    static_libs: [
+        "libgmock",
+        "libnetd_test_dnsresponder",
+        "libnetd_test_metrics_listener",
+        "libnetd_test_tun_interface",
+        "liblogwrap",
+        "libnetdutils",
+        "netd_aidl_interface-V2-cpp",
+        "netd_event_listener_interface-V1-cpp",
+        "dnsresolver_aidl_interface-V2-cpp",
+    ],
+    compile_multilib: "both",
+    sanitize: {
+        address: true,
+        recover: ["all"],
+    },
+}
+
+cc_test {
+    name: "resolv_unit_test",
+    test_suites: ["device-tests"],
+    defaults: ["netd_defaults"],
+    srcs: [
+        "dns_tls_test.cpp",
+        "libnetd_resolv_test.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+        "liblog",
+        "libssl",
+    ],
+    static_libs: [
+        "libnetd_resolv",
+        "libnetd_test_dnsresponder",
+        "libnetdutils",
+        "server_configurable_flags",
+    ],
+}
diff --git a/resolv/AndroidTest.xml b/resolv/AndroidTest.xml
new file mode 100644
index 0000000..f5715c9
--- /dev/null
+++ b/resolv/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Config for resolv_integration_test.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="resolv_integration_test->/data/local/tmp/resolv_integration_test" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="resolv_integration_test" />
+        <!--
+            On 2018-12-12, GetAddrInfoStressTest_Binder_100 suddenly jumped
+            from ~1xs to ~70s runtime in APCT continuous integration, causing
+            resolv_integration_test to flake with the default 60s timeout.
+            We're not sure what caused the regression, but it's not due to a change
+            in the Android image and unlikely to affect users.
+            Just bump the timeout to 120s for now.
+        -->
+        <option name="native-test-timeout" value="120000" />
+    </test>
+</configuration>
diff --git a/resolv/Dns64Configuration.cpp b/resolv/Dns64Configuration.cpp
new file mode 100644
index 0000000..6cd9c2e
--- /dev/null
+++ b/resolv/Dns64Configuration.cpp
@@ -0,0 +1,233 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Dns64Configuration"
+#define DBG 0
+
+#include "Dns64Configuration.h"
+
+#include <log/log.h>
+#include <netdb.h>
+#include <thread>
+#include <utility>
+
+#include <arpa/inet.h>
+
+#include "DnsResolver.h"
+#include "NetdConstants.h"  // ScopedAddrinfo
+#include "getaddrinfo.h"
+#include "netd_resolv/resolv.h"
+#include "netdutils/BackoffSequence.h"
+#include "netdutils/DumpWriter.h"
+#include "netid_client.h"
+
+namespace android {
+
+using netdutils::DumpWriter;
+using netdutils::IPAddress;
+using netdutils::IPPrefix;
+
+namespace net {
+
+const char Dns64Configuration::kIPv4OnlyHost[] = "ipv4only.arpa.";
+const char Dns64Configuration::kIPv4Literal1[] = "192.0.0.170";
+const char Dns64Configuration::kIPv4Literal2[] = "192.0.0.171";
+
+void Dns64Configuration::startPrefixDiscovery(unsigned netId) {
+    std::lock_guard guard(mMutex);
+
+    // TODO: Keep previous prefix for a while
+    // Currently, we remove current prefix, if any, before starting a prefix discovery.
+    // This causes that Netd and framework temporarily forgets DNS64 prefix even the prefix may be
+    // discovered in a short time.
+    removeDns64Config(netId);
+
+    Dns64Config cfg(getNextId(), netId);
+    // Emplace a copy of |cfg| in the map.
+    mDns64Configs.emplace(std::make_pair(netId, cfg));
+
+    // Note that capturing |cfg| in this lambda creates a copy.
+    std::thread discovery_thread([this, cfg] {
+        // Make a mutable copy rather than mark the whole lambda mutable.
+        // No particular reason.
+        Dns64Config evalCfg(cfg);
+
+        auto backoff = netdutils::BackoffSequence<>::Builder()
+                               .withInitialRetransmissionTime(std::chrono::seconds(1))
+                               .withMaximumRetransmissionTime(std::chrono::seconds(3600))
+                               .build();
+
+        while (true) {
+            if (!this->shouldContinueDiscovery(evalCfg)) break;
+
+            android_net_context netcontext{};
+            mGetNetworkContextCallback(evalCfg.netId, 0, &netcontext);
+
+            // Prefix discovery must bypass private DNS because in strict mode
+            // the server generally won't know the NAT64 prefix.
+            netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
+            if (doRfc7050PrefixDiscovery(netcontext, &evalCfg)) {
+                this->recordDns64Config(evalCfg);
+                break;
+            }
+
+            if (!this->shouldContinueDiscovery(evalCfg)) break;
+
+            if (!backoff.hasNextTimeout()) break;
+            {
+                std::unique_lock<std::mutex> cvGuard(mMutex);
+                // TODO: Consider some chrono math, combined with wait_until()
+                // perhaps, to prevent early re-resolves from the removal of
+                // other netids with IPv6-only nameservers.
+                mCv.wait_for(cvGuard, backoff.getNextTimeout());
+            }
+        }
+    });
+    discovery_thread.detach();
+}
+
+void Dns64Configuration::stopPrefixDiscovery(unsigned netId) {
+    std::lock_guard guard(mMutex);
+    removeDns64Config(netId);
+    mCv.notify_all();
+}
+
+IPPrefix Dns64Configuration::getPrefix64Locked(unsigned netId) const REQUIRES(mMutex) {
+    const auto& iter = mDns64Configs.find(netId);
+    if (iter != mDns64Configs.end()) return iter->second.prefix64;
+
+    return IPPrefix{};
+}
+
+IPPrefix Dns64Configuration::getPrefix64(unsigned netId) const {
+    std::lock_guard guard(mMutex);
+    return getPrefix64Locked(netId);
+}
+
+void Dns64Configuration::dump(DumpWriter& dw, unsigned netId) {
+    static const char kLabel[] = "DNS64 config";
+
+    std::lock_guard guard(mMutex);
+
+    const auto& iter = mDns64Configs.find(netId);
+    if (iter == mDns64Configs.end()) {
+        dw.println("%s: none", kLabel);
+        return;
+    }
+
+    const Dns64Config& cfg = iter->second;
+    if (cfg.prefix64.length() == 0) {
+        dw.println("%s: no prefix yet discovered", kLabel);
+    } else {
+        dw.println("%s: discovered prefix %s", kLabel, cfg.prefix64.toString().c_str());
+    }
+}
+
+// NOTE: The full RFC 7050 DNS64 discovery process is not implemented here.
+// Instead, and more simplistic version of the same thing is done, and it
+// currently assumes the DNS64 prefix is a /96.
+bool Dns64Configuration::doRfc7050PrefixDiscovery(const android_net_context& netcontext,
+                                                  Dns64Config* cfg) {
+    ALOGW("(%u, %u) Detecting NAT64 prefix from DNS...", cfg->netId, cfg->discoveryId);
+
+    const struct addrinfo hints = {
+            .ai_family = AF_INET6,
+    };
+
+    // TODO: Refactor so that netd can get all the regular getaddrinfo handling
+    // that regular apps get. We bypass the UNIX socket connection back to
+    // ourselves, which means we also bypass all the special netcontext flag
+    // handling and the resolver event logging.
+    struct addrinfo* res = nullptr;
+    const int status =
+            android_getaddrinfofornetcontext(kIPv4OnlyHost, nullptr, &hints, &netcontext, &res);
+    ScopedAddrinfo result(res);
+    if (status != 0) {
+        ALOGW("(%u, %u) plat_prefix/dns(%s) status = %d/%s", cfg->netId, cfg->discoveryId,
+              kIPv4OnlyHost, status, gai_strerror(status));
+        return false;
+    }
+
+    // Use only the first result.  If other records are present, possibly
+    // with differing DNS64 prefixes they are ignored. Note that this is a
+    // violation of https://tools.ietf.org/html/rfc7050#section-3
+    //
+    //     "A node MUST look through all of the received AAAA resource records
+    //      to collect one or more Pref64::/n."
+    //
+    // TODO: Consider remedying this.
+    if (result->ai_family != AF_INET6) {
+        ALOGW("(%u, %u) plat_prefix/unexpected address family: %d", cfg->netId, cfg->discoveryId,
+              result->ai_family);
+        return false;
+    }
+    const IPAddress ipv6(reinterpret_cast<sockaddr_in6*>(result->ai_addr)->sin6_addr);
+    // Only /96 DNS64 prefixes are supported at this time.
+    cfg->prefix64 = IPPrefix(ipv6, 96);
+
+    ALOGW("(%u, %u) Detected NAT64 prefix %s", cfg->netId, cfg->discoveryId,
+          cfg->prefix64.toString().c_str());
+
+    return true;
+}
+
+bool Dns64Configuration::isDiscoveryInProgress(const Dns64Config& cfg) const REQUIRES(mMutex) {
+    const auto& iter = mDns64Configs.find(cfg.netId);
+    if (iter == mDns64Configs.end()) return false;
+
+    const Dns64Config& currentCfg = iter->second;
+    return (currentCfg.discoveryId == cfg.discoveryId);
+}
+
+bool Dns64Configuration::reportNat64PrefixStatus(unsigned netId, bool added, const IPPrefix& pfx) {
+    if (pfx.ip().family() != AF_INET6 || pfx.ip().scope_id() != 0) {
+        ALOGW("Abort to send NAT64 prefix notification. Unexpected NAT64 prefix (%u, %d, %s).",
+              netId, added, pfx.toString().c_str());
+        return false;
+    }
+    Nat64PrefixInfo args = {netId, added, pfx.ip().toString(), (uint8_t)pfx.length()};
+    mPrefixCallback(args);
+    return true;
+}
+
+bool Dns64Configuration::shouldContinueDiscovery(const Dns64Config& cfg) {
+    std::lock_guard guard(mMutex);
+    return isDiscoveryInProgress(cfg);
+}
+
+void Dns64Configuration::removeDns64Config(unsigned netId) REQUIRES(mMutex) {
+    IPPrefix prefix = getPrefix64Locked(netId);
+    mDns64Configs.erase(netId);
+    if (!prefix.isUninitialized()) {
+        reportNat64PrefixStatus(netId, PREFIX_REMOVED, prefix);
+    }
+}
+
+void Dns64Configuration::recordDns64Config(const Dns64Config& cfg) {
+    std::lock_guard guard(mMutex);
+    if (!isDiscoveryInProgress(cfg)) return;
+
+    removeDns64Config(cfg.netId);
+    mDns64Configs.emplace(std::make_pair(cfg.netId, cfg));
+
+    reportNat64PrefixStatus(cfg.netId, PREFIX_ADDED, cfg.prefix64);
+
+    // TODO: consider extending INetdEventListener to report the DNS64 prefix
+    // up to ConnectivityService to potentially include this in LinkProperties.
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/Dns64Configuration.h b/resolv/Dns64Configuration.h
new file mode 100644
index 0000000..58b115e
--- /dev/null
+++ b/resolv/Dns64Configuration.h
@@ -0,0 +1,122 @@
+/*
+ * 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 DNS_DNS64CONFIGURATION_H_
+#define DNS_DNS64CONFIGURATION_H_
+
+#include <netinet/in.h>
+#include <condition_variable>
+#include <cstdlib>
+#include <mutex>
+#include <unordered_map>
+
+#include <android-base/thread_annotations.h>
+#include "netdutils/DumpWriter.h"
+#include "netdutils/InternetAddresses.h"
+
+struct android_net_context;
+
+namespace android {
+namespace net {
+
+/**
+ * This class handles RFC 7050 -style DNS64 prefix discovery.
+ *
+ * The ResolverController starts DNS64 prefix discovery when it observes a
+ * a network with only IPv6 nameservers. (It stops discovery whenever an IPv4
+ * nameserver is added or the network is deleted.)
+ *
+ * Each time prefix discovery is started, a new discoveryId is generated so
+ * that running resolution threads can notice they are no longer the most
+ * recent resolution attempt. This results in the backoff schedule of resolution
+ * being reset.
+ *
+ * Thread-safety: All public methods in this class MUST be thread-safe.
+ * (In other words: this class handles all its locking privately.)
+ */
+class Dns64Configuration {
+  public:
+    // Simple data struct for passing back packet NAT64 prefix event information to the
+    // Dns64PrefixCallback callback.
+    struct Nat64PrefixInfo {
+        unsigned netId;
+        bool added;
+        std::string prefixString;
+        uint8_t prefixLength;
+    };
+
+    // Callback that is triggered for every NAT64 prefix event.
+    using Nat64PrefixCallback = std::function<void(const Nat64PrefixInfo&)>;
+
+    using GetNetworkContextCallback = std::function<void(uint32_t, uint32_t, android_net_context*)>;
+
+    // Parameters from RFC 7050 section 8.
+    static const char kIPv4OnlyHost[];  // "ipv4only.arpa."
+    static const char kIPv4Literal1[];  // 192.0.0.170
+    static const char kIPv4Literal2[];  // 192.0.0.171
+
+    Dns64Configuration() = delete;
+    Dns64Configuration(GetNetworkContextCallback getNetworkCallback,
+                       Nat64PrefixCallback prefixCallback)
+        : mGetNetworkContextCallback(std::move(getNetworkCallback)),
+          mPrefixCallback(std::move(prefixCallback)) {}
+    Dns64Configuration(const Dns64Configuration&) = delete;
+    Dns64Configuration(Dns64Configuration&&) = delete;
+    Dns64Configuration& operator=(const Dns64Configuration&) = delete;
+    Dns64Configuration& operator=(Dns64Configuration&&) = delete;
+
+    void startPrefixDiscovery(unsigned netId);
+    void stopPrefixDiscovery(unsigned netId);
+    netdutils::IPPrefix getPrefix64(unsigned netId) const;
+
+    void dump(netdutils::DumpWriter& dw, unsigned netId);
+
+  private:
+    struct Dns64Config {
+        Dns64Config(unsigned pseudoRandomId, unsigned network)
+            : discoveryId(pseudoRandomId), netId(network) {}
+
+        const unsigned int discoveryId;
+        const unsigned int netId;
+        netdutils::IPPrefix prefix64{};
+    };
+
+    enum { PREFIX_REMOVED, PREFIX_ADDED };
+
+    static bool doRfc7050PrefixDiscovery(const android_net_context& netcontext, Dns64Config* cfg);
+
+    unsigned getNextId() REQUIRES(mMutex) { return mNextId++; }
+    netdutils::IPPrefix getPrefix64Locked(unsigned netId) const REQUIRES(mMutex);
+    bool isDiscoveryInProgress(const Dns64Config& cfg) const REQUIRES(mMutex);
+    bool reportNat64PrefixStatus(unsigned netId, bool added, const netdutils::IPPrefix& pfx)
+            REQUIRES(mMutex);
+
+    bool shouldContinueDiscovery(const Dns64Config& cfg);
+    void recordDns64Config(const Dns64Config& cfg);
+    void removeDns64Config(unsigned netId) REQUIRES(mMutex);
+
+    mutable std::mutex mMutex;
+    std::condition_variable mCv;
+    unsigned int mNextId GUARDED_BY(mMutex);
+    std::unordered_map<unsigned, Dns64Config> mDns64Configs GUARDED_BY(mMutex);
+    const GetNetworkContextCallback mGetNetworkContextCallback;
+    const Nat64PrefixCallback mPrefixCallback;
+};
+
+}  // namespace net
+}  // namespace android
+
+#endif  // DNS_DNS64CONFIGURATION_H_
diff --git a/resolv/DnsProxyListener.cpp b/resolv/DnsProxyListener.cpp
new file mode 100644
index 0000000..63aa331
--- /dev/null
+++ b/resolv/DnsProxyListener.cpp
@@ -0,0 +1,1221 @@
+/*
+ * Copyright (C) 2010 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 "DnsProxyListener.h"
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <linux/if.h>
+#include <math.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#define LOG_TAG "DnsProxyListener"
+
+#include <algorithm>
+#include <list>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android/multinetwork.h>  // ResNsendFlags
+#include <cutils/misc.h>           // FIRST_APPLICATION_UID
+#include <netdutils/InternetAddresses.h>
+#include <netdutils/OperationLimiter.h>
+#include <netdutils/ResponseCode.h>
+#include <netdutils/Slice.h>
+#include <netdutils/Stopwatch.h>
+#include <netdutils/ThreadUtil.h>
+#include <private/android_filesystem_config.h>  // AID_SYSTEM
+#include <statslog_resolv.h>
+#include <sysutils/SocketClient.h>
+
+#include "DnsResolver.h"
+#include "NetdClient.h"  // NETID_USE_LOCAL_NAMESERVERS
+#include "NetdPermissions.h"
+#include "PrivateDnsConfiguration.h"
+#include "ResolverEventReporter.h"
+#include "getaddrinfo.h"
+#include "gethnamaddr.h"
+#include "netd_resolv/stats.h"  // RCODE_TIMEOUT
+#include "res_send.h"
+#include "resolv_private.h"
+#include "stats.pb.h"
+
+using aidl::android::net::metrics::INetdEventListener;
+using android::net::NetworkDnsEventReported;
+
+namespace android {
+
+using netdutils::ResponseCode;
+using netdutils::Stopwatch;
+
+namespace net {
+namespace {
+
+// Limits the number of outstanding DNS queries by client UID.
+constexpr int MAX_QUERIES_PER_UID = 256;
+
+// Max packet size for answer, sync with getaddrinfo.c
+constexpr int MAXPACKET = 8 * 1024;
+
+android::netdutils::OperationLimiter<uid_t> queryLimiter(MAX_QUERIES_PER_UID);
+
+void logArguments(int argc, char** argv) {
+    if (!WOULD_LOG(VERBOSE)) return;
+    for (int i = 0; i < argc; i++) {
+        LOG(VERBOSE) << __func__ << ": argv[" << i << "]=" << (argv[i] ? argv[i] : "null");
+    }
+}
+
+template<typename T>
+void tryThreadOrError(SocketClient* cli, T* handler) {
+    cli->incRef();
+
+    const int rval = netdutils::threadLaunch(handler);
+    if (rval == 0) {
+        // SocketClient decRef() happens in the handler's run() method.
+        return;
+    }
+
+    char* msg = nullptr;
+    asprintf(&msg, "%s (%d)", strerror(-rval), -rval);
+    cli->sendMsg(ResponseCode::OperationFailed, msg, false);
+    free(msg);
+
+    delete handler;
+    cli->decRef();
+}
+
+bool checkAndClearUseLocalNameserversFlag(unsigned* netid) {
+    if (netid == nullptr || ((*netid) & NETID_USE_LOCAL_NAMESERVERS) == 0) {
+        return false;
+    }
+    *netid = (*netid) & ~NETID_USE_LOCAL_NAMESERVERS;
+    return true;
+}
+
+constexpr bool requestingUseLocalNameservers(unsigned flags) {
+    return (flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) != 0;
+}
+
+inline bool queryingViaTls(unsigned dns_netid) {
+    // TODO: The simpler PrivateDnsStatus should suffice here.
+    ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
+    gPrivateDnsConfiguration.getStatus(dns_netid, &privateDnsStatus);
+    switch (static_cast<PrivateDnsMode>(privateDnsStatus.mode)) {
+        case PrivateDnsMode::OPPORTUNISTIC:
+            for (int i = 0; i < privateDnsStatus.numServers; i++) {
+                if (privateDnsStatus.serverStatus[i].validation == Validation::success) {
+                    return true;
+                }
+            }
+            return false;
+        case PrivateDnsMode::STRICT:
+            return true;
+        default:
+            return false;
+    }
+}
+
+bool hasPermissionToBypassPrivateDns(uid_t uid) {
+    static_assert(AID_SYSTEM >= 0 && AID_SYSTEM < FIRST_APPLICATION_UID,
+        "Calls from AID_SYSTEM must not result in a permission check to avoid deadlock.");
+    if (uid >= 0 && uid < FIRST_APPLICATION_UID) {
+        return true;
+    }
+
+    for (const char* const permission :
+         {PERM_CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERM_NETWORK_BYPASS_PRIVATE_DNS,
+          PERM_MAINLINE_NETWORK_STACK}) {
+        if (gResNetdCallbacks.check_calling_permission(permission)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void maybeFixupNetContext(android_net_context* ctx) {
+    if (requestingUseLocalNameservers(ctx->flags) && !hasPermissionToBypassPrivateDns(ctx->uid)) {
+        // Not permitted; clear the flag.
+        ctx->flags &= ~NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
+    }
+
+    if (!requestingUseLocalNameservers(ctx->flags)) {
+        // If we're not explicitly bypassing DNS-over-TLS servers, check whether
+        // DNS-over-TLS is in use as an indicator for when to use more modern
+        // DNS resolution mechanics.
+        if (queryingViaTls(ctx->dns_netid)) {
+            ctx->flags |= NET_CONTEXT_FLAG_USE_EDNS;
+        }
+    }
+}
+
+void addIpAddrWithinLimit(std::vector<std::string>* ip_addrs, const sockaddr* addr,
+                          socklen_t addrlen);
+
+int extractResNsendAnswers(const uint8_t* answer, size_t anslen, int ipType,
+                           std::vector<std::string>* ip_addrs) {
+    int total_ip_addr_count = 0;
+    ns_msg handle;
+    if (ns_initparse((const uint8_t*) answer, anslen, &handle) < 0) {
+        return 0;
+    }
+    int ancount = ns_msg_count(handle, ns_s_an);
+    ns_rr rr;
+    for (int i = 0; i < ancount; i++) {
+        if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
+            continue;
+        }
+        const uint8_t* rdata = ns_rr_rdata(rr);
+        if (ipType == ns_t_a) {
+            sockaddr_in sin = {.sin_family = AF_INET};
+            memcpy(&sin.sin_addr, rdata, sizeof(sin.sin_addr));
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin, sizeof(sin));
+            total_ip_addr_count++;
+        } else if (ipType == ns_t_aaaa) {
+            sockaddr_in6 sin6 = {.sin6_family = AF_INET6};
+            memcpy(&sin6.sin6_addr, rdata, sizeof(sin6.sin6_addr));
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin6, sizeof(sin6));
+            total_ip_addr_count++;
+        }
+    }
+
+    return total_ip_addr_count;
+}
+
+int extractGetAddrInfoAnswers(const addrinfo* result, std::vector<std::string>* ip_addrs) {
+    int total_ip_addr_count = 0;
+    if (result == nullptr) {
+        return 0;
+    }
+    for (const addrinfo* ai = result; ai; ai = ai->ai_next) {
+        sockaddr* ai_addr = ai->ai_addr;
+        if (ai_addr) {
+            addIpAddrWithinLimit(ip_addrs, ai_addr, ai->ai_addrlen);
+            total_ip_addr_count++;
+        }
+    }
+    return total_ip_addr_count;
+}
+
+int extractGetHostByNameAnswers(const hostent* hp, std::vector<std::string>* ip_addrs) {
+    int total_ip_addr_count = 0;
+    if (hp == nullptr) {
+        return 0;
+    }
+    if (hp->h_addrtype == AF_INET) {
+        in_addr** list = (in_addr**) hp->h_addr_list;
+        for (int i = 0; list[i] != nullptr; i++) {
+            sockaddr_in sin = {.sin_family = AF_INET, .sin_addr = *list[i]};
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin, sizeof(sin));
+            total_ip_addr_count++;
+        }
+    } else if (hp->h_addrtype == AF_INET6) {
+        in6_addr** list = (in6_addr**) hp->h_addr_list;
+        for (int i = 0; list[i] != nullptr; i++) {
+            sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = *list[i]};
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin6, sizeof(sin6));
+            total_ip_addr_count++;
+        }
+    }
+    return total_ip_addr_count;
+}
+
+int rcodeToAiError(int rcode) {
+    switch (rcode) {
+        case NOERROR:
+            return 0;
+        case RCODE_TIMEOUT:
+            return NETD_RESOLV_TIMEOUT;
+        default:
+            return EAI_NODATA;
+    }
+}
+
+int resNSendToAiError(int err, int rcode) {
+    if (err > 0) {
+        return rcodeToAiError(rcode);
+    }
+    if (err == -ETIMEDOUT) {
+        return NETD_RESOLV_TIMEOUT;
+    }
+    return EAI_SYSTEM;
+}
+
+template <typename IntegralType>
+bool simpleStrtoul(const char* input, IntegralType* output, int base = 10) {
+    char* endPtr;
+    errno = 0;
+    auto result = strtoul(input, &endPtr, base);
+    // Check the length in order to ensure there is no "-" sign
+    if (!*input || *endPtr || (endPtr - input) != static_cast<ptrdiff_t>(strlen(input)) ||
+        (errno == ERANGE && (result == ULONG_MAX))) {
+        return false;
+    }
+    *output = result;
+    return true;
+}
+
+bool setQueryId(uint8_t* msg, size_t msgLen, uint16_t query_id) {
+    if (msgLen < sizeof(HEADER)) {
+        errno = EINVAL;
+        return false;
+    }
+    auto hp = reinterpret_cast<HEADER*>(msg);
+    hp->id = htons(query_id);
+    return true;
+}
+
+bool parseQuery(const uint8_t* msg, size_t msgLen, uint16_t* query_id, int* rr_type,
+                std::string* rr_name) {
+    ns_msg handle;
+    ns_rr rr;
+    if (ns_initparse((const uint8_t*)msg, msgLen, &handle) < 0 ||
+        ns_parserr(&handle, ns_s_qd, 0, &rr) < 0) {
+        return false;
+    }
+    *query_id = ns_msg_id(handle);
+    *rr_name = ns_rr_name(rr);
+    *rr_type = ns_rr_type(rr);
+    return true;
+}
+
+void reportDnsEvent(int eventType, const android_net_context& netContext, int latencyUs,
+                    int returnCode, const NetworkDnsEventReported& dnsEvent,
+                    const std::string& query_name, const std::vector<std::string>& ip_addrs = {},
+                    int total_ip_addr_count = 0) {
+    std::string dnsQueryStats = dnsEvent.dns_query_events().SerializeAsString();
+    char const* dnsQueryStatsBytes = dnsQueryStats.c_str();
+    stats::BytesField dnsQueryBytesField{dnsQueryStatsBytes, dnsQueryStats.size()};
+    android::net::stats::stats_write(android::net::stats::NETWORK_DNS_EVENT_REPORTED, eventType,
+                                     returnCode, latencyUs, dnsEvent.hints_ai_flags(),
+                                     dnsEvent.res_nsend_flags(), dnsEvent.network_type(),
+                                     dnsEvent.private_dns_modes(), dnsQueryBytesField);
+
+    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
+    if (listeners.size() == 0) {
+        LOG(ERROR) << __func__
+                   << ": DNS event not sent since no INetdEventListener receiver is available.";
+        return;
+    }
+    const int latencyMs = latencyUs / 1000;
+    for (const auto& it : listeners) {
+        it->onDnsEvent(netContext.dns_netid, eventType, returnCode, latencyMs, query_name, ip_addrs,
+                       total_ip_addr_count, netContext.uid);
+    }
+}
+
+bool onlyIPv4Answers(const addrinfo* res) {
+    // Null addrinfo pointer isn't checked because the caller doesn't pass null pointer.
+
+    for (const addrinfo* ai = res; ai; ai = ai->ai_next)
+        if (ai->ai_family != AF_INET) return false;
+
+    return true;
+}
+
+bool isSpecialUseIPv4Address(const struct in_addr& ia) {
+    const uint32_t addr = ntohl(ia.s_addr);
+
+    // Only check necessary IP ranges in RFC 5735 section 4
+    return ((addr & 0xff000000) == 0x00000000) ||  // "This" Network
+           ((addr & 0xff000000) == 0x7f000000) ||  // Loopback
+           ((addr & 0xffff0000) == 0xa9fe0000) ||  // Link Local
+           ((addr & 0xf0000000) == 0xe0000000) ||  // Multicast
+           (addr == INADDR_BROADCAST);             // Limited Broadcast
+}
+
+bool isSpecialUseIPv4Address(const struct sockaddr* sa) {
+    if (sa->sa_family != AF_INET) return false;
+
+    return isSpecialUseIPv4Address(((struct sockaddr_in*) sa)->sin_addr);
+}
+
+bool onlyNonSpecialUseIPv4Addresses(struct hostent* hp) {
+    // Null hostent pointer isn't checked because the caller doesn't pass null pointer.
+
+    if (hp->h_addrtype != AF_INET) return false;
+
+    for (int i = 0; hp->h_addr_list[i] != nullptr; i++)
+        if (isSpecialUseIPv4Address(*(struct in_addr*) hp->h_addr_list[i])) return false;
+
+    return true;
+}
+
+bool onlyNonSpecialUseIPv4Addresses(const addrinfo* res) {
+    // Null addrinfo pointer isn't checked because the caller doesn't pass null pointer.
+
+    for (const addrinfo* ai = res; ai; ai = ai->ai_next) {
+        if (ai->ai_family != AF_INET) return false;
+        if (isSpecialUseIPv4Address(ai->ai_addr)) return false;
+    }
+
+    return true;
+}
+
+void logDnsQueryResult(const struct hostent* hp) {
+    if (!WOULD_LOG(DEBUG)) return;
+    if (hp == nullptr) return;
+
+    LOG(DEBUG) << __func__ << ": DNS records:";
+    for (int i = 0; hp->h_addr_list[i] != nullptr; i++) {
+        char ip_addr[INET6_ADDRSTRLEN];
+        if (inet_ntop(hp->h_addrtype, hp->h_addr_list[i], ip_addr, sizeof(ip_addr)) != nullptr) {
+            LOG(DEBUG) << __func__ << ": [" << i << "] " << hp->h_addrtype;
+        } else {
+            PLOG(DEBUG) << __func__ << ": [" << i << "] numeric hostname translation fail";
+        }
+    }
+}
+
+void logDnsQueryResult(const addrinfo* res) {
+    if (!WOULD_LOG(DEBUG)) return;
+    if (res == nullptr) return;
+
+    int i;
+    const addrinfo* ai;
+    LOG(DEBUG) << __func__ << ": DNS records:";
+    for (ai = res, i = 0; ai; ai = ai->ai_next, i++) {
+        if ((ai->ai_family != AF_INET) && (ai->ai_family != AF_INET6)) continue;
+        char ip_addr[INET6_ADDRSTRLEN];
+        int ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ip_addr, sizeof(ip_addr), nullptr, 0,
+                              NI_NUMERICHOST);
+        if (!ret) {
+            LOG(DEBUG) << __func__ << ": [" << i << "] " << ai->ai_flags << " " << ai->ai_family
+                       << " " << ai->ai_socktype << " " << ai->ai_protocol;
+        } else {
+            LOG(DEBUG) << __func__ << ": [" << i << "] numeric hostname translation fail " << ret;
+        }
+    }
+}
+
+bool isValidNat64Prefix(const netdutils::IPPrefix prefix) {
+    if (prefix.family() != AF_INET6) {
+        LOG(ERROR) << __func__ << ": Only IPv6 NAT64 prefixes are supported " << prefix.family();
+        return false;
+    }
+    if (prefix.length() != 96) {
+        LOG(ERROR) << __func__ << ": Only /96 NAT64 prefixes are supported " << prefix.length();
+        return false;
+    }
+    return true;
+}
+
+bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, struct hostent* hp) {
+    if (hp == nullptr) return false;
+    if (!onlyNonSpecialUseIPv4Addresses(hp)) return false;
+    if (!isValidNat64Prefix(prefix)) return false;
+
+    struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
+    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+    for (int i = 0; hp->h_addr_list[i] != nullptr; i++) {
+        struct in_addr iaOriginal = *(struct in_addr*) hp->h_addr_list[i];
+        struct in6_addr* ia6 = (struct in6_addr*) hp->h_addr_list[i];
+        memset(ia6, 0, sizeof(struct in6_addr));
+
+        // Synthesize /96 NAT64 prefix in place. The space has reserved by getanswer() and
+        // _hf_gethtbyname2() in system/netd/resolv/gethnamaddr.cpp and
+        // system/netd/resolv/sethostent.cpp.
+        *ia6 = v6prefix->sin6_addr;
+        ia6->s6_addr32[3] = iaOriginal.s_addr;
+
+        if (WOULD_LOG(DEBUG)) {
+            char buf[INET6_ADDRSTRLEN];  // big enough for either IPv4 or IPv6
+            inet_ntop(AF_INET, &iaOriginal.s_addr, buf, sizeof(buf));
+            LOG(DEBUG) << __func__ << ": DNS A record: " << buf;
+            inet_ntop(AF_INET6, &v6prefix->sin6_addr, buf, sizeof(buf));
+            LOG(DEBUG) << __func__ << ": NAT64 prefix: " << buf;
+            inet_ntop(AF_INET6, ia6, buf, sizeof(buf));
+            LOG(DEBUG) << __func__ << ": DNS64 Synthesized AAAA record: " << buf;
+        }
+    }
+    hp->h_addrtype = AF_INET6;
+    hp->h_length = sizeof(in6_addr);
+
+    logDnsQueryResult(hp);
+    return true;
+}
+
+bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinfo* result) {
+    if (result == nullptr) return false;
+    if (!onlyNonSpecialUseIPv4Addresses(result)) return false;
+    if (!isValidNat64Prefix(prefix)) return false;
+
+    struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
+    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+    for (addrinfo* ai = result; ai; ai = ai->ai_next) {
+        struct sockaddr_in sinOriginal = *(struct sockaddr_in*) ai->ai_addr;
+        struct sockaddr_in6* sin6 = (struct sockaddr_in6*) ai->ai_addr;
+        memset(sin6, 0, sizeof(sockaddr_in6));
+
+        // Synthesize /96 NAT64 prefix in place. The space has reserved by get_ai() in
+        // system/netd/resolv/getaddrinfo.cpp.
+        sin6->sin6_addr = v6prefix->sin6_addr;
+        sin6->sin6_addr.s6_addr32[3] = sinOriginal.sin_addr.s_addr;
+        sin6->sin6_family = AF_INET6;
+        sin6->sin6_port = sinOriginal.sin_port;
+        ai->ai_addrlen = sizeof(struct sockaddr_in6);
+        ai->ai_family = AF_INET6;
+
+        if (WOULD_LOG(DEBUG)) {
+            char buf[INET6_ADDRSTRLEN];  // big enough for either IPv4 or IPv6
+            inet_ntop(AF_INET, &sinOriginal.sin_addr.s_addr, buf, sizeof(buf));
+            LOG(DEBUG) << __func__ << ": DNS A record: " << buf;
+            inet_ntop(AF_INET6, &v6prefix->sin6_addr, buf, sizeof(buf));
+            LOG(DEBUG) << __func__ << ": NAT64 prefix: " << buf;
+            inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf));
+            LOG(DEBUG) << __func__ << ": DNS64 Synthesized AAAA record: " << buf;
+        }
+    }
+    logDnsQueryResult(result);
+    return true;
+}
+
+bool getDns64Prefix(unsigned netId, netdutils::IPPrefix* prefix) {
+    return !gDnsResolv->resolverCtrl.getPrefix64(netId, prefix);
+}
+
+}  // namespace
+
+DnsProxyListener::DnsProxyListener() : FrameworkListener(SOCKET_NAME) {
+    registerCmd(new GetAddrInfoCmd());
+    registerCmd(new GetHostByAddrCmd());
+    registerCmd(new GetHostByNameCmd());
+    registerCmd(new ResNSendCommand());
+    registerCmd(new GetDnsNetIdCommand());
+}
+
+DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(SocketClient* c, char* host, char* service,
+                                                         addrinfo* hints,
+                                                         const android_net_context& netcontext)
+    : mClient(c), mHost(host), mService(service), mHints(hints), mNetContext(netcontext) {}
+
+DnsProxyListener::GetAddrInfoHandler::~GetAddrInfoHandler() {
+    free(mHost);
+    free(mService);
+    free(mHints);
+}
+
+static bool sendBE32(SocketClient* c, uint32_t data) {
+    uint32_t be_data = htonl(data);
+    return c->sendData(&be_data, sizeof(be_data)) == 0;
+}
+
+// Sends 4 bytes of big-endian length, followed by the data.
+// Returns true on success.
+static bool sendLenAndData(SocketClient* c, const int len, const void* data) {
+    return sendBE32(c, len) && (len == 0 || c->sendData(data, len) == 0);
+}
+
+// Returns true on success
+static bool sendhostent(SocketClient* c, hostent* hp) {
+    bool success = true;
+    int i;
+    if (hp->h_name != nullptr) {
+        success &= sendLenAndData(c, strlen(hp->h_name)+1, hp->h_name);
+    } else {
+        success &= sendLenAndData(c, 0, "") == 0;
+    }
+
+    for (i=0; hp->h_aliases[i] != nullptr; i++) {
+        success &= sendLenAndData(c, strlen(hp->h_aliases[i])+1, hp->h_aliases[i]);
+    }
+    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
+
+    uint32_t buf = htonl(hp->h_addrtype);
+    success &= c->sendData(&buf, sizeof(buf)) == 0;
+
+    buf = htonl(hp->h_length);
+    success &= c->sendData(&buf, sizeof(buf)) == 0;
+
+    for (i=0; hp->h_addr_list[i] != nullptr; i++) {
+        success &= sendLenAndData(c, 16, hp->h_addr_list[i]);
+    }
+    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
+    return success;
+}
+
+static bool sendaddrinfo(SocketClient* c, addrinfo* ai) {
+    // struct addrinfo {
+    //      int     ai_flags;       /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
+    //      int     ai_family;      /* PF_xxx */
+    //      int     ai_socktype;    /* SOCK_xxx */
+    //      int     ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
+    //      socklen_t ai_addrlen;   /* length of ai_addr */
+    //      char    *ai_canonname;  /* canonical name for hostname */
+    //      struct  sockaddr *ai_addr;      /* binary address */
+    //      struct  addrinfo *ai_next;      /* next structure in linked list */
+    // };
+
+    // Write the struct piece by piece because we might be a 64-bit netd
+    // talking to a 32-bit process.
+    bool success =
+            sendBE32(c, ai->ai_flags) &&
+            sendBE32(c, ai->ai_family) &&
+            sendBE32(c, ai->ai_socktype) &&
+            sendBE32(c, ai->ai_protocol);
+    if (!success) {
+        return false;
+    }
+
+    // ai_addrlen and ai_addr.
+    if (!sendLenAndData(c, ai->ai_addrlen, ai->ai_addr)) {
+        return false;
+    }
+
+    // strlen(ai_canonname) and ai_canonname.
+    if (!sendLenAndData(c, ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0, ai->ai_canonname)) {
+        return false;
+    }
+
+    return true;
+}
+
+void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res) {
+    if (mHost == nullptr) return;
+
+    const bool ipv6WantedButNoData = (mHints && mHints->ai_family == AF_INET6 && *rv == EAI_NODATA);
+    const bool unspecWantedButNoIPv6 =
+            ((!mHints || mHints->ai_family == AF_UNSPEC) && *rv == 0 && onlyIPv4Answers(*res));
+
+    if (!ipv6WantedButNoData && !unspecWantedButNoIPv6) {
+        return;
+    }
+
+    netdutils::IPPrefix prefix{};
+    if (!getDns64Prefix(mNetContext.dns_netid, &prefix)) {
+        return;
+    }
+
+    if (ipv6WantedButNoData) {
+        // If caller wants IPv6 answers but no data, try to query IPv4 answers for synthesis
+        const uid_t uid = mClient->getUid();
+        if (queryLimiter.start(uid)) {
+            mHints->ai_family = AF_INET;
+            // Don't need to do freeaddrinfo(res) before starting new DNS lookup because previous
+            // DNS lookup is failed with error EAI_NODATA.
+            *rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, res);
+            queryLimiter.finish(uid);
+            if (*rv) {
+                *rv = EAI_NODATA;  // return original error code
+                return;
+            }
+        } else {
+            LOG(ERROR) << __func__ << ": from UID " << uid << ", max concurrent queries reached";
+            return;
+        }
+    }
+
+    if (!synthesizeNat64PrefixWithARecord(prefix, *res)) {
+        if (ipv6WantedButNoData) {
+            // If caller wants IPv6 answers but no data and failed to synthesize IPv6 answers,
+            // don't return the IPv4 answers.
+            *rv = EAI_NODATA;  // return original error code
+            if (*res) {
+                freeaddrinfo(*res);
+                *res = nullptr;
+            }
+        }
+    }
+}
+
+void DnsProxyListener::GetAddrInfoHandler::run() {
+    LOG(DEBUG) << "GetAddrInfoHandler::run: {" << mNetContext.app_netid << " "
+               << mNetContext.app_mark << " " << mNetContext.dns_netid << " "
+               << mNetContext.dns_mark << " " << mNetContext.uid << " " << mNetContext.flags << "}";
+
+    addrinfo* result = nullptr;
+    Stopwatch s;
+    maybeFixupNetContext(&mNetContext);
+    const uid_t uid = mClient->getUid();
+    int32_t rv = 0;
+    NetworkDnsEventReported dnsEvent;
+    if (queryLimiter.start(uid)) {
+        rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result);
+        queryLimiter.finish(uid);
+    } else {
+        // Note that this error code is currently not passed down to the client.
+        // android_getaddrinfo_proxy() returns EAI_NODATA on any error.
+        rv = EAI_MEMORY;
+        LOG(ERROR) << "GetAddrInfoHandler::run: from UID " << uid
+                   << ", max concurrent queries reached";
+    }
+
+    doDns64Synthesis(&rv, &result);
+    const int latencyUs = int(s.timeTakenUs());
+
+    if (rv) {
+        // getaddrinfo failed
+        mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, &rv, sizeof(rv));
+    } else {
+        bool success = !mClient->sendCode(ResponseCode::DnsProxyQueryResult);
+        addrinfo* ai = result;
+        while (ai && success) {
+            success = sendBE32(mClient, 1) && sendaddrinfo(mClient, ai);
+            ai = ai->ai_next;
+        }
+        success = success && sendBE32(mClient, 0);
+        if (!success) {
+            LOG(WARNING) << "GetAddrInfoHandler::run: Error writing DNS result to client";
+        }
+    }
+    std::vector<std::string> ip_addrs;
+    const int total_ip_addr_count = extractGetAddrInfoAnswers(result, &ip_addrs);
+    reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, dnsEvent,
+                   mHost, ip_addrs, total_ip_addr_count);
+    freeaddrinfo(result);
+    mClient->decRef();
+}
+
+namespace {
+
+void addIpAddrWithinLimit(std::vector<std::string>* ip_addrs, const sockaddr* addr,
+                          socklen_t addrlen) {
+    // ipAddresses array is limited to first INetdEventListener::DNS_REPORTED_IP_ADDRESSES_LIMIT
+    // addresses for A and AAAA. Total count of addresses is provided, to be able to tell whether
+    // some addresses didn't get logged.
+    if (ip_addrs->size() < INetdEventListener::DNS_REPORTED_IP_ADDRESSES_LIMIT) {
+        char ip_addr[INET6_ADDRSTRLEN];
+        if (getnameinfo(addr, addrlen, ip_addr, sizeof(ip_addr), nullptr, 0, NI_NUMERICHOST) == 0) {
+            ip_addrs->push_back(std::string(ip_addr));
+        }
+    }
+}
+
+}  // namespace
+
+DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd() : FrameworkCommand("getaddrinfo") {}
+
+int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
+                                            int argc, char **argv) {
+    logArguments(argc, argv);
+
+    if (argc != 8) {
+        char* msg = nullptr;
+        asprintf( &msg, "Invalid number of arguments to getaddrinfo: %i", argc);
+        LOG(WARNING) << "GetAddrInfoCmd::runCommand: " << (msg ? msg : "null");
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    char* name = argv[1];
+    if (strcmp("^", name) == 0) {
+        name = nullptr;
+    } else {
+        name = strdup(name);
+    }
+
+    char* service = argv[2];
+    if (strcmp("^", service) == 0) {
+        service = nullptr;
+    } else {
+        service = strdup(service);
+    }
+
+    addrinfo* hints = nullptr;
+    int ai_flags = strtol(argv[3], nullptr, 10);
+    int ai_family = strtol(argv[4], nullptr, 10);
+    int ai_socktype = strtol(argv[5], nullptr, 10);
+    int ai_protocol = strtol(argv[6], nullptr, 10);
+    unsigned netId = strtoul(argv[7], nullptr, 10);
+    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
+    const uid_t uid = cli->getUid();
+
+    android_net_context netcontext;
+    gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
+
+    if (useLocalNameservers) {
+        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
+    }
+
+    if (ai_flags != -1 || ai_family != -1 ||
+        ai_socktype != -1 || ai_protocol != -1) {
+        hints = (addrinfo*) calloc(1, sizeof(addrinfo));
+        hints->ai_flags = ai_flags;
+        hints->ai_family = ai_family;
+        hints->ai_socktype = ai_socktype;
+        hints->ai_protocol = ai_protocol;
+    }
+
+    DnsProxyListener::GetAddrInfoHandler* handler =
+            new DnsProxyListener::GetAddrInfoHandler(cli, name, service, hints, netcontext);
+    tryThreadOrError(cli, handler);
+    return 0;
+}
+
+/*******************************************************
+ *                  ResNSendCommand                    *
+ *******************************************************/
+DnsProxyListener::ResNSendCommand::ResNSendCommand() : FrameworkCommand("resnsend") {}
+
+int DnsProxyListener::ResNSendCommand::runCommand(SocketClient* cli, int argc, char** argv) {
+    logArguments(argc, argv);
+
+    const uid_t uid = cli->getUid();
+    if (argc != 4) {
+        LOG(WARNING) << "ResNSendCommand::runCommand: resnsend: from UID " << uid
+                     << ", invalid number of arguments to resnsend: " << argc;
+        sendBE32(cli, -EINVAL);
+        return -1;
+    }
+
+    unsigned netId;
+    if (!simpleStrtoul(argv[1], &netId)) {
+        LOG(WARNING) << "ResNSendCommand::runCommand: resnsend: from UID " << uid
+                     << ", invalid netId";
+        sendBE32(cli, -EINVAL);
+        return -1;
+    }
+
+    uint32_t flags;
+    if (!simpleStrtoul(argv[2], &flags)) {
+        LOG(WARNING) << "ResNSendCommand::runCommand: resnsend: from UID " << uid
+                     << ", invalid flags";
+        sendBE32(cli, -EINVAL);
+        return -1;
+    }
+
+    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
+
+    android_net_context netcontext;
+    gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
+
+    if (useLocalNameservers) {
+        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
+    }
+
+    DnsProxyListener::ResNSendHandler* handler =
+            new DnsProxyListener::ResNSendHandler(cli, argv[3], flags, netcontext);
+    tryThreadOrError(cli, handler);
+    return 0;
+}
+
+DnsProxyListener::ResNSendHandler::ResNSendHandler(SocketClient* c, std::string msg, uint32_t flags,
+                                                   const android_net_context& netcontext)
+    : mClient(c), mMsg(std::move(msg)), mFlags(flags), mNetContext(netcontext) {}
+
+DnsProxyListener::ResNSendHandler::~ResNSendHandler() {
+    mClient->decRef();
+}
+
+void DnsProxyListener::ResNSendHandler::run() {
+    LOG(DEBUG) << "ResNSendHandler::run: " << mFlags << " / {" << mNetContext.app_netid << " "
+               << mNetContext.app_mark << " " << mNetContext.dns_netid << " "
+               << mNetContext.dns_mark << " " << mNetContext.uid << " " << mNetContext.flags << "}";
+
+    Stopwatch s;
+    maybeFixupNetContext(&mNetContext);
+
+    // Decode
+    std::vector<uint8_t> msg(MAXPACKET, 0);
+
+    // Max length of mMsg is less than 1024 since the CMD_BUF_SIZE in FrameworkListener is 1024
+    int msgLen = b64_pton(mMsg.c_str(), msg.data(), MAXPACKET);
+    if (msgLen == -1) {
+        // Decode fail
+        sendBE32(mClient, -EILSEQ);
+        return;
+    }
+
+    const uid_t uid = mClient->getUid();
+    int rr_type = 0;
+    std::string rr_name;
+    uint16_t original_query_id = 0;
+
+    // TODO: Handle the case which is msg contains more than one query
+    if (!parseQuery(msg.data(), msgLen, &original_query_id, &rr_type, &rr_name) ||
+        !setQueryId(msg.data(), msgLen, arc4random_uniform(65536))) {
+        // If the query couldn't be parsed, block the request.
+        LOG(WARNING) << "ResNSendHandler::run: resnsend: from UID " << uid << ", invalid query";
+        sendBE32(mClient, -EINVAL);
+        return;
+    }
+
+    // Send DNS query
+    std::vector<uint8_t> ansBuf(MAXPACKET, 0);
+    int arcode, nsendAns = -1;
+    NetworkDnsEventReported dnsEvent;
+    if (queryLimiter.start(uid)) {
+        nsendAns = resolv_res_nsend(&mNetContext, msg.data(), msgLen, ansBuf.data(), MAXPACKET,
+                                    &arcode, static_cast<ResNsendFlags>(mFlags));
+        queryLimiter.finish(uid);
+    } else {
+        LOG(WARNING) << "ResNSendHandler::run: resnsend: from UID " << uid
+                     << ", max concurrent queries reached";
+        nsendAns = -EBUSY;
+    }
+
+    const int latencyUs = int(s.timeTakenUs());
+
+    // Fail, send -errno
+    if (nsendAns < 0) {
+        sendBE32(mClient, nsendAns);
+        if (rr_type == ns_t_a || rr_type == ns_t_aaaa) {
+            reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
+                           resNSendToAiError(nsendAns, arcode), dnsEvent, rr_name);
+        }
+        return;
+    }
+
+    // Send rcode
+    if (!sendBE32(mClient, arcode)) {
+        PLOG(WARNING) << "ResNSendHandler::run: resnsend: failed to send rcode to uid " << uid;
+        return;
+    }
+
+    // Restore query id and send answer
+    if (!setQueryId(ansBuf.data(), nsendAns, original_query_id) ||
+        !sendLenAndData(mClient, nsendAns, ansBuf.data())) {
+        PLOG(WARNING) << "ResNSendHandler::run: resnsend: failed to send answer to uid " << uid;
+        return;
+    }
+
+    if (rr_type == ns_t_a || rr_type == ns_t_aaaa) {
+        std::vector<std::string> ip_addrs;
+        const int total_ip_addr_count =
+                extractResNsendAnswers((uint8_t*) ansBuf.data(), nsendAns, rr_type, &ip_addrs);
+        reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
+                       resNSendToAiError(nsendAns, arcode), dnsEvent, rr_name, ip_addrs,
+                       total_ip_addr_count);
+    }
+}
+
+namespace {
+
+bool sendCodeAndBe32(SocketClient* c, int code, int data) {
+    return !c->sendCode(code) && sendBE32(c, data);
+}
+
+}  // namespace
+
+/*******************************************************
+ *                  GetDnsNetId                        *
+ *******************************************************/
+DnsProxyListener::GetDnsNetIdCommand::GetDnsNetIdCommand() : FrameworkCommand("getdnsnetid") {}
+
+int DnsProxyListener::GetDnsNetIdCommand::runCommand(SocketClient* cli, int argc, char** argv) {
+    logArguments(argc, argv);
+
+    const uid_t uid = cli->getUid();
+    if (argc != 2) {
+        LOG(WARNING) << "GetDnsNetIdCommand::runCommand: getdnsnetid: from UID " << uid
+                     << ", invalid number of arguments to getdnsnetid: " << argc;
+        sendCodeAndBe32(cli, ResponseCode::DnsProxyQueryResult, -EINVAL);
+        return -1;
+    }
+
+    unsigned netId;
+    if (!simpleStrtoul(argv[1], &netId)) {
+        LOG(WARNING) << "GetDnsNetIdCommand::runCommand: getdnsnetid: from UID " << uid
+                     << ", invalid netId";
+        sendCodeAndBe32(cli, ResponseCode::DnsProxyQueryResult, -EINVAL);
+        return -1;
+    }
+
+    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
+    android_net_context netcontext;
+    gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
+
+    if (useLocalNameservers) {
+        netcontext.app_netid |= NETID_USE_LOCAL_NAMESERVERS;
+    }
+
+    return sendCodeAndBe32(cli, ResponseCode::DnsProxyQueryResult, netcontext.app_netid) ? 0 : -1;
+}
+
+/*******************************************************
+ *                  GetHostByName                      *
+ *******************************************************/
+DnsProxyListener::GetHostByNameCmd::GetHostByNameCmd() : FrameworkCommand("gethostbyname") {}
+
+int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient *cli,
+                                            int argc, char **argv) {
+    logArguments(argc, argv);
+
+    if (argc != 4) {
+        char* msg = nullptr;
+        asprintf(&msg, "Invalid number of arguments to gethostbyname: %i", argc);
+        LOG(WARNING) << "GetHostByNameCmd::runCommand: " << (msg ? msg : "null");
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    uid_t uid = cli->getUid();
+    unsigned netId = strtoul(argv[1], nullptr, 10);
+    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
+    char* name = argv[2];
+    int af = strtol(argv[3], nullptr, 10);
+
+    if (strcmp(name, "^") == 0) {
+        name = nullptr;
+    } else {
+        name = strdup(name);
+    }
+
+    android_net_context netcontext;
+    gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
+
+    if (useLocalNameservers) {
+        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
+    }
+
+    DnsProxyListener::GetHostByNameHandler* handler =
+            new DnsProxyListener::GetHostByNameHandler(cli, name, af, netcontext);
+    tryThreadOrError(cli, handler);
+    return 0;
+}
+
+DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, char* name, int af,
+                                                             const android_net_context& netcontext)
+    : mClient(c), mName(name), mAf(af), mNetContext(netcontext) {}
+
+DnsProxyListener::GetHostByNameHandler::~GetHostByNameHandler() {
+    free(mName);
+}
+
+void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, struct hostent** hpp) {
+    // Don't have to consider family AF_UNSPEC case because gethostbyname{, 2} only supports
+    // family AF_INET or AF_INET6.
+    const bool ipv6WantedButNoData = (mAf == AF_INET6 && *rv == EAI_NODATA);
+
+    if (!ipv6WantedButNoData) {
+        return;
+    }
+
+    netdutils::IPPrefix prefix{};
+    if (!getDns64Prefix(mNetContext.dns_netid, &prefix)) {
+        return;
+    }
+
+    // If caller wants IPv6 answers but no data, try to query IPv4 answers for synthesis
+    const uid_t uid = mClient->getUid();
+    if (queryLimiter.start(uid)) {
+        *rv = android_gethostbynamefornetcontext(mName, AF_INET, &mNetContext, hpp);
+        queryLimiter.finish(uid);
+        if (*rv) {
+            *rv = EAI_NODATA;  // return original error code
+            return;
+        }
+    } else {
+        LOG(ERROR) << __func__ << ": from UID " << uid << ", max concurrent queries reached";
+        return;
+    }
+
+    if (!synthesizeNat64PrefixWithARecord(prefix, *hpp)) {
+        // If caller wants IPv6 answers but no data and failed to synthesize IPv4 answers,
+        // don't return the IPv4 answers.
+        *hpp = nullptr;
+    }
+}
+
+void DnsProxyListener::GetHostByNameHandler::run() {
+    Stopwatch s;
+    maybeFixupNetContext(&mNetContext);
+    const uid_t uid = mClient->getUid();
+    hostent* hp = nullptr;
+    int32_t rv = 0;
+    NetworkDnsEventReported dnsEvent;
+    if (queryLimiter.start(uid)) {
+        rv = android_gethostbynamefornetcontext(mName, mAf, &mNetContext, &hp);
+        queryLimiter.finish(uid);
+    } else {
+        rv = EAI_MEMORY;
+        LOG(ERROR) << "GetHostByNameHandler::run: from UID " << uid
+                   << ", max concurrent queries reached";
+    }
+
+    doDns64Synthesis(&rv, &hp);
+    const int latencyUs = lround(s.timeTakenUs());
+    LOG(DEBUG) << "GetHostByNameHandler::run: errno: " << (hp ? "success" : strerror(errno));
+
+    bool success = true;
+    if (hp) {
+        // hp is not nullptr iff. rv is 0.
+        success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
+        success &= sendhostent(mClient, hp);
+    } else {
+        success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, nullptr, 0) == 0;
+    }
+
+    if (!success) {
+        LOG(WARNING) << "GetHostByNameHandler::run: Error writing DNS result to client";
+    }
+
+    std::vector<std::string> ip_addrs;
+    const int total_ip_addr_count = extractGetHostByNameAnswers(hp, &ip_addrs);
+    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, dnsEvent,
+                   mName, ip_addrs, total_ip_addr_count);
+    mClient->decRef();
+}
+
+
+/*******************************************************
+ *                  GetHostByAddr                      *
+ *******************************************************/
+DnsProxyListener::GetHostByAddrCmd::GetHostByAddrCmd() : FrameworkCommand("gethostbyaddr") {}
+
+int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient *cli,
+                                            int argc, char **argv) {
+    logArguments(argc, argv);
+
+    if (argc != 5) {
+        char* msg = nullptr;
+        asprintf(&msg, "Invalid number of arguments to gethostbyaddr: %i", argc);
+        LOG(WARNING) << "GetHostByAddrCmd::runCommand: " << (msg ? msg : "null");
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    char* addrStr = argv[1];
+    int addrLen = strtol(argv[2], nullptr, 10);
+    int addrFamily = strtol(argv[3], nullptr, 10);
+    uid_t uid = cli->getUid();
+    unsigned netId = strtoul(argv[4], nullptr, 10);
+    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
+
+    void* addr = malloc(sizeof(in6_addr));
+    errno = 0;
+    int result = inet_pton(addrFamily, addrStr, addr);
+    if (result <= 0) {
+        char* msg = nullptr;
+        asprintf(&msg, "inet_pton(\"%s\") failed %s", addrStr, strerror(errno));
+        LOG(WARNING) << "GetHostByAddrCmd::runCommand: " << (msg ? msg : "null");
+        cli->sendMsg(ResponseCode::OperationFailed, msg, false);
+        free(addr);
+        free(msg);
+        return -1;
+    }
+
+    android_net_context netcontext;
+    gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
+
+    if (useLocalNameservers) {
+        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
+    }
+
+    DnsProxyListener::GetHostByAddrHandler* handler = new DnsProxyListener::GetHostByAddrHandler(
+            cli, addr, addrLen, addrFamily, netcontext);
+    tryThreadOrError(cli, handler);
+    return 0;
+}
+
+DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c, void* address,
+                                                             int addressLen, int addressFamily,
+                                                             const android_net_context& netcontext)
+    : mClient(c),
+      mAddress(address),
+      mAddressLen(addressLen),
+      mAddressFamily(addressFamily),
+      mNetContext(netcontext) {}
+
+DnsProxyListener::GetHostByAddrHandler::~GetHostByAddrHandler() {
+    free(mAddress);
+}
+
+void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(struct hostent** hpp) {
+    if (*hpp != nullptr || mAddressFamily != AF_INET6 || !mAddress) {
+        return;
+    }
+
+    netdutils::IPPrefix prefix{};
+    if (!getDns64Prefix(mNetContext.dns_netid, &prefix)) {
+        return;
+    }
+
+    if (!isValidNat64Prefix(prefix)) {
+        return;
+    }
+
+    struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
+    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+    struct in6_addr v6addr = *(in6_addr*) mAddress;
+    // Check if address has NAT64 prefix. Only /96 IPv6 NAT64 prefixes are supported
+    if ((v6addr.s6_addr32[0] != v6prefix->sin6_addr.s6_addr32[0]) ||
+        (v6addr.s6_addr32[1] != v6prefix->sin6_addr.s6_addr32[1]) ||
+        (v6addr.s6_addr32[2] != v6prefix->sin6_addr.s6_addr32[2])) {
+        return;
+    }
+
+    const uid_t uid = mClient->getUid();
+    if (queryLimiter.start(uid)) {
+        // Remove NAT64 prefix and do reverse DNS query
+        struct in_addr v4addr = {.s_addr = v6addr.s6_addr32[3]};
+        android_gethostbyaddrfornetcontext(&v4addr, sizeof(v4addr), AF_INET, &mNetContext, hpp);
+        queryLimiter.finish(uid);
+        if (*hpp) {
+            // Replace IPv4 address with original queried IPv6 address in place. The space has
+            // reserved by dns_gethtbyaddr() and netbsd_gethostent_r() in
+            // system/netd/resolv/gethnamaddr.cpp.
+            // Note that android_gethostbyaddrfornetcontext returns only one entry in result.
+            memcpy((*hpp)->h_addr_list[0], &v6addr, sizeof(v6addr));
+            (*hpp)->h_addrtype = AF_INET6;
+            (*hpp)->h_length = sizeof(struct in6_addr);
+        }
+    } else {
+        LOG(ERROR) << __func__ << ": from UID " << uid << ", max concurrent queries reached";
+    }
+}
+
+void DnsProxyListener::GetHostByAddrHandler::run() {
+    Stopwatch s;
+    maybeFixupNetContext(&mNetContext);
+    const uid_t uid = mClient->getUid();
+    hostent* hp = nullptr;
+    int32_t rv = 0;
+    NetworkDnsEventReported dnsEvent;
+    if (queryLimiter.start(uid)) {
+        rv = android_gethostbyaddrfornetcontext(mAddress, mAddressLen, mAddressFamily,
+                                                &mNetContext, &hp);
+        queryLimiter.finish(uid);
+    } else {
+        rv = EAI_MEMORY;
+        LOG(ERROR) << "GetHostByAddrHandler::run: from UID " << uid
+                   << ", max concurrent queries reached";
+    }
+
+    doDns64ReverseLookup(&hp);
+    const int latencyUs = int(s.timeTakenUs());
+
+    LOG(DEBUG) << "GetHostByAddrHandler::run: result: " << (hp ? "success" : gai_strerror(rv));
+
+    bool success = true;
+    if (hp) {
+        success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
+        success &= sendhostent(mClient, hp);
+    } else {
+        success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, nullptr, 0) == 0;
+    }
+
+    if (!success) {
+        LOG(WARNING) << "GetHostByAddrHandler::run: Error writing DNS result to client";
+    }
+
+    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, dnsEvent,
+                   (hp && hp->h_name) ? hp->h_name : "null", {}, 0);
+    mClient->decRef();
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/DnsProxyListener.h b/resolv/DnsProxyListener.h
new file mode 100644
index 0000000..423e77f
--- /dev/null
+++ b/resolv/DnsProxyListener.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2010 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 <string>
+
+#include <netd_resolv/resolv.h>  // android_net_context
+#include <sysutils/FrameworkCommand.h>
+#include <sysutils/FrameworkListener.h>
+
+struct addrinfo;
+struct hostent;
+
+namespace android {
+namespace net {
+
+class DnsProxyListener : public FrameworkListener {
+  public:
+    DnsProxyListener();
+    virtual ~DnsProxyListener() {}
+
+    static constexpr const char* SOCKET_NAME = "dnsproxyd";
+
+  private:
+    class GetAddrInfoCmd : public FrameworkCommand {
+      public:
+        GetAddrInfoCmd();
+        virtual ~GetAddrInfoCmd() {}
+        int runCommand(SocketClient* c, int argc, char** argv) override;
+    };
+
+    /* ------ getaddrinfo ------*/
+    class GetAddrInfoHandler {
+      public:
+        // Note: All of host, service, and hints may be NULL
+        GetAddrInfoHandler(SocketClient* c, char* host, char* service, addrinfo* hints,
+                           const android_net_context& netcontext);
+        ~GetAddrInfoHandler();
+
+        void run();
+
+      private:
+        void doDns64Synthesis(int32_t* rv, addrinfo** res);
+
+        SocketClient* mClient;  // ref counted
+        char* mHost;            // owned. TODO: convert to std::string.
+        char* mService;         // owned. TODO: convert to std::string.
+        addrinfo* mHints;       // owned
+        android_net_context mNetContext;
+    };
+
+    /* ------ gethostbyname ------*/
+    class GetHostByNameCmd : public FrameworkCommand {
+      public:
+        GetHostByNameCmd();
+        virtual ~GetHostByNameCmd() {}
+        int runCommand(SocketClient* c, int argc, char** argv) override;
+    };
+
+    class GetHostByNameHandler {
+      public:
+        GetHostByNameHandler(SocketClient* c, char* name, int af,
+                             const android_net_context& netcontext);
+        ~GetHostByNameHandler();
+
+        void run();
+
+      private:
+        void doDns64Synthesis(int32_t* rv, hostent** hpp);
+
+        SocketClient* mClient;  // ref counted
+        char* mName;            // owned. TODO: convert to std::string.
+        int mAf;
+        android_net_context mNetContext;
+    };
+
+    /* ------ gethostbyaddr ------*/
+    class GetHostByAddrCmd : public FrameworkCommand {
+      public:
+        GetHostByAddrCmd();
+        virtual ~GetHostByAddrCmd() {}
+        int runCommand(SocketClient* c, int argc, char** argv) override;
+    };
+
+    class GetHostByAddrHandler {
+      public:
+        GetHostByAddrHandler(SocketClient* c, void* address, int addressLen, int addressFamily,
+                             const android_net_context& netcontext);
+        ~GetHostByAddrHandler();
+
+        void run();
+
+      private:
+        void doDns64ReverseLookup(hostent** hpp);
+
+        SocketClient* mClient;  // ref counted
+        void* mAddress;         // address to lookup; owned
+        int mAddressLen;        // length of address to look up
+        int mAddressFamily;     // address family
+        android_net_context mNetContext;
+    };
+
+    /* ------ resnsend ------*/
+    class ResNSendCommand : public FrameworkCommand {
+      public:
+        ResNSendCommand();
+        virtual ~ResNSendCommand() {}
+        int runCommand(SocketClient* c, int argc, char** argv) override;
+    };
+
+    class ResNSendHandler {
+      public:
+        ResNSendHandler(SocketClient* c, std::string msg, uint32_t flags,
+                        const android_net_context& netcontext);
+        ~ResNSendHandler();
+
+        void run();
+
+      private:
+        SocketClient* mClient;  // ref counted
+        std::string mMsg;
+        uint32_t mFlags;
+        android_net_context mNetContext;
+    };
+
+    /* ------ getdnsnetid ------*/
+    class GetDnsNetIdCommand : public FrameworkCommand {
+      public:
+        GetDnsNetIdCommand();
+        virtual ~GetDnsNetIdCommand() {}
+        int runCommand(SocketClient* c, int argc, char** argv) override;
+    };
+};
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/DnsResolver.cpp b/resolv/DnsResolver.cpp
new file mode 100644
index 0000000..092ff3f
--- /dev/null
+++ b/resolv/DnsResolver.cpp
@@ -0,0 +1,82 @@
+/**
+ * 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 "DnsResolver.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+#include "DnsProxyListener.h"
+#include "DnsResolverService.h"
+#include "netd_resolv/resolv.h"
+#include "res_debug.h"
+
+bool resolv_init(const ResolverNetdCallbacks* callbacks) {
+    android::base::InitLogging(/*argv=*/nullptr);
+    android::base::SetDefaultTag("libnetd_resolv");
+    LOG(INFO) << __func__ << ": Initializing resolver";
+    resolv_set_log_severity(android::base::WARNING);
+
+    android::net::gResNetdCallbacks = *callbacks;
+    android::net::gDnsResolv = android::net::DnsResolver::getInstance();
+    return android::net::gDnsResolv->start();
+}
+
+namespace android {
+namespace net {
+
+namespace {
+
+bool verifyCallbacks() {
+    return gResNetdCallbacks.check_calling_permission && gResNetdCallbacks.get_network_context &&
+           gResNetdCallbacks.log;
+}
+
+}  // namespace
+
+DnsResolver* gDnsResolv = nullptr;
+ResolverNetdCallbacks gResNetdCallbacks;
+netdutils::Log gDnsResolverLog("dnsResolver");
+
+DnsResolver* DnsResolver::getInstance() {
+    // Instantiated on first use.
+    static DnsResolver instance;
+    return &instance;
+}
+
+bool DnsResolver::start() {
+    if (!verifyCallbacks()) {
+        LOG(ERROR) << __func__ << ": Callback verification failed";
+        return false;
+    }
+    if (mDnsProxyListener.startListener()) {
+        PLOG(ERROR) << __func__ << ": Unable to start DnsProxyListener";
+        return false;
+    }
+    binder_status_t ret;
+    if ((ret = DnsResolverService::start()) != STATUS_OK) {
+        LOG(ERROR) << __func__ << ": Unable to start DnsResolverService: " << ret;
+        return false;
+    }
+    return true;
+}
+
+int DnsResolver::setLogSeverity(int32_t logSeverity) {
+    return resolv_set_log_severity(logSeverity);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/DnsResolver.h b/resolv/DnsResolver.h
new file mode 100644
index 0000000..6d8d2ce
--- /dev/null
+++ b/resolv/DnsResolver.h
@@ -0,0 +1,51 @@
+/**
+ * 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 _DNS_RESOLVER_H_
+#define _DNS_RESOLVER_H_
+
+#include "DnsProxyListener.h"
+#include "ResolverController.h"
+#include "netd_resolv/resolv.h"
+#include "netdutils/Log.h"
+
+namespace android {
+namespace net {
+
+class DnsResolver {
+  public:
+    static DnsResolver* getInstance();
+    bool start();
+    int setLogSeverity(int32_t logSeverity);
+
+    DnsResolver(DnsResolver const&) = delete;
+    void operator=(DnsResolver const&) = delete;
+
+    ResolverController resolverCtrl;
+
+  private:
+    DnsResolver() {}
+    DnsProxyListener mDnsProxyListener;
+};
+
+extern DnsResolver* gDnsResolv;
+extern ResolverNetdCallbacks gResNetdCallbacks;
+extern netdutils::Log gDnsResolverLog;
+
+}  // namespace net
+}  // namespace android
+
+#endif  // _DNS_RESOLVER_H_
diff --git a/resolv/DnsResolverService.cpp b/resolv/DnsResolverService.cpp
new file mode 100644
index 0000000..8eefd22
--- /dev/null
+++ b/resolv/DnsResolverService.cpp
@@ -0,0 +1,297 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "DnsResolverService"
+
+#include "DnsResolverService.h"
+
+#include <set>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <json/value.h>
+#include <json/writer.h>
+#include <log/log.h>
+#include <netdutils/DumpWriter.h>
+#include <openssl/base64.h>
+#include <private/android_filesystem_config.h>  // AID_SYSTEM
+
+#include "BinderUtil.h"
+#include "DnsResolver.h"
+#include "NetdConstants.h"    // SHA256_SIZE
+#include "NetdPermissions.h"  // PERM_*
+#include "ResolverEventReporter.h"
+#include "resolv_cache.h"
+
+using aidl::android::net::ResolverParamsParcel;
+using android::base::Join;
+using android::base::StringPrintf;
+using android::netdutils::DumpWriter;
+
+namespace android {
+namespace net {
+
+namespace {
+
+#define ENFORCE_ANY_PERMISSION(...)                                      \
+    do {                                                                 \
+        ::ndk::ScopedAStatus status = checkAnyPermission({__VA_ARGS__}); \
+        if (!status.isOk()) {                                            \
+            return status;                                               \
+        }                                                                \
+    } while (0)
+
+#define ENFORCE_INTERNAL_PERMISSIONS() \
+    ENFORCE_ANY_PERMISSION(PERM_CONNECTIVITY_INTERNAL, PERM_MAINLINE_NETWORK_STACK)
+
+#define ENFORCE_NETWORK_STACK_PERMISSIONS() \
+    ENFORCE_ANY_PERMISSION(PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK)
+
+inline ::ndk::ScopedAStatus statusFromErrcode(int ret) {
+    if (ret) {
+        return ::ndk::ScopedAStatus(
+                AStatus_fromServiceSpecificErrorWithMessage(-ret, strerror(-ret)));
+    }
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
+}  // namespace
+
+DnsResolverService::DnsResolverService() {
+    // register log callback to BnDnsResolver::logFunc
+    BnDnsResolver::logFunc =
+            std::bind(binderCallLogFn, std::placeholders::_1,
+                      [](const std::string& msg) { gResNetdCallbacks.log(msg.c_str()); });
+}
+
+binder_status_t DnsResolverService::start() {
+    // TODO: Add disableBackgroundScheduling(true) after libbinder_ndk support it. b/126506010
+    // NetdNativeService does call disableBackgroundScheduling currently, so it is fine now.
+    DnsResolverService* resolverService = new DnsResolverService();
+    binder_status_t status =
+            AServiceManager_addService(resolverService->asBinder().get(), getServiceName());
+    if (status != STATUS_OK) {
+        return status;
+    }
+
+    ABinderProcess_startThreadPool();
+
+    // TODO: register log callback if binder NDK backend support it. b/126501406
+
+    return STATUS_OK;
+}
+
+binder_status_t DnsResolverService::dump(int fd, const char**, uint32_t) {
+    auto dump_permission = checkAnyPermission({PERM_DUMP});
+    if (!dump_permission.isOk()) {
+        return STATUS_PERMISSION_DENIED;
+    }
+
+    // This method does not grab any locks. If individual classes need locking
+    // their dump() methods MUST handle locking appropriately.
+    DumpWriter dw(fd);
+    for (auto netId : resolv_list_caches()) {
+        dw.println("NetId: %u", netId);
+        gDnsResolv->resolverCtrl.dump(dw, netId);
+        dw.blankline();
+    }
+
+    return STATUS_OK;
+}
+
+::ndk::ScopedAStatus DnsResolverService::isAlive(bool* alive) {
+    ENFORCE_INTERNAL_PERMISSIONS();
+
+    *alive = true;
+
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
+::ndk::ScopedAStatus DnsResolverService::registerEventListener(
+        const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    int res = ResolverEventReporter::getInstance().addListener(listener);
+
+    return statusFromErrcode(res);
+}
+
+::ndk::ScopedAStatus DnsResolverService::checkAnyPermission(
+        const std::vector<const char*>& permissions) {
+    // TODO: Remove callback and move this to unnamed namespace after libbiner_ndk supports
+    // check_permission.
+    if (!gResNetdCallbacks.check_calling_permission) {
+        return ::ndk::ScopedAStatus(AStatus_fromExceptionCodeWithMessage(
+                EX_NULL_POINTER, "check_calling_permission is null"));
+    }
+    pid_t pid = AIBinder_getCallingPid();
+    uid_t uid = AIBinder_getCallingUid();
+
+    // If the caller is the system UID, don't check permissions.
+    // Otherwise, if the system server's binder thread pool is full, and all the threads are
+    // blocked on a thread that's waiting for us to complete, we deadlock. http://b/69389492
+    //
+    // From a security perspective, there is currently no difference, because:
+    // 1. The only permissions we check in netd's binder interface are CONNECTIVITY_INTERNAL
+    //    and NETWORK_STACK, which the system server always has (or MAINLINE_NETWORK_STACK, which
+    //    is equivalent to having both CONNECTIVITY_INTERNAL and NETWORK_STACK).
+    // 2. AID_SYSTEM always has all permissions. See ActivityManager#checkComponentPermission.
+    if (uid == AID_SYSTEM) {
+        return ::ndk::ScopedAStatus(AStatus_newOk());
+    }
+
+    for (const char* permission : permissions) {
+        if (gResNetdCallbacks.check_calling_permission(permission)) {
+            return ::ndk::ScopedAStatus(AStatus_newOk());
+        }
+    }
+
+    auto err = StringPrintf("UID %d / PID %d does not have any of the following permissions: %s",
+                            uid, pid, Join(permissions, ',').c_str());
+    return ::ndk::ScopedAStatus(AStatus_fromExceptionCodeWithMessage(EX_SECURITY, err.c_str()));
+}
+
+namespace {
+
+// Parse a base64 encoded string into a vector of bytes.
+// On failure, return an empty vector.
+static std::vector<uint8_t> parseBase64(const std::string& input) {
+    std::vector<uint8_t> decoded;
+    size_t out_len;
+    if (EVP_DecodedLength(&out_len, input.size()) != 1) {
+        return decoded;
+    }
+    // out_len is now an upper bound on the output length.
+    decoded.resize(out_len);
+    if (EVP_DecodeBase64(decoded.data(), &out_len, decoded.size(),
+                         reinterpret_cast<const uint8_t*>(input.data()), input.size()) == 1) {
+        // Possibly shrink the vector if the actual output was smaller than the bound.
+        decoded.resize(out_len);
+    } else {
+        decoded.clear();
+    }
+    if (out_len != SHA256_SIZE) {
+        decoded.clear();
+    }
+    return decoded;
+}
+
+}  // namespace
+
+::ndk::ScopedAStatus DnsResolverService::setResolverConfiguration(
+        const ResolverParamsParcel& resolverParams) {
+    // Locking happens in PrivateDnsConfiguration and res_* functions.
+    ENFORCE_INTERNAL_PERMISSIONS();
+    // TODO: Remove this log after AIDL gen_log supporting more types, b/129732660
+    auto entry =
+            gDnsResolverLog.newEntry()
+                    .prettyFunction(__PRETTY_FUNCTION__)
+                    .args(resolverParams.netId, resolverParams.servers, resolverParams.domains,
+                          resolverParams.sampleValiditySeconds, resolverParams.successThreshold,
+                          resolverParams.minSamples, resolverParams.maxSamples,
+                          resolverParams.baseTimeoutMsec, resolverParams.retryCount,
+                          resolverParams.tlsServers, resolverParams.tlsFingerprints);
+
+    std::set<std::vector<uint8_t>> decoded_fingerprints;
+    for (const std::string& fingerprint : resolverParams.tlsFingerprints) {
+        std::vector<uint8_t> decoded = parseBase64(fingerprint);
+        if (decoded.empty()) {
+            return ::ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    EINVAL, "ResolverController error: bad fingerprint"));
+        }
+        decoded_fingerprints.emplace(decoded);
+    }
+
+    int res =
+            gDnsResolv->resolverCtrl.setResolverConfiguration(resolverParams, decoded_fingerprints);
+
+    gResNetdCallbacks.log(entry.returns(res).withAutomaticDuration().toString().c_str());
+
+    return statusFromErrcode(res);
+}
+
+::ndk::ScopedAStatus DnsResolverService::getResolverInfo(
+        int32_t netId, std::vector<std::string>* servers, std::vector<std::string>* domains,
+        std::vector<std::string>* tlsServers, std::vector<int32_t>* params,
+        std::vector<int32_t>* stats, std::vector<int32_t>* wait_for_pending_req_timeout_count) {
+    // Locking happens in PrivateDnsConfiguration and res_* functions.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    int res = gDnsResolv->resolverCtrl.getResolverInfo(netId, servers, domains, tlsServers, params,
+                                                       stats, wait_for_pending_req_timeout_count);
+
+    return statusFromErrcode(res);
+}
+
+::ndk::ScopedAStatus DnsResolverService::startPrefix64Discovery(int32_t netId) {
+    // Locking happens in Dns64Configuration.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    gDnsResolv->resolverCtrl.startPrefix64Discovery(netId);
+
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
+::ndk::ScopedAStatus DnsResolverService::stopPrefix64Discovery(int32_t netId) {
+    // Locking happens in Dns64Configuration.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    gDnsResolv->resolverCtrl.stopPrefix64Discovery(netId);
+
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
+::ndk::ScopedAStatus DnsResolverService::getPrefix64(int netId, std::string* stringPrefix) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    netdutils::IPPrefix prefix{};
+    int res = gDnsResolv->resolverCtrl.getPrefix64(netId, &prefix);
+    *stringPrefix = prefix.toString();
+
+    return statusFromErrcode(res);
+}
+
+::ndk::ScopedAStatus DnsResolverService::setLogSeverity(int32_t logSeverity) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    int res = gDnsResolv->setLogSeverity(logSeverity);
+
+    return statusFromErrcode(res);
+}
+
+::ndk::ScopedAStatus DnsResolverService::destroyNetworkCache(int netId) {
+    // Locking happens in res_cache.cpp functions.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    gDnsResolv->resolverCtrl.destroyNetworkCache(netId);
+
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
+::ndk::ScopedAStatus DnsResolverService::createNetworkCache(int netId) {
+    // Locking happens in res_cache.cpp functions.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    int res = gDnsResolv->resolverCtrl.createNetworkCache(netId);
+
+    return statusFromErrcode(res);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/DnsResolverService.h b/resolv/DnsResolverService.h
new file mode 100644
index 0000000..1a1c9e7
--- /dev/null
+++ b/resolv/DnsResolverService.h
@@ -0,0 +1,72 @@
+/**
+ * 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 _DNS_RESOLVER_SERVICE_H_
+#define _DNS_RESOLVER_SERVICE_H_
+
+#include <vector>
+
+#include <aidl/android/net/BnDnsResolver.h>
+#include <aidl/android/net/ResolverParamsParcel.h>
+#include <android/binder_ibinder.h>
+
+#include "netd_resolv/resolv.h"
+
+namespace android {
+namespace net {
+
+class DnsResolverService : public aidl::android::net::BnDnsResolver {
+  public:
+    static binder_status_t start();
+    static char const* getServiceName() { return "dnsresolver"; }
+
+    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+    ::ndk::ScopedAStatus isAlive(bool* alive) override;
+    ::ndk::ScopedAStatus registerEventListener(
+            const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
+            override;
+
+    // Resolver commands.
+    ::ndk::ScopedAStatus setResolverConfiguration(
+            const aidl::android::net::ResolverParamsParcel& resolverParams) override;
+    ::ndk::ScopedAStatus getResolverInfo(
+            int32_t netId, std::vector<std::string>* servers, std::vector<std::string>* domains,
+            std::vector<std::string>* tlsServers, std::vector<int32_t>* params,
+            std::vector<int32_t>* stats,
+            std::vector<int32_t>* wait_for_pending_req_timeout_count) override;
+    ::ndk::ScopedAStatus destroyNetworkCache(int32_t netId) override;
+    ::ndk::ScopedAStatus createNetworkCache(int32_t netId) override;
+
+    // DNS64-related commands
+    ::ndk::ScopedAStatus startPrefix64Discovery(int32_t netId) override;
+    ::ndk::ScopedAStatus stopPrefix64Discovery(int32_t netId) override;
+    // (internal use only)
+    ::ndk::ScopedAStatus getPrefix64(int netId, std::string* stringPrefix) override;
+
+    // Debug log command
+    ::ndk::ScopedAStatus setLogSeverity(int32_t logSeverity) override;
+
+  private:
+    DnsResolverService();
+    // TODO: Remove below items after libbiner_ndk supports check_permission.
+    ::ndk::ScopedAStatus checkAnyPermission(const std::vector<const char*>& permissions);
+};
+
+}  // namespace net
+}  // namespace android
+
+#endif  // _DNS_RESOLVER_SERVICE_H_
diff --git a/server/dns/DnsTlsDispatcher.cpp b/resolv/DnsTlsDispatcher.cpp
similarity index 95%
rename from server/dns/DnsTlsDispatcher.cpp
rename to resolv/DnsTlsDispatcher.cpp
index 95fbb9a..d9896ad 100644
--- a/server/dns/DnsTlsDispatcher.cpp
+++ b/resolv/DnsTlsDispatcher.cpp
@@ -17,7 +17,8 @@
 #define LOG_TAG "DnsTlsDispatcher"
 //#define LOG_NDEBUG 0
 
-#include "dns/DnsTlsDispatcher.h"
+#include "DnsTlsDispatcher.h"
+#include "DnsTlsSocketFactory.h"
 
 #include "log/log.h"
 
@@ -29,6 +30,10 @@
 // static
 std::mutex DnsTlsDispatcher::sLock;
 
+DnsTlsDispatcher::DnsTlsDispatcher() {
+    mFactory.reset(new DnsTlsSocketFactory());
+}
+
 std::list<DnsTlsServer> DnsTlsDispatcher::getOrderedServerList(
         const std::list<DnsTlsServer> &tlsServers, unsigned mark) const {
     // Our preferred DnsTlsServer order is:
@@ -44,7 +49,7 @@
     // Pull out any servers for which we might have existing connections and
     // place them at the from the list of servers to try.
     {
-        std::lock_guard<std::mutex> guard(sLock);
+        std::lock_guard guard(sLock);
 
         for (const auto& tlsServer : tlsServers) {
             const Key key = std::make_pair(mark, tlsServer);
@@ -113,7 +118,7 @@
     const Key key = std::make_pair(mark, server);
     Transport* xport;
     {
-        std::lock_guard<std::mutex> guard(sLock);
+        std::lock_guard guard(sLock);
         auto it = mStore.find(key);
         if (it == mStore.end()) {
             xport = new Transport(server, mark, mFactory.get());
@@ -144,7 +149,7 @@
 
     auto now = std::chrono::steady_clock::now();
     {
-        std::lock_guard<std::mutex> guard(sLock);
+        std::lock_guard guard(sLock);
         --xport->useCount;
         xport->lastUsed = now;
         cleanup(now);
diff --git a/server/dns/DnsTlsDispatcher.h b/resolv/DnsTlsDispatcher.h
similarity index 80%
rename from server/dns/DnsTlsDispatcher.h
rename to resolv/DnsTlsDispatcher.h
index 5ba057a..7a48089 100644
--- a/server/dns/DnsTlsDispatcher.h
+++ b/resolv/DnsTlsDispatcher.h
@@ -18,52 +18,48 @@
 #define _DNS_DNSTLSDISPATCHER_H
 
 #include <list>
-#include <memory>
 #include <map>
+#include <memory>
 #include <mutex>
 
 #include <android-base/thread_annotations.h>
-
 #include <netdutils/Slice.h>
 
-#include "dns/DnsTlsServer.h"
-#include "dns/DnsTlsSocket.h"
-#include "dns/DnsTlsSocketFactory.h"
-#include "dns/IDnsTlsSocketFactory.h"
-#include "dns/DnsTlsTransport.h"
+#include "DnsTlsServer.h"
+#include "DnsTlsTransport.h"
+#include "IDnsTlsSocketFactory.h"
 
 namespace android {
 namespace net {
 
-using netdutils::Slice;
-
 // This is a singleton class that manages the collection of active DnsTlsTransports.
 // Queries made here are dispatched to an existing or newly constructed DnsTlsTransport.
 class DnsTlsDispatcher {
-public:
+  public:
     // Default constructor.
-    DnsTlsDispatcher() {
-        mFactory.reset(new DnsTlsSocketFactory());
-    }
+    DnsTlsDispatcher();
+
     // Constructor with dependency injection for testing.
-    DnsTlsDispatcher(std::unique_ptr<IDnsTlsSocketFactory> factory) :
-            mFactory(std::move(factory)) {}
+    explicit DnsTlsDispatcher(std::unique_ptr<IDnsTlsSocketFactory> factory)
+        : mFactory(std::move(factory)) {}
 
     // Enqueues |query| for resolution via the given |tlsServers| on the
     // network indicated by |mark|; writes the response into |ans|, and stores
     // the count of bytes written in |resplen|. Returns a success or error code.
     // The order in which servers from |tlsServers| are queried may not be the
     // order passed in by the caller.
-    DnsTlsTransport::Response query(const std::list<DnsTlsServer> &tlsServers, unsigned mark,
-                                    const Slice query, const Slice ans, int * _Nonnull resplen);
+    DnsTlsTransport::Response query(const std::list<DnsTlsServer>& tlsServers, unsigned mark,
+                                    const netdutils::Slice query, const netdutils::Slice ans,
+                                    int* _Nonnull resplen);
 
     // Given a |query|, sends it to the server on the network indicated by |mark|,
     // and writes the response into |ans|,  and indicates
     // the number of bytes written in |resplen|.  Returns a success or error code.
     DnsTlsTransport::Response query(const DnsTlsServer& server, unsigned mark,
-                                    const Slice query, const Slice ans, int * _Nonnull resplen);
+                                    const netdutils::Slice query, const netdutils::Slice ans,
+                                    int* _Nonnull resplen);
 
-private:
+  private:
     // This lock is static so that it can be used to annotate the Transport struct.
     // DnsTlsDispatcher is a singleton in practice, so making this static does not change
     // the locking behavior.
@@ -75,9 +71,8 @@
     // Transport is a thin wrapper around DnsTlsTransport, adding reference counting and
     // usage monitoring so we can expire idle sessions from the cache.
     struct Transport {
-        Transport(const DnsTlsServer& server, unsigned mark,
-                  IDnsTlsSocketFactory* _Nonnull factory) :
-                transport(server, mark, factory) {}
+        Transport(const DnsTlsServer& server, unsigned mark, IDnsTlsSocketFactory* _Nonnull factory)
+            : transport(server, mark, factory) {}
         // DnsTlsTransport is thread-safe, so it doesn't need to be guarded.
         DnsTlsTransport transport;
         // This use counter and timestamp are used to ensure that only idle sessions are
@@ -101,8 +96,8 @@
     void cleanup(std::chrono::time_point<std::chrono::steady_clock> now) REQUIRES(sLock);
 
     // Return a sorted list of DnsTlsServers in preference order.
-    std::list<DnsTlsServer> getOrderedServerList(
-            const std::list<DnsTlsServer> &tlsServers, unsigned mark) const;
+    std::list<DnsTlsServer> getOrderedServerList(const std::list<DnsTlsServer>& tlsServers,
+                                                 unsigned mark) const;
 
     // Trivial factory for DnsTlsSockets.  Dependency injection is only used for testing.
     std::unique_ptr<IDnsTlsSocketFactory> mFactory;
diff --git a/server/dns/DnsTlsQueryMap.cpp b/resolv/DnsTlsQueryMap.cpp
similarity index 90%
rename from server/dns/DnsTlsQueryMap.cpp
rename to resolv/DnsTlsQueryMap.cpp
index 760b26a..6e6399c 100644
--- a/server/dns/DnsTlsQueryMap.cpp
+++ b/resolv/DnsTlsQueryMap.cpp
@@ -17,15 +17,16 @@
 #define LOG_TAG "DnsTlsQueryMap"
 //#define LOG_NDEBUG 0
 
-#include "dns/DnsTlsTransport.h"
+#include "DnsTlsQueryMap.h"
 
 #include "log/log.h"
 
 namespace android {
 namespace net {
 
-std::unique_ptr<DnsTlsQueryMap::QueryFuture> DnsTlsQueryMap::recordQuery(const Slice query) {
-    std::lock_guard<std::mutex> guard(mLock);
+std::unique_ptr<DnsTlsQueryMap::QueryFuture> DnsTlsQueryMap::recordQuery(
+        const netdutils::Slice query) {
+    std::lock_guard guard(mLock);
 
     // Store the query so it can be matched to the response or reissued.
     if (query.size() < 2) {
@@ -54,7 +55,7 @@
 }
 
 void DnsTlsQueryMap::markTried(uint16_t newId) {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     auto it = mQueries.find(newId);
     if (it != mQueries.end()) {
         it->second.tries++;
@@ -62,7 +63,7 @@
 }
 
 void DnsTlsQueryMap::cleanup() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     for (auto it = mQueries.begin(); it != mQueries.end();) {
         auto& p = it->second;
         if (p.tries >= kMaxTries) {
@@ -101,7 +102,7 @@
 }
 
 std::vector<DnsTlsQueryMap::Query> DnsTlsQueryMap::getAll() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     std::vector<DnsTlsQueryMap::Query> queries;
     for (auto& q : mQueries) {
         queries.push_back(q.second.query);
@@ -110,12 +111,12 @@
 }
 
 bool DnsTlsQueryMap::empty() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     return mQueries.empty();
 }
 
 void DnsTlsQueryMap::clear() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     for (auto& q : mQueries) {
         expire(&q.second);
     }
@@ -129,7 +130,7 @@
         return;
     }
     uint16_t id = response[0] << 8 | response[1];
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     auto it = mQueries.find(id);
     if (it == mQueries.end()) {
         ALOGW("Discarding response: unknown ID %d", id);
diff --git a/server/dns/DnsTlsQueryMap.h b/resolv/DnsTlsQueryMap.h
similarity index 91%
rename from server/dns/DnsTlsQueryMap.h
rename to resolv/DnsTlsQueryMap.h
index 4e933c4..c5ab023 100644
--- a/server/dns/DnsTlsQueryMap.h
+++ b/resolv/DnsTlsQueryMap.h
@@ -25,30 +25,28 @@
 #include <android-base/thread_annotations.h>
 #include <netdutils/Slice.h>
 
-#include "dns/DnsTlsServer.h"
+#include "DnsTlsServer.h"
 
 namespace android {
 namespace net {
 
-using netdutils::Slice;
-
 // Keeps track of queries and responses.  This class matches responses with queries.
 // All methods are thread-safe and non-blocking.
 class DnsTlsQueryMap {
-public:
+  public:
     struct Query {
         // The new ID number assigned to this query.
         uint16_t newId;
         // A query that has been passed to recordQuery(), with its original ID number.
-        const Slice query;
+        const netdutils::Slice query;
     };
 
     typedef DnsTlsServer::Response Response;
     typedef DnsTlsServer::Result Result;
 
     struct QueryFuture {
-        QueryFuture(Query query, std::future<Result> result) :
-                query(query), result(std::move(result)) {}
+        QueryFuture(Query query, std::future<Result> result)
+            : query(query), result(std::move(result)) {}
         Query query;
         // A future which will resolve to the result of this query.
         std::future<Result> result;
@@ -56,7 +54,7 @@
 
     // Returns an object containing everything needed to complete processing of
     // this query, or null if the query could not be recorded.
-    std::unique_ptr<QueryFuture> recordQuery(const Slice query);
+    std::unique_ptr<QueryFuture> recordQuery(const netdutils::Slice query);
 
     // Process a response, including a new ID.  If the response
     // is not recognized as matching any query, it will be ignored.
@@ -76,7 +74,7 @@
     // Returns true if there are no pending queries.
     bool empty();
 
-private:
+  private:
     std::mutex mLock;
 
     struct QueryPromise {
diff --git a/server/dns/DnsTlsServer.cpp b/resolv/DnsTlsServer.cpp
similarity index 98%
rename from server/dns/DnsTlsServer.cpp
rename to resolv/DnsTlsServer.cpp
index dbb38df..a97c672 100644
--- a/server/dns/DnsTlsServer.cpp
+++ b/resolv/DnsTlsServer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "dns/DnsTlsServer.h"
+#include "DnsTlsServer.h"
 
 #include <algorithm>
 
diff --git a/server/dns/DnsTlsServer.h b/resolv/DnsTlsServer.h
similarity index 91%
rename from server/dns/DnsTlsServer.h
rename to resolv/DnsTlsServer.h
index 1c1cffe..7fc4a35 100644
--- a/server/dns/DnsTlsServer.h
+++ b/resolv/DnsTlsServer.h
@@ -23,6 +23,8 @@
 
 #include <netinet/in.h>
 
+#include <netd_resolv/params.h>
+
 namespace android {
 namespace net {
 
@@ -59,15 +61,15 @@
     int protocol = IPPROTO_TCP;
 
     // Exact comparison of DnsTlsServer objects
-    bool operator <(const DnsTlsServer& other) const;
-    bool operator ==(const DnsTlsServer& other) const;
+    bool operator<(const DnsTlsServer& other) const;
+    bool operator==(const DnsTlsServer& other) const;
 
     bool wasExplicitlyConfigured() const;
 };
 
 // This comparison only checks the IP address.  It ignores ports, names, and fingerprints.
 struct AddressComparator {
-    bool operator() (const DnsTlsServer& x, const DnsTlsServer& y) const;
+    bool operator()(const DnsTlsServer& x, const DnsTlsServer& y) const;
 };
 
 }  // namespace net
diff --git a/server/dns/DnsTlsSessionCache.cpp b/resolv/DnsTlsSessionCache.cpp
similarity index 94%
rename from server/dns/DnsTlsSessionCache.cpp
rename to resolv/DnsTlsSessionCache.cpp
index 880b773..8d6dc6b 100644
--- a/server/dns/DnsTlsSessionCache.cpp
+++ b/resolv/DnsTlsSessionCache.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
- #include "DnsTlsSessionCache.h"
+#include "DnsTlsSessionCache.h"
 
 #define LOG_TAG "DnsTlsSessionCache"
 //#define LOG_NDEBUG 0
@@ -54,7 +54,7 @@
 }
 
 void DnsTlsSessionCache::recordSession(SSL_SESSION* session) {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     mSessions.emplace_front(session);
     if (mSessions.size() > kMaxSize) {
         ALOGV("Too many sessions; trimming");
@@ -63,7 +63,7 @@
 }
 
 bssl::UniquePtr<SSL_SESSION> DnsTlsSessionCache::getSession() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     if (mSessions.size() == 0) {
         ALOGV("No known sessions");
         return nullptr;
diff --git a/server/dns/DnsTlsSessionCache.h b/resolv/DnsTlsSessionCache.h
similarity index 95%
rename from server/dns/DnsTlsSessionCache.h
rename to resolv/DnsTlsSessionCache.h
index 32b8b55..e1a88cf 100644
--- a/server/dns/DnsTlsSessionCache.h
+++ b/resolv/DnsTlsSessionCache.h
@@ -17,15 +17,12 @@
 #ifndef _DNS_DNSTLSSESSIONCACHE_H
 #define _DNS_DNSTLSSESSIONCACHE_H
 
-#include <mutex>
 #include <deque>
+#include <mutex>
 
 #include <openssl/ssl.h>
 
 #include <android-base/thread_annotations.h>
-#include <android-base/unique_fd.h>
-
-#include "dns/DnsTlsServer.h"
 
 namespace android {
 namespace net {
@@ -33,7 +30,7 @@
 // Cache of recently seen SSL_SESSIONs.  This is used to support session tickets.
 // This class is thread-safe.
 class DnsTlsSessionCache {
-public:
+  public:
     // Prepare SSL objects to use this session cache.  These methods must be called
     // before making use of either object.
     void prepareSslContext(SSL_CTX* _Nonnull ssl_ctx);
@@ -46,7 +43,7 @@
     // pointer.)
     bssl::UniquePtr<SSL_SESSION> getSession() EXCLUDES(mLock);
 
-private:
+  private:
     static constexpr size_t kMaxSize = 5;
     static int newSessionCallback(SSL* _Nullable ssl, SSL_SESSION* _Nullable session);
 
diff --git a/server/dns/DnsTlsSocket.cpp b/resolv/DnsTlsSocket.cpp
similarity index 82%
rename from server/dns/DnsTlsSocket.cpp
rename to resolv/DnsTlsSocket.cpp
index ca1cdc9..a63d221 100644
--- a/server/dns/DnsTlsSocket.cpp
+++ b/resolv/DnsTlsSocket.cpp
@@ -17,38 +17,37 @@
 #define LOG_TAG "DnsTlsSocket"
 //#define LOG_NDEBUG 0
 
-#include "dns/DnsTlsSocket.h"
+#include "DnsTlsSocket.h"
 
-#include <algorithm>
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 #include <errno.h>
 #include <linux/tcp.h>
 #include <openssl/err.h>
+#include <openssl/sha.h>
+#include <sys/eventfd.h>
 #include <sys/poll.h>
+#include <algorithm>
 
-#include "dns/DnsTlsSessionCache.h"
-#include "dns/IDnsTlsSocketObserver.h"
+#include "DnsTlsSessionCache.h"
+#include "IDnsTlsSocketObserver.h"
 
 #include "log/log.h"
 #include "netdutils/SocketOption.h"
-#include "Fwmark.h"
-#undef ADD  // already defined in nameser.h
-#include "NetdConstants.h"
-#include "Permission.h"
-
 
 namespace android {
 
 using netdutils::enableSockopt;
 using netdutils::enableTcpKeepAlives;
 using netdutils::isOk;
+using netdutils::Slice;
 using netdutils::Status;
 
 namespace net {
 namespace {
 
 constexpr const char kCaCertDir[] = "/system/etc/security/cacerts";
+constexpr size_t SHA256_SIZE = SHA256_DIGEST_LENGTH;
 
 int waitForReading(int fd) {
     struct pollfd fds = { .fd = fd, .events = POLLIN };
@@ -94,7 +93,7 @@
     }
 
     // Send 5 keepalives, 3 seconds apart, after 15 seconds of inactivity.
-    enableTcpKeepAlives(mSslFd.get(), 15U, 5U, 3U);
+    enableTcpKeepAlives(mSslFd.get(), 15U, 5U, 3U).ignoreError();
 
     if (connect(mSslFd.get(), reinterpret_cast<const struct sockaddr *>(&mServer.ss),
                 sizeof(mServer.ss)) != 0 &&
@@ -108,7 +107,7 @@
 }
 
 bool getSPKIDigest(const X509* cert, std::vector<uint8_t>* out) {
-    int spki_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
+    int spki_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), nullptr);
     unsigned char spki[spki_len];
     unsigned char* temp = spki;
     if (spki_len != i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp)) {
@@ -117,7 +116,7 @@
     }
     out->resize(SHA256_SIZE);
     unsigned int digest_len = 0;
-    int ret = EVP_Digest(spki, spki_len, out->data(), &digest_len, EVP_sha256(), NULL);
+    int ret = EVP_Digest(spki, spki_len, out->data(), &digest_len, EVP_sha256(), nullptr);
     if (ret != 1) {
         ALOGW("Server cert digest extraction failed");
         return false;
@@ -132,7 +131,7 @@
 bool DnsTlsSocket::initialize() {
     // This method should only be called once, at the beginning, so locking should be
     // unnecessary.  This lock only serves to help catch bugs in code that calls this method.
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     if (mSslCtx) {
         // This is a bug in the caller.
         return false;
@@ -166,14 +165,8 @@
     if (!mSsl) {
         return false;
     }
-    int sv[2];
-    if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, sv)) {
-        return false;
-    }
-    // The two sockets are perfectly symmetrical, so the choice of which one is
-    // "in" and which one is "out" is arbitrary.
-    mIpcInFd.reset(sv[0]);
-    mIpcOutFd.reset(sv[1]);
+
+    mEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
 
     // Start the I/O loop.
     mLoopThread.reset(new std::thread(&DnsTlsSocket::loop, this));
@@ -340,30 +333,31 @@
 }
 
 void DnsTlsSocket::loop() {
-    std::lock_guard<std::mutex> guard(mLock);
-    // Buffer at most one query.
-    Query q;
+    std::lock_guard guard(mLock);
+    std::deque<std::vector<uint8_t>> q;
 
     const int timeout_msecs = DnsTlsSocket::kIdleTimeout.count() * 1000;
     while (true) {
         // poll() ignores negative fds
         struct pollfd fds[2] = { { .fd = -1 }, { .fd = -1 } };
-        enum { SSLFD = 0, IPCFD = 1 };
+        enum { SSLFD = 0, EVENTFD = 1 };
 
         // Always listen for a response from server.
         fds[SSLFD].fd = mSslFd.get();
         fds[SSLFD].events = POLLIN;
 
-        // If we have a pending query, also wait for space
-        // to write it, otherwise listen for a new query.
-        if (!q.query.empty()) {
+        // If we have pending queries, wait for space to write one.
+        // Otherwise, listen for new queries.
+        // Note: This blocks the destructor until q is empty, i.e. until all pending
+        // queries are sent or have failed to send.
+        if (!q.empty()) {
             fds[SSLFD].events |= POLLOUT;
         } else {
-            fds[IPCFD].fd = mIpcOutFd.get();
-            fds[IPCFD].events = POLLIN;
+            fds[EVENTFD].fd = mEventFd.get();
+            fds[EVENTFD].events = POLLIN;
         }
 
-        const int s = TEMP_FAILURE_RETRY(poll(fds, ARRAY_SIZE(fds), timeout_msecs));
+        const int s = TEMP_FAILURE_RETRY(poll(fds, std::size(fds), timeout_msecs));
         if (s == 0) {
             ALOGV("Idle timeout");
             break;
@@ -372,34 +366,42 @@
             ALOGV("Poll failed: %d", errno);
             break;
         }
-        if (fds[SSLFD].revents & (POLLIN | POLLERR)) {
+        if (fds[SSLFD].revents & (POLLIN | POLLERR | POLLHUP)) {
             if (!readResponse()) {
                 ALOGV("SSL remote close or read error.");
                 break;
             }
         }
-        if (fds[IPCFD].revents & (POLLIN | POLLERR)) {
-            int res = read(mIpcOutFd.get(), &q, sizeof(q));
+        if (fds[EVENTFD].revents & (POLLIN | POLLERR)) {
+            int64_t num_queries;
+            ssize_t res = read(mEventFd.get(), &num_queries, sizeof(num_queries));
             if (res < 0) {
-                ALOGW("Error during IPC read");
+                ALOGW("Error during eventfd read");
                 break;
             } else if (res == 0) {
-                ALOGV("IPC channel closed; disconnecting");
+                ALOGW("eventfd closed; disconnecting");
                 break;
-            } else if (res != sizeof(q)) {
-                ALOGE("Struct size mismatch: %d != %zu", res, sizeof(q));
+            } else if (res != sizeof(num_queries)) {
+                ALOGE("Int size mismatch: %zd != %zu", res, sizeof(num_queries));
+                break;
+            } else if (num_queries < 0) {
+                ALOGV("Negative eventfd read indicates destructor-initiated shutdown");
                 break;
             }
+            // Take ownership of all pending queries.  (q is always empty here.)
+            mQueue.swap(q);
         } else if (fds[SSLFD].revents & POLLOUT) {
-            // query cannot be null here.
-            if (!sendQuery(q)) {
+            // q cannot be empty here.
+            // Sending the entire queue here would risk a TCP flow control deadlock, so
+            // we only send a single query on each cycle of this loop.
+            // TODO: Coalesce multiple pending queries if there is enough space in the
+            // write buffer.
+            if (!sendQuery(q.front())) {
                 break;
             }
-            q = Query();  // Reset q to empty
+            q.pop_front();
         }
     }
-    ALOGV("Closing IPC read FD");
-    mIpcOutFd.reset();
     ALOGV("Disconnecting");
     sslDisconnect();
     ALOGV("Calling onClosed");
@@ -410,10 +412,10 @@
 DnsTlsSocket::~DnsTlsSocket() {
     ALOGV("Destructor");
     // This will trigger an orderly shutdown in loop().
-    mIpcInFd.reset();
+    requestLoopShutdown();
     {
         // Wait for the orderly shutdown to complete.
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         if (mLoopThread && std::this_thread::get_id() == mLoopThread->get_id()) {
             ALOGE("Violation of re-entrance precondition");
             return;
@@ -428,12 +430,42 @@
 }
 
 bool DnsTlsSocket::query(uint16_t id, const Slice query) {
-    const Query q = { .id = id, .query = query };
-    if (!mIpcInFd) {
+    // Compose the entire message in a single buffer, so that it can be
+    // sent as a single TLS record.
+    std::vector<uint8_t> buf(query.size() + 4);
+    // Write 2-byte length
+    uint16_t len = query.size() + 2;  // + 2 for the ID.
+    buf[0] = len >> 8;
+    buf[1] = len;
+    // Write 2-byte ID
+    buf[2] = id >> 8;
+    buf[3] = id;
+    // Copy body
+    std::memcpy(buf.data() + 4, query.base(), query.size());
+
+    mQueue.push(std::move(buf));
+    // Increment the mEventFd counter by 1.
+    return incrementEventFd(1);
+}
+
+void DnsTlsSocket::requestLoopShutdown() {
+    if (mEventFd != -1) {
+        // Write a negative number to the eventfd.  This triggers an immediate shutdown.
+        incrementEventFd(INT64_MIN);
+    }
+}
+
+bool DnsTlsSocket::incrementEventFd(const int64_t count) {
+    if (mEventFd == -1) {
+        ALOGE("eventfd is not initialized");
         return false;
     }
-    int written = write(mIpcInFd.get(), &q, sizeof(q));
-    return written == sizeof(q);
+    ssize_t written = write(mEventFd.get(), &count, sizeof(count));
+    if (written != sizeof(count)) {
+        ALOGE("Failed to increment eventfd by %" PRId64, count);
+        return false;
+    }
+    return true;
 }
 
 // Read exactly len bytes into buffer or fail with an SSL error code
@@ -467,20 +499,7 @@
     return SSL_ERROR_NONE;
 }
 
-bool DnsTlsSocket::sendQuery(const Query& q) {
-    ALOGV("sending query");
-    // Compose the entire message in a single buffer, so that it can be
-    // sent as a single TLS record.
-    std::vector<uint8_t> buf(q.query.size() + 4);
-    // Write 2-byte length
-    uint16_t len = q.query.size() + 2; // + 2 for the ID.
-    buf[0] = len >> 8;
-    buf[1] = len;
-    // Write 2-byte ID
-    buf[2] = q.id >> 8;
-    buf[3] = q.id;
-    // Copy body
-    std::memcpy(buf.data() + 4, q.query.base(), q.query.size());
+bool DnsTlsSocket::sendQuery(const std::vector<uint8_t>& buf) {
     if (!sslWrite(netdutils::makeSlice(buf))) {
         return false;
     }
diff --git a/server/dns/DnsTlsSocket.h b/resolv/DnsTlsSocket.h
similarity index 68%
rename from server/dns/DnsTlsSocket.h
rename to resolv/DnsTlsSocket.h
index 0c37a52..2940500 100644
--- a/server/dns/DnsTlsSocket.h
+++ b/resolv/DnsTlsSocket.h
@@ -17,17 +17,18 @@
 #ifndef _DNS_DNSTLSSOCKET_H
 #define _DNS_DNSTLSSOCKET_H
 
+#include <openssl/ssl.h>
 #include <future>
 #include <mutex>
-#include <openssl/ssl.h>
 
 #include <android-base/thread_annotations.h>
 #include <android-base/unique_fd.h>
 #include <netdutils/Slice.h>
 #include <netdutils/Status.h>
 
-#include "dns/DnsTlsServer.h"
-#include "dns/IDnsTlsSocket.h"
+#include "DnsTlsServer.h"
+#include "IDnsTlsSocket.h"
+#include "LockedQueue.h"
 
 namespace android {
 namespace net {
@@ -35,8 +36,6 @@
 class IDnsTlsSocketObserver;
 class DnsTlsSessionCache;
 
-using netdutils::Slice;
-
 // A class for managing a TLS socket that sends and receives messages in
 // [length][value] format, with a 2-byte length (i.e. DNS-over-TCP format).
 // This class is not aware of query-response pairing or anything else about DNS.
@@ -46,11 +45,10 @@
 // This class may call the observer at any time after initialize(), until the destructor
 // returns (but not after).
 class DnsTlsSocket : public IDnsTlsSocket {
-public:
+  public:
     DnsTlsSocket(const DnsTlsServer& server, unsigned mark,
-                 IDnsTlsSocketObserver* _Nonnull observer,
-                 DnsTlsSessionCache* _Nonnull cache) :
-            mMark(mark), mServer(server), mObserver(observer), mCache(cache) {}
+                 IDnsTlsSocketObserver* _Nonnull observer, DnsTlsSessionCache* _Nonnull cache)
+        : mMark(mark), mServer(server), mObserver(observer), mCache(cache) {}
     ~DnsTlsSocket();
 
     // Creates the SSL context for this session and connect.  Returns false on failure.
@@ -64,9 +62,9 @@
     // notified that the socket is closed.
     // Note that success here indicates successful sending, not receipt of a response.
     // Thread-safe.
-    bool query(uint16_t id, const Slice query) override;
+    bool query(uint16_t id, const netdutils::Slice query) override EXCLUDES(mLock);
 
-private:
+  private:
     // Lock to be held by the SSL event loop thread.  This is not normally in contention.
     std::mutex mLock;
 
@@ -87,28 +85,38 @@
     void sslDisconnect() REQUIRES(mLock);
 
     // Writes a buffer to the socket.
-    bool sslWrite(const Slice buffer) REQUIRES(mLock);
+    bool sslWrite(const netdutils::Slice buffer) REQUIRES(mLock);
 
     // Reads exactly the specified number of bytes from the socket, or fails.
     // Returns SSL_ERROR_NONE on success.
     // If |wait| is true, then this function always blocks.  Otherwise, it
     // will return SSL_ERROR_WANT_READ if there is no data from the server to read.
-    int sslRead(const Slice buffer, bool wait) REQUIRES(mLock);
+    int sslRead(const netdutils::Slice buffer, bool wait) REQUIRES(mLock);
 
-    struct Query {
-        uint16_t id;
-        Slice query;
-    };
-
-    bool sendQuery(const Query& q) REQUIRES(mLock);
+    bool sendQuery(const std::vector<uint8_t>& buf) REQUIRES(mLock);
     bool readResponse() REQUIRES(mLock);
 
-    // SOCK_SEQPACKET socket pair used for sending queries from myriad query
-    // threads to the SSL thread.  EOF indicates a close request.
-    // We have to use a socket pair (i.e. a pipe) because the SSL thread needs to wait in
-    // select() for input from either a remote server or a query thread.
-    base::unique_fd mIpcInFd;
-    base::unique_fd mIpcOutFd GUARDED_BY(mLock);
+    // Similar to query(), this function uses incrementEventFd to send a message to the
+    // loop thread.  However, instead of incrementing the counter by one (indicating a
+    // new query), it wraps the counter to negative, which we use to indicate a shutdown
+    // request.
+    void requestLoopShutdown() EXCLUDES(mLock);
+
+    // This function sends a message to the loop thread by incrementing mEventFd.
+    bool incrementEventFd(int64_t count) EXCLUDES(mLock);
+
+    // Queue of pending queries.  query() pushes items onto the queue and notifies
+    // the loop thread by incrementing mEventFd.  loop() reads items off the queue.
+    LockedQueue<std::vector<uint8_t>> mQueue;
+
+    // eventfd socket used for notifying the SSL thread when queries are ready to send.
+    // This socket acts similarly to an atomic counter, incremented by query() and cleared
+    // by loop().  We have to use a socket because the SSL thread needs to wait in poll()
+    // for input from either a remote server or a query thread.  Since eventfd does not have
+    // EOF, we indicate a close request by setting the counter to a negative number.
+    // This file descriptor is opened by initialize(), and closed implicitly after
+    // destruction.
+    base::unique_fd mEventFd;
 
     // SSL Socket fields.
     bssl::UniquePtr<SSL_CTX> mSslCtx GUARDED_BY(mLock);
diff --git a/server/dns/DnsTlsSocketFactory.h b/resolv/DnsTlsSocketFactory.h
similarity index 83%
rename from server/dns/DnsTlsSocketFactory.h
rename to resolv/DnsTlsSocketFactory.h
index 68c35cc..af4011d 100644
--- a/server/dns/DnsTlsSocketFactory.h
+++ b/resolv/DnsTlsSocketFactory.h
@@ -19,8 +19,8 @@
 
 #include <memory>
 
-#include "dns/DnsTlsSocket.h"
-#include "dns/IDnsTlsSocketFactory.h"
+#include "DnsTlsSocket.h"
+#include "IDnsTlsSocketFactory.h"
 
 namespace android {
 namespace net {
@@ -31,10 +31,10 @@
 
 // Trivial RAII factory for DnsTlsSocket.  This is owned by DnsTlsDispatcher.
 class DnsTlsSocketFactory : public IDnsTlsSocketFactory {
-public:
+  public:
     std::unique_ptr<IDnsTlsSocket> createDnsTlsSocket(const DnsTlsServer& server, unsigned mark,
-                                                     IDnsTlsSocketObserver* _Nonnull observer,
-                                                     DnsTlsSessionCache* _Nonnull cache) override {
+                                                      IDnsTlsSocketObserver* _Nonnull observer,
+                                                      DnsTlsSessionCache* _Nonnull cache) override {
         auto socket = std::make_unique<DnsTlsSocket>(server, mark, observer, cache);
         if (!socket->initialize()) {
             return nullptr;
diff --git a/server/dns/DnsTlsTransport.cpp b/resolv/DnsTlsTransport.cpp
similarity index 86%
rename from server/dns/DnsTlsTransport.cpp
rename to resolv/DnsTlsTransport.cpp
index 033ae80..0f9042e 100644
--- a/server/dns/DnsTlsTransport.cpp
+++ b/resolv/DnsTlsTransport.cpp
@@ -17,26 +17,21 @@
 #define LOG_TAG "DnsTlsTransport"
 //#define LOG_NDEBUG 0
 
-#include "dns/DnsTlsTransport.h"
+#include "DnsTlsTransport.h"
 
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 
-#include "dns/DnsTlsServer.h"
-#include "dns/DnsTlsSocketFactory.h"
-#include "dns/IDnsTlsSocketFactory.h"
+#include "DnsTlsSocketFactory.h"
+#include "IDnsTlsSocketFactory.h"
 
 #include "log/log.h"
-#include "Fwmark.h"
-#undef ADD  // already defined in nameser.h
-#include "NetdConstants.h"
-#include "Permission.h"
 
 namespace android {
 namespace net {
 
 std::future<DnsTlsTransport::Result> DnsTlsTransport::query(const netdutils::Slice query) {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
 
     auto record = mQueries.recordQuery(query);
     if (!record) {
@@ -89,7 +84,7 @@
 }
 
 void DnsTlsTransport::onClosed() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     if (mClosing) {
         return;
     }
@@ -109,7 +104,7 @@
 }
 
 void DnsTlsTransport::doReconnect() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     if (mClosing) {
         return;
     }
@@ -126,7 +121,7 @@
 DnsTlsTransport::~DnsTlsTransport() {
     ALOGV("Destructor");
     {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         ALOGV("Locked destruction procedure");
         mQueries.clear();
         mClosing = true;
@@ -148,7 +143,7 @@
 // static
 // TODO: Use this function to preheat the session cache.
 // That may require moving it to DnsTlsDispatcher.
-bool DnsTlsTransport::validate(const DnsTlsServer& server, unsigned netid) {
+bool DnsTlsTransport::validate(const DnsTlsServer& server, unsigned netid, uint32_t mark) {
     ALOGV("Beginning validation on %u", netid);
     // Generate "<random>-dnsotls-ds.metric.gstatic.com", which we will lookup through |ss| in
     // order to prove that it is actually a working DNS over TLS server.
@@ -157,10 +152,10 @@
             "ABCDEFHIJKLMNOPQRSTUVWXYZ"
             "0123456789";
     const auto c = [](uint8_t rnd) -> uint8_t {
-        return kDnsSafeChars[(rnd % ARRAY_SIZE(kDnsSafeChars))];
+        return kDnsSafeChars[(rnd % std::size(kDnsSafeChars))];
     };
     uint8_t rnd[8];
-    arc4random_buf(rnd, ARRAY_SIZE(rnd));
+    arc4random_buf(rnd, std::size(rnd));
     // We could try to use res_mkquery() here, but it's basically the same.
     uint8_t query[] = {
         rnd[6], rnd[7],  // [0-1]   query ID
@@ -178,20 +173,12 @@
         0, ns_t_aaaa,  // QTYPE
         0, ns_c_in     // QCLASS
     };
-    const int qlen = ARRAY_SIZE(query);
+    const int qlen = std::size(query);
 
-    // At validation time, we only know the netId, so we have to guess/compute the
-    // corresponding socket mark.
-    Fwmark fwmark;
-    fwmark.permission = PERMISSION_SYSTEM;
-    fwmark.explicitlySelected = true;
-    fwmark.protectedFromVpn = true;
-    fwmark.netId = netid;
-    unsigned mark = fwmark.intValue;
     int replylen = 0;
     DnsTlsSocketFactory factory;
     DnsTlsTransport transport(server, mark, &factory);
-    auto r = transport.query(Slice(query, qlen)).get();
+    auto r = transport.query(netdutils::Slice(query, qlen)).get();
     if (r.code != Response::success) {
         ALOGV("query failed");
         return false;
diff --git a/server/dns/DnsTlsTransport.h b/resolv/DnsTlsTransport.h
similarity index 89%
rename from server/dns/DnsTlsTransport.h
rename to resolv/DnsTlsTransport.h
index b55cf35..6c98fa6 100644
--- a/server/dns/DnsTlsTransport.h
+++ b/resolv/DnsTlsTransport.h
@@ -24,15 +24,14 @@
 
 #include <android-base/thread_annotations.h>
 #include <android-base/unique_fd.h>
-
-#include "dns/DnsTlsSessionCache.h"
-#include "dns/DnsTlsQueryMap.h"
-#include "dns/DnsTlsServer.h"
-#include "dns/IDnsTlsSocket.h"
-#include "dns/IDnsTlsSocketObserver.h"
-
 #include <netdutils/Slice.h>
 
+#include "DnsTlsQueryMap.h"
+#include "DnsTlsServer.h"
+#include "DnsTlsSessionCache.h"
+#include "IDnsTlsSocket.h"
+#include "IDnsTlsSocketObserver.h"
+
 namespace android {
 namespace net {
 
@@ -41,10 +40,10 @@
 // Manages at most one DnsTlsSocket at a time.  This class handles socket lifetime issues,
 // such as reopening the socket and reissuing pending queries.
 class DnsTlsTransport : public IDnsTlsSocketObserver {
-public:
+  public:
     DnsTlsTransport(const DnsTlsServer& server, unsigned mark,
-                    IDnsTlsSocketFactory* _Nonnull factory) :
-            mMark(mark), mServer(server), mFactory(factory) {}
+                    IDnsTlsSocketFactory* _Nonnull factory)
+        : mMark(mark), mServer(server), mFactory(factory) {}
     ~DnsTlsTransport();
 
     typedef DnsTlsServer::Response Response;
@@ -56,13 +55,13 @@
     // Check that a given TLS server is fully working on the specified netid, and has the
     // provided SHA-256 fingerprint (if nonempty).  This function is used in ResolverController
     // to ensure that we don't enable DNS over TLS on networks where it doesn't actually work.
-    static bool validate(const DnsTlsServer& server, unsigned netid);
+    static bool validate(const DnsTlsServer& server, unsigned netid, uint32_t mark);
 
     // Implement IDnsTlsSocketObserver
     void onResponse(std::vector<uint8_t> response) override;
     void onClosed() override EXCLUDES(mLock);
 
-private:
+  private:
     std::mutex mLock;
 
     DnsTlsSessionCache mCache;
diff --git a/server/dns/IDnsTlsSocket.h b/resolv/IDnsTlsSocket.h
similarity index 97%
rename from server/dns/IDnsTlsSocket.h
rename to resolv/IDnsTlsSocket.h
index 0f988d6..0f2800e 100644
--- a/server/dns/IDnsTlsSocket.h
+++ b/resolv/IDnsTlsSocket.h
@@ -17,8 +17,8 @@
 #ifndef _DNS_IDNSTLSSOCKET_H
 #define _DNS_IDNSTLSSOCKET_H
 
-#include <cstdint>
 #include <cstddef>
+#include <cstdint>
 
 #include <netdutils/Slice.h>
 
@@ -32,8 +32,8 @@
 // [length][value] format, with a 2-byte length (i.e. DNS-over-TCP format).
 // This interface is not aware of query-response pairing or anything else about DNS.
 class IDnsTlsSocket {
-public:
-    virtual ~IDnsTlsSocket() {};
+  public:
+    virtual ~IDnsTlsSocket(){};
     // Send a query on the provided SSL socket.  |query| contains
     // the body of a query, not including the ID bytes.  This function will typically return before
     // the query is actually sent.  If this function fails, the observer will be
diff --git a/server/dns/IDnsTlsSocketFactory.h b/resolv/IDnsTlsSocketFactory.h
similarity index 85%
rename from server/dns/IDnsTlsSocketFactory.h
rename to resolv/IDnsTlsSocketFactory.h
index a13723a..a391f59 100644
--- a/server/dns/IDnsTlsSocketFactory.h
+++ b/resolv/IDnsTlsSocketFactory.h
@@ -17,7 +17,7 @@
 #ifndef _DNS_IDNSTLSSOCKETFACTORY_H
 #define _DNS_IDNSTLSSOCKETFACTORY_H
 
-#include "dns/IDnsTlsSocket.h"
+#include "IDnsTlsSocket.h"
 
 namespace android {
 namespace net {
@@ -29,12 +29,10 @@
 // Dependency injection interface for DnsTlsSocketFactory.
 // This pattern allows mocking of DnsTlsSocket for tests.
 class IDnsTlsSocketFactory {
-public:
-    virtual ~IDnsTlsSocketFactory() {};
+  public:
+    virtual ~IDnsTlsSocketFactory(){};
     virtual std::unique_ptr<IDnsTlsSocket> createDnsTlsSocket(
-            const DnsTlsServer& server,
-            unsigned mark,
-            IDnsTlsSocketObserver* _Nonnull observer,
+            const DnsTlsServer& server, unsigned mark, IDnsTlsSocketObserver* _Nonnull observer,
             DnsTlsSessionCache* _Nonnull cache) = 0;
 };
 
diff --git a/server/dns/IDnsTlsSocketObserver.h b/resolv/IDnsTlsSocketObserver.h
similarity index 95%
rename from server/dns/IDnsTlsSocketObserver.h
rename to resolv/IDnsTlsSocketObserver.h
index 7ae364c..980f74a 100644
--- a/server/dns/IDnsTlsSocketObserver.h
+++ b/resolv/IDnsTlsSocketObserver.h
@@ -25,8 +25,8 @@
 // DnsTlsTransport, but it is a separate interface for clarity and to avoid a
 // circular dependency with DnsTlsSocket.
 class IDnsTlsSocketObserver {
-public:
-    virtual ~IDnsTlsSocketObserver() {};
+  public:
+    virtual ~IDnsTlsSocketObserver(){};
     virtual void onResponse(std::vector<uint8_t> response) = 0;
 
     virtual void onClosed() = 0;
diff --git a/resolv/LockedQueue.h b/resolv/LockedQueue.h
new file mode 100644
index 0000000..0481eda
--- /dev/null
+++ b/resolv/LockedQueue.h
@@ -0,0 +1,52 @@
+/*
+ * 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 _DNS_LOCKED_QUEUE_H
+#define _DNS_LOCKED_QUEUE_H
+
+#include <algorithm>
+#include <deque>
+#include <mutex>
+
+#include <android-base/thread_annotations.h>
+
+namespace android {
+namespace net {
+
+template <typename T>
+class LockedQueue {
+  public:
+    // Push an item onto the queue.
+    void push(T item) {
+        std::lock_guard guard(mLock);
+        mQueue.push_front(std::move(item));
+    }
+
+    // Swap out the contents of the queue
+    void swap(std::deque<T>& other) {
+        std::lock_guard guard(mLock);
+        mQueue.swap(other);
+    }
+
+  private:
+    std::mutex mLock;
+    std::deque<T> mQueue GUARDED_BY(mLock);
+};
+
+}  // end of namespace net
+}  // end of namespace android
+
+#endif  // _DNS_LOCKEDQUEUE_H
diff --git a/resolv/NOTICE b/resolv/NOTICE
new file mode 100644
index 0000000..7504f4d
--- /dev/null
+++ b/resolv/NOTICE
@@ -0,0 +1,418 @@
+Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the project nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (C) 2008 The Android Open Source Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ * Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (C) 2014 The Android Open Source Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ * Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+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.
+
+-------------------------------------------------------------------
+
+Copyright (c) 1983, 1987, 1989
+   The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 1985
+   The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. All advertising materials mentioning features or use of this software
+   must display the following acknowledgement:
+    This product includes software developed by the University of
+    California, Berkeley and its contributors.
+4. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 1985, 1988, 1993
+   The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Portions Copyright (c) 1993 by Digital Equipment Corporation.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies, and that
+the name of Digital Equipment Corporation not be used in advertising or
+publicity pertaining to distribution of the document or software without
+specific, written prior permission.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 1985, 1989, 1993
+   The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. All advertising materials mentioning features or use of this software
+   must display the following acknowledgement:
+    This product includes software developed by the University of
+    California, Berkeley and its contributors.
+4. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 1985, 1993
+   The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. All advertising materials mentioning features or use of this software
+   must display the following acknowledgement:
+    This product includes software developed by the University of
+    California, Berkeley and its contributors.
+4. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 1985, 1993
+   The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+Copyright (c) 1995-1999 by Internet Software Consortium.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+Copyright (c) 1999 by Internet Software Consortium.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-------------------------------------------------------------------
+
+Portions Copyright (C) 2004, 2005, 2008, 2009  Internet Systems Consortium, Inc. ("ISC")
+Portions Copyright (C) 1996-2003  Internet Software Consortium.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+-------------------------------------------------------------------
+
+Portions Copyright (c) 1993 by Digital Equipment Corporation.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies, and that
+the name of Digital Equipment Corporation not be used in advertising or
+publicity pertaining to distribution of the document or software without
+specific, written prior permission.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+-------------------------------------------------------------------
+
+Portions Copyright (c) 1995 by International Business Machines, Inc.
+
+International Business Machines, Inc. (hereinafter called IBM) grants
+permission under its copyrights to use, copy, modify, and distribute this
+Software with or without fee, provided that the above copyright notice and
+all paragraphs of this notice appear in all copies, and that the name of IBM
+not be used in connection with the marketing of any product incorporating
+the Software or modifications thereof, without specific, written prior
+permission.
+
+To the extent it has a right to do so, IBM grants an immunity from suit
+under its patents, if any, for the use, sale or manufacture of products to
+the extent that such products are used for performing Domain Name System
+dynamic updates in TCP/IP networks by means of the Software.  No immunity is
+granted for any product per se or for any other function of any product.
+
+THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE.  IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
+DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
+OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
+IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+-------------------------------------------------------------------
+
diff --git a/resolv/PrivateDnsConfiguration.cpp b/resolv/PrivateDnsConfiguration.cpp
new file mode 100644
index 0000000..1a88275
--- /dev/null
+++ b/resolv/PrivateDnsConfiguration.cpp
@@ -0,0 +1,315 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PrivateDnsConfiguration"
+#define DBG 0
+
+#include "PrivateDnsConfiguration.h"
+
+#include <log/log.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include "DnsTlsTransport.h"
+#include "ResolverEventReporter.h"
+#include "netd_resolv/resolv.h"
+#include "netdutils/BackoffSequence.h"
+
+namespace android {
+namespace net {
+
+std::string addrToString(const sockaddr_storage* addr) {
+    char out[INET6_ADDRSTRLEN] = {0};
+    getnameinfo((const sockaddr*) addr, sizeof(sockaddr_storage), out, INET6_ADDRSTRLEN, nullptr, 0,
+                NI_NUMERICHOST);
+    return std::string(out);
+}
+
+bool parseServer(const char* server, sockaddr_storage* parsed) {
+    addrinfo hints = {.ai_family = AF_UNSPEC, .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV};
+    addrinfo* res;
+
+    int err = getaddrinfo(server, "853", &hints, &res);
+    if (err != 0) {
+        ALOGW("Failed to parse server address (%s): %s", server, gai_strerror(err));
+        return false;
+    }
+
+    memcpy(parsed, res->ai_addr, res->ai_addrlen);
+    freeaddrinfo(res);
+    return true;
+}
+
+int PrivateDnsConfiguration::set(int32_t netId, uint32_t mark,
+                                 const std::vector<std::string>& servers, const std::string& name,
+                                 const std::set<std::vector<uint8_t>>& fingerprints) {
+    if (DBG) {
+        ALOGD("PrivateDnsConfiguration::set(%u, 0x%x, %zu, %s, %zu)", netId, mark, servers.size(),
+              name.c_str(), fingerprints.size());
+    }
+
+    const bool explicitlyConfigured = !name.empty() || !fingerprints.empty();
+
+    // Parse the list of servers that has been passed in
+    std::set<DnsTlsServer> tlsServers;
+    for (size_t i = 0; i < servers.size(); ++i) {
+        sockaddr_storage parsed;
+        if (!parseServer(servers[i].c_str(), &parsed)) {
+            return -EINVAL;
+        }
+        DnsTlsServer server(parsed);
+        server.name = name;
+        server.fingerprints = fingerprints;
+        tlsServers.insert(server);
+    }
+
+    std::lock_guard guard(mPrivateDnsLock);
+    if (explicitlyConfigured) {
+        mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
+    } else if (!tlsServers.empty()) {
+        mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
+    } else {
+        mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
+        mPrivateDnsTransports.erase(netId);
+        return 0;
+    }
+
+    // Create the tracker if it was not present
+    auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair == mPrivateDnsTransports.end()) {
+        // No TLS tracker yet for this netId.
+        bool added;
+        std::tie(netPair, added) = mPrivateDnsTransports.emplace(netId, PrivateDnsTracker());
+        if (!added) {
+            ALOGE("Memory error while recording private DNS for netId %d", netId);
+            return -ENOMEM;
+        }
+    }
+    auto& tracker = netPair->second;
+
+    // Remove any servers from the tracker that are not in |servers| exactly.
+    for (auto it = tracker.begin(); it != tracker.end();) {
+        if (tlsServers.count(it->first) == 0) {
+            it = tracker.erase(it);
+        } else {
+            ++it;
+        }
+    }
+
+    // Add any new or changed servers to the tracker, and initiate async checks for them.
+    for (const auto& server : tlsServers) {
+        if (needsValidation(tracker, server)) {
+            validatePrivateDnsProvider(server, tracker, netId, mark);
+        }
+    }
+    return 0;
+}
+
+PrivateDnsStatus PrivateDnsConfiguration::getStatus(unsigned netId) {
+    PrivateDnsStatus status{PrivateDnsMode::OFF, {}};
+    std::lock_guard guard(mPrivateDnsLock);
+
+    const auto mode = mPrivateDnsModes.find(netId);
+    if (mode == mPrivateDnsModes.end()) return status;
+    status.mode = mode->second;
+
+    const auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair != mPrivateDnsTransports.end()) {
+        for (const auto& serverPair : netPair->second) {
+            if (serverPair.second == Validation::success) {
+                status.validatedServers.push_back(serverPair.first);
+            }
+        }
+    }
+
+    return status;
+}
+
+void PrivateDnsConfiguration::getStatus(unsigned netId, ExternalPrivateDnsStatus* status) {
+    std::lock_guard guard(mPrivateDnsLock);
+
+    const auto mode = mPrivateDnsModes.find(netId);
+    if (mode == mPrivateDnsModes.end()) return;
+    status->mode = mode->second;
+
+    const auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair != mPrivateDnsTransports.end()) {
+        int count = 0;
+        for (const auto& serverPair : netPair->second) {
+            status->serverStatus[count].ss = serverPair.first.ss;
+            status->serverStatus[count].hostname =
+                    serverPair.first.name.empty() ? "" : serverPair.first.name.c_str();
+            status->serverStatus[count].validation = serverPair.second;
+            count++;
+            if (count >= MAXNS) break;  // Lose the rest
+        }
+        status->numServers = count;
+    }
+}
+
+void PrivateDnsConfiguration::clear(unsigned netId) {
+    if (DBG) {
+        ALOGD("PrivateDnsConfiguration::clear(%u)", netId);
+    }
+    std::lock_guard guard(mPrivateDnsLock);
+    mPrivateDnsModes.erase(netId);
+    mPrivateDnsTransports.erase(netId);
+}
+
+void PrivateDnsConfiguration::validatePrivateDnsProvider(const DnsTlsServer& server,
+                                                         PrivateDnsTracker& tracker, unsigned netId,
+                                                         uint32_t mark) REQUIRES(mPrivateDnsLock) {
+    if (DBG) {
+        ALOGD("validatePrivateDnsProvider(%s, %u)", addrToString(&server.ss).c_str(), netId);
+    }
+
+    tracker[server] = Validation::in_process;
+    if (DBG) {
+        ALOGD("Server %s marked as in_process.  Tracker now has size %zu",
+              addrToString(&server.ss).c_str(), tracker.size());
+    }
+    // Note that capturing |server| and |netId| in this lambda create copies.
+    std::thread validate_thread([this, server, netId, mark] {
+        // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
+        //
+        // Start with a 1 minute delay and backoff to once per hour.
+        //
+        // Assumptions:
+        //     [1] Each TLS validation is ~10KB of certs+handshake+payload.
+        //     [2] Network typically provision clients with <=4 nameservers.
+        //     [3] Average month has 30 days.
+        //
+        // Each validation pass in a given hour is ~1.2MB of data. And 24
+        // such validation passes per day is about ~30MB per month, in the
+        // worst case. Otherwise, this will cost ~600 SYNs per month
+        // (6 SYNs per ip, 4 ips per validation pass, 24 passes per day).
+        auto backoff = netdutils::BackoffSequence<>::Builder()
+                               .withInitialRetransmissionTime(std::chrono::seconds(60))
+                               .withMaximumRetransmissionTime(std::chrono::seconds(3600))
+                               .build();
+
+        while (true) {
+            // ::validate() is a blocking call that performs network operations.
+            // It can take milliseconds to minutes, up to the SYN retry limit.
+            const bool success = DnsTlsTransport::validate(server, netId, mark);
+            if (DBG) {
+                ALOGD("validateDnsTlsServer returned %d for %s", success,
+                      addrToString(&server.ss).c_str());
+            }
+
+            const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
+            if (!needs_reeval) {
+                break;
+            }
+
+            if (backoff.hasNextTimeout()) {
+                std::this_thread::sleep_for(backoff.getNextTimeout());
+            } else {
+                break;
+            }
+        }
+    });
+    validate_thread.detach();
+}
+
+bool PrivateDnsConfiguration::recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId,
+                                                         bool success) {
+    constexpr bool NEEDS_REEVALUATION = true;
+    constexpr bool DONT_REEVALUATE = false;
+
+    std::lock_guard guard(mPrivateDnsLock);
+
+    auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair == mPrivateDnsTransports.end()) {
+        ALOGW("netId %u was erased during private DNS validation", netId);
+        return DONT_REEVALUATE;
+    }
+
+    const auto mode = mPrivateDnsModes.find(netId);
+    if (mode == mPrivateDnsModes.end()) {
+        ALOGW("netId %u has no private DNS validation mode", netId);
+        return DONT_REEVALUATE;
+    }
+    const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
+
+    bool reevaluationStatus =
+            (success || !modeDoesReevaluation) ? DONT_REEVALUATE : NEEDS_REEVALUATION;
+
+    auto& tracker = netPair->second;
+    auto serverPair = tracker.find(server);
+    if (serverPair == tracker.end()) {
+        ALOGW("Server %s was removed during private DNS validation",
+              addrToString(&server.ss).c_str());
+        success = false;
+        reevaluationStatus = DONT_REEVALUATE;
+    } else if (!(serverPair->first == server)) {
+        // TODO: It doesn't seem correct to overwrite the tracker entry for
+        // |server| down below in this circumstance... Fix this.
+        ALOGW("Server %s was changed during private DNS validation",
+              addrToString(&server.ss).c_str());
+        success = false;
+        reevaluationStatus = DONT_REEVALUATE;
+    }
+
+    // Send a validation event to NetdEventListenerService.
+    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
+    if (listeners.size() != 0) {
+        for (const auto& it : listeners) {
+            it->onPrivateDnsValidationEvent(netId, addrToString(&server.ss), server.name, success);
+        }
+        if (DBG) {
+            ALOGD("Sent validation %s event on netId %u for %s with hostname %s",
+                  success ? "success" : "failure", netId, addrToString(&server.ss).c_str(),
+                  server.name.c_str());
+        }
+    } else {
+        ALOGE("Validation event not sent since no INetdEventListener receiver is available.");
+    }
+
+    if (success) {
+        tracker[server] = Validation::success;
+        if (DBG) {
+            ALOGD("Validation succeeded for %s! Tracker now has %zu entries.",
+                  addrToString(&server.ss).c_str(), tracker.size());
+        }
+    } else {
+        // Validation failure is expected if a user is on a captive portal.
+        // TODO: Trigger a second validation attempt after captive portal login
+        // succeeds.
+        tracker[server] = (reevaluationStatus == NEEDS_REEVALUATION) ? Validation::in_process
+                                                                     : Validation::fail;
+        if (DBG) {
+            ALOGD("Validation failed for %s!", addrToString(&server.ss).c_str());
+        }
+    }
+
+    return reevaluationStatus;
+}
+
+// Start validation for newly added servers as well as any servers that have
+// landed in Validation::fail state. Note that servers that have failed
+// multiple validation attempts but for which there is still a validating
+// thread running are marked as being Validation::in_process.
+bool PrivateDnsConfiguration::needsValidation(const PrivateDnsTracker& tracker,
+                                              const DnsTlsServer& server) {
+    const auto& iter = tracker.find(server);
+    return (iter == tracker.end()) || (iter->second == Validation::fail);
+}
+
+PrivateDnsConfiguration gPrivateDnsConfiguration;
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/PrivateDnsConfiguration.h b/resolv/PrivateDnsConfiguration.h
new file mode 100644
index 0000000..50fb54d
--- /dev/null
+++ b/resolv/PrivateDnsConfiguration.h
@@ -0,0 +1,92 @@
+/*
+ * 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 NETD_RESOLV_PRIVATEDNSCONFIGURATION_H
+#define NETD_RESOLV_PRIVATEDNSCONFIGURATION_H
+
+#include <list>
+#include <map>
+#include <mutex>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+#include "DnsTlsServer.h"
+
+namespace android {
+namespace net {
+
+// The DNS over TLS mode on a specific netId.
+enum class PrivateDnsMode : uint8_t { OFF, OPPORTUNISTIC, STRICT };
+
+// Validation status of a DNS over TLS server (on a specific netId).
+enum class Validation : uint8_t { in_process, success, fail, unknown_server, unknown_netid };
+
+struct PrivateDnsStatus {
+    PrivateDnsMode mode;
+    std::list<DnsTlsServer> validatedServers;
+};
+
+// TODO: remove this C-style struct and use PrivateDnsStatus everywhere.
+struct ExternalPrivateDnsStatus {
+    PrivateDnsMode mode;
+    int numServers;
+    struct PrivateDnsInfo {
+        sockaddr_storage ss;
+        const char* hostname;
+        Validation validation;
+    } serverStatus[MAXNS];
+};
+
+class PrivateDnsConfiguration {
+  public:
+    int set(int32_t netId, uint32_t mark, const std::vector<std::string>& servers,
+            const std::string& name, const std::set<std::vector<uint8_t>>& fingerprints);
+
+    PrivateDnsStatus getStatus(unsigned netId);
+
+    // DEPRECATED, use getStatus() above.
+    void getStatus(unsigned netId, ExternalPrivateDnsStatus* status);
+
+    void clear(unsigned netId);
+
+  private:
+    typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
+
+    void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
+                                    unsigned netId, uint32_t mark) REQUIRES(mPrivateDnsLock);
+
+    bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success);
+
+    // Start validation for newly added servers as well as any servers that have
+    // landed in Validation::fail state. Note that servers that have failed
+    // multiple validation attempts but for which there is still a validating
+    // thread running are marked as being Validation::in_process.
+    bool needsValidation(const PrivateDnsTracker& tracker, const DnsTlsServer& server);
+
+    std::mutex mPrivateDnsLock;
+    std::map<unsigned, PrivateDnsMode> mPrivateDnsModes GUARDED_BY(mPrivateDnsLock);
+    // Structure for tracking the validation status of servers on a specific netId.
+    // Using the AddressComparator ensures at most one entry per IP address.
+    std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
+};
+
+extern PrivateDnsConfiguration gPrivateDnsConfiguration;
+
+}  // namespace net
+}  // namespace android
+
+#endif /* NETD_RESOLV_PRIVATEDNSCONFIGURATION_H */
diff --git a/server/dns/README.md b/resolv/README.md
similarity index 93%
rename from server/dns/README.md
rename to resolv/README.md
index a463fc9..8fe4c89 100644
--- a/server/dns/README.md
+++ b/resolv/README.md
@@ -9,7 +9,7 @@
  * `DnsTlsSessionCache`
  * `DnsTlsSocket`
 
-`DnsTlsDispatcher` is a singleton class whose `query` method is the `dns/` directory's
+`DnsTlsDispatcher` is a singleton class whose `query` method is the DnsTls's
 only public interface.  `DnsTlsDispatcher` is just a table holding the
 `DnsTlsTransport` for each server (represented by a `DnsTlsServer` struct) and
 network.  `DnsTlsDispatcher` also blocks each query thread, waiting on a
@@ -43,7 +43,7 @@
 also heavily threaded to exercise this functionality.
 
 This code creates O(1) threads per socket, and does not create a new thread for each
-query or response.  However, bionic's stub resolver does create a thread for each query.
+query or response.  However, DnsProxyListener does create a thread for each query.
 
 ### Threading in `DnsTlsSocket`
 
@@ -57,9 +57,9 @@
 
 We need to pass messages between threads using a pipe, and not a condition variable
 or a thread-safe queue, because the socket thread has to be blocked
-in `select` waiting for data from the server, but also has to be woken
+in `poll()` waiting for data from the server, but also has to be woken
 up on inputs from the query threads.  Therefore, inputs from the query
-threads have to arrive on a socket, so that `select()` can listen for them.
+threads have to arrive on a socket, so that `poll()` can listen for them.
 (There can only be a single thread because [you can't use different threads
 to read and write in OpenSSL](https://www.openssl.org/blog/blog/2017/02/21/threads/)).
 
@@ -104,10 +104,10 @@
 
 ## Testing
 
-Unit tests are in `../tests/dns_tls_test.cpp`.  They cover all the classes except
+Unit tests are in `dns_tls_test.cpp`. They cover all the classes except
 `DnsTlsSocket` (which requires `CAP_NET_ADMIN` because it uses `setsockopt(SO_MARK)`) and
 `DnsTlsSessionCache` (which requires integration with libssl).  These classes are
-exercised by the integration tests in `../tests/netd_test.cpp`.
+exercised by the integration tests in `../tests/resolv_test.cpp`.
 
 ### Dependency Injection
 
diff --git a/resolv/ResolverController.cpp b/resolv/ResolverController.cpp
new file mode 100644
index 0000000..e16ca69
--- /dev/null
+++ b/resolv/ResolverController.cpp
@@ -0,0 +1,382 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "ResolverController"
+
+#include "ResolverController.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <netdb.h>
+
+#include <aidl/android/net/IDnsResolver.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "Dns64Configuration.h"
+#include "DnsResolver.h"
+#include "Fwmark.h"
+#include "PrivateDnsConfiguration.h"
+#include "ResolverEventReporter.h"
+#include "ResolverStats.h"
+#include "netd_resolv/stats.h"
+#include "resolv_cache.h"
+
+using namespace std::placeholders;
+using aidl::android::net::ResolverParamsParcel;
+
+namespace android {
+
+using netdutils::DumpWriter;
+
+namespace net {
+
+namespace {
+
+std::string addrToString(const sockaddr_storage* addr) {
+    char out[INET6_ADDRSTRLEN] = {0};
+    getnameinfo((const sockaddr*)addr, sizeof(sockaddr_storage), out, INET6_ADDRSTRLEN, nullptr, 0,
+                NI_NUMERICHOST);
+    return std::string(out);
+}
+
+const char* getPrivateDnsModeString(PrivateDnsMode mode) {
+    switch (mode) {
+        case PrivateDnsMode::OFF:
+            return "OFF";
+        case PrivateDnsMode::OPPORTUNISTIC:
+            return "OPPORTUNISTIC";
+        case PrivateDnsMode::STRICT:
+            return "STRICT";
+    }
+}
+
+constexpr const char* validationStatusToString(Validation value) {
+    switch (value) {
+        case Validation::in_process:
+            return "in_process";
+        case Validation::success:
+            return "success";
+        case Validation::fail:
+            return "fail";
+        case Validation::unknown_server:
+            return "unknown_server";
+        case Validation::unknown_netid:
+            return "unknown_netid";
+        default:
+            return "unknown_status";
+    }
+}
+
+void sendNat64PrefixEvent(const Dns64Configuration::Nat64PrefixInfo& args) {
+    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
+    if (listeners.size() == 0) {
+        LOG(ERROR) << __func__ << ": No available listener. dropping NAT64 prefix event";
+        return;
+    }
+    for (const auto& it : listeners) {
+        it->onNat64PrefixEvent(args.netId, args.added, args.prefixString, args.prefixLength);
+    }
+}
+
+int getDnsInfo(unsigned netId, std::vector<std::string>* servers, std::vector<std::string>* domains,
+               res_params* params, std::vector<android::net::ResolverStats>* stats,
+               std::vector<int32_t>* wait_for_pending_req_timeout_count) {
+    using aidl::android::net::IDnsResolver;
+    using android::net::ResolverStats;
+    static_assert(ResolverStats::STATS_SUCCESSES == IDnsResolver::RESOLVER_STATS_SUCCESSES &&
+                          ResolverStats::STATS_ERRORS == IDnsResolver::RESOLVER_STATS_ERRORS &&
+                          ResolverStats::STATS_TIMEOUTS == IDnsResolver::RESOLVER_STATS_TIMEOUTS &&
+                          ResolverStats::STATS_INTERNAL_ERRORS ==
+                                  IDnsResolver::RESOLVER_STATS_INTERNAL_ERRORS &&
+                          ResolverStats::STATS_RTT_AVG == IDnsResolver::RESOLVER_STATS_RTT_AVG &&
+                          ResolverStats::STATS_LAST_SAMPLE_TIME ==
+                                  IDnsResolver::RESOLVER_STATS_LAST_SAMPLE_TIME &&
+                          ResolverStats::STATS_USABLE == IDnsResolver::RESOLVER_STATS_USABLE &&
+                          ResolverStats::STATS_COUNT == IDnsResolver::RESOLVER_STATS_COUNT,
+                  "AIDL and ResolverStats.h out of sync");
+    int nscount = -1;
+    sockaddr_storage res_servers[MAXNS];
+    int dcount = -1;
+    char res_domains[MAXDNSRCH][MAXDNSRCHPATH];
+    res_stats res_stats[MAXNS];
+    servers->clear();
+    domains->clear();
+    *params = res_params{};
+    stats->clear();
+    int res_wait_for_pending_req_timeout_count;
+    int revision_id = android_net_res_stats_get_info_for_net(
+            netId, &nscount, res_servers, &dcount, res_domains, params, res_stats,
+            &res_wait_for_pending_req_timeout_count);
+
+    // If the netId is unknown (which can happen for valid net IDs for which no DNS servers have
+    // yet been configured), there is no revision ID. In this case there is no data to return.
+    if (revision_id < 0) {
+        return 0;
+    }
+
+    // Verify that the returned data is sane.
+    if (nscount < 0 || nscount > MAXNS || dcount < 0 || dcount > MAXDNSRCH) {
+        LOG(ERROR) << __func__ << ": nscount = " << nscount << ", dcount = " << dcount;
+        return -ENOTRECOVERABLE;
+    }
+
+    // Determine which servers are considered usable by the resolver.
+    bool valid_servers[MAXNS];
+    std::fill_n(valid_servers, MAXNS, false);
+    android_net_res_stats_get_usable_servers(params, res_stats, nscount, valid_servers);
+
+    // Convert the server sockaddr structures to std::string.
+    stats->resize(nscount);
+    for (int i = 0; i < nscount; ++i) {
+        char hbuf[NI_MAXHOST];
+        int rv =
+                getnameinfo(reinterpret_cast<const sockaddr*>(&res_servers[i]),
+                            sizeof(res_servers[i]), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST);
+        std::string server_str;
+        if (rv == 0) {
+            server_str.assign(hbuf);
+        } else {
+            LOG(ERROR) << "getnameinfo() failed for server #" << i << ": " << gai_strerror(rv);
+            server_str.assign("<invalid>");
+        }
+        servers->push_back(std::move(server_str));
+        android::net::ResolverStats& cur_stats = (*stats)[i];
+        android_net_res_stats_aggregate(&res_stats[i], &cur_stats.successes, &cur_stats.errors,
+                                        &cur_stats.timeouts, &cur_stats.internal_errors,
+                                        &cur_stats.rtt_avg, &cur_stats.last_sample_time);
+        cur_stats.usable = valid_servers[i];
+    }
+
+    // Convert the stack-allocated search domain strings to std::string.
+    for (int i = 0; i < dcount; ++i) {
+        domains->push_back(res_domains[i]);
+    }
+
+    (*wait_for_pending_req_timeout_count)[0] = res_wait_for_pending_req_timeout_count;
+    return 0;
+}
+
+}  // namespace
+
+ResolverController::ResolverController()
+    : mDns64Configuration(
+              [](uint32_t netId, uint32_t uid, android_net_context* netcontext) {
+                  gResNetdCallbacks.get_network_context(netId, uid, netcontext);
+              },
+              std::bind(sendNat64PrefixEvent, _1)) {}
+
+void ResolverController::destroyNetworkCache(unsigned netId) {
+    LOG(VERBOSE) << __func__ << ": netId = " << netId;
+
+    resolv_delete_cache_for_net(netId);
+    mDns64Configuration.stopPrefixDiscovery(netId);
+    gPrivateDnsConfiguration.clear(netId);
+}
+
+int ResolverController::createNetworkCache(unsigned netId) {
+    LOG(VERBOSE) << __func__ << ": netId = " << netId;
+
+    return resolv_create_cache_for_net(netId);
+}
+
+int ResolverController::setResolverConfiguration(
+        const ResolverParamsParcel& resolverParams,
+        const std::set<std::vector<uint8_t>>& tlsFingerprints) {
+    using aidl::android::net::IDnsResolver;
+
+    // At private DNS validation time, we only know the netId, so we have to guess/compute the
+    // corresponding socket mark.
+    Fwmark fwmark;
+    fwmark.netId = resolverParams.netId;
+    fwmark.explicitlySelected = true;
+    fwmark.protectedFromVpn = true;
+    fwmark.permission = PERMISSION_SYSTEM;
+
+    // Allow at most MAXNS private DNS servers in a network to prevent too many broken servers.
+    std::vector<std::string> tlsServers = resolverParams.tlsServers;
+    if (tlsServers.size() > MAXNS) {
+        tlsServers.resize(MAXNS);
+    }
+    const int err = gPrivateDnsConfiguration.set(resolverParams.netId, fwmark.intValue, tlsServers,
+                                                 resolverParams.tlsName, tlsFingerprints);
+    if (err != 0) {
+        return err;
+    }
+
+    // Convert network-assigned server list to bionic's format.
+    const size_t serverCount = std::min<size_t>(MAXNS, resolverParams.servers.size());
+    std::vector<const char*> server_ptrs;
+    for (size_t i = 0; i < serverCount; ++i) {
+        server_ptrs.push_back(resolverParams.servers[i].c_str());
+    }
+
+    std::string domains_str = android::base::Join(resolverParams.domains, " ");
+
+    // TODO: Change resolv_set_nameservers_for_net() to use ResolverParamsParcel directly.
+    res_params res_params = {};
+    res_params.sample_validity = resolverParams.sampleValiditySeconds;
+    res_params.success_threshold = resolverParams.successThreshold;
+    res_params.min_samples = resolverParams.minSamples;
+    res_params.max_samples = resolverParams.maxSamples;
+    res_params.base_timeout_msec = resolverParams.baseTimeoutMsec;
+    res_params.retry_count = resolverParams.retryCount;
+
+    LOG(VERBOSE) << "setDnsServers netId = " << resolverParams.netId
+                 << ", numservers = " << resolverParams.domains.size();
+
+    return -resolv_set_nameservers_for_net(resolverParams.netId, server_ptrs.data(),
+                                           server_ptrs.size(), domains_str.c_str(), &res_params);
+}
+
+int ResolverController::getResolverInfo(int32_t netId, std::vector<std::string>* servers,
+                                        std::vector<std::string>* domains,
+                                        std::vector<std::string>* tlsServers,
+                                        std::vector<int32_t>* params, std::vector<int32_t>* stats,
+                                        std::vector<int32_t>* wait_for_pending_req_timeout_count) {
+    using aidl::android::net::IDnsResolver;
+    using android::net::ResolverStats;
+    res_params res_params;
+    std::vector<ResolverStats> res_stats;
+    int ret = getDnsInfo(netId, servers, domains, &res_params, &res_stats,
+                         wait_for_pending_req_timeout_count);
+    if (ret != 0) {
+        return ret;
+    }
+
+    // Serialize the information for binder.
+    ResolverStats::encodeAll(res_stats, stats);
+
+    ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
+    gPrivateDnsConfiguration.getStatus(netId, &privateDnsStatus);
+    for (int i = 0; i < privateDnsStatus.numServers; i++) {
+        std::string tlsServer_str = addrToString(&(privateDnsStatus.serverStatus[i].ss));
+        tlsServers->push_back(std::move(tlsServer_str));
+    }
+
+    params->resize(IDnsResolver::RESOLVER_PARAMS_COUNT);
+    (*params)[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY] = res_params.sample_validity;
+    (*params)[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD] = res_params.success_threshold;
+    (*params)[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES] = res_params.min_samples;
+    (*params)[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES] = res_params.max_samples;
+    (*params)[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC] = res_params.base_timeout_msec;
+    (*params)[IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT] = res_params.retry_count;
+    return 0;
+}
+
+void ResolverController::startPrefix64Discovery(int32_t netId) {
+    mDns64Configuration.startPrefixDiscovery(netId);
+}
+
+void ResolverController::stopPrefix64Discovery(int32_t netId) {
+    return mDns64Configuration.stopPrefixDiscovery(netId);
+}
+
+// TODO: use StatusOr<T> to wrap the result.
+int ResolverController::getPrefix64(unsigned netId, netdutils::IPPrefix* prefix) {
+    netdutils::IPPrefix p = mDns64Configuration.getPrefix64(netId);
+    if (p.family() != AF_INET6 || p.length() == 0) {
+        LOG(ERROR) << "No valid NAT64 prefix (" << netId << ", " << p.toString().c_str() << ")";
+
+        return -ENOENT;
+    }
+    *prefix = p;
+    return 0;
+}
+
+void ResolverController::dump(DumpWriter& dw, unsigned netId) {
+    // No lock needed since Bionic's resolver locks all accessed data structures internally.
+    using android::net::ResolverStats;
+    std::vector<std::string> servers;
+    std::vector<std::string> domains;
+    res_params params = {};
+    std::vector<ResolverStats> stats;
+    std::vector<int32_t> wait_for_pending_req_timeout_count(1, 0);
+    time_t now = time(nullptr);
+    int rv = getDnsInfo(netId, &servers, &domains, &params, &stats,
+                        &wait_for_pending_req_timeout_count);
+    dw.incIndent();
+    if (rv != 0) {
+        dw.println("getDnsInfo() failed for netid %u", netId);
+    } else {
+        if (servers.empty()) {
+            dw.println("No DNS servers defined");
+        } else {
+            dw.println(
+                    "DNS servers: # IP (total, successes, errors, timeouts, internal errors, "
+                    "RTT avg, last sample)");
+            dw.incIndent();
+            for (size_t i = 0; i < servers.size(); ++i) {
+                if (i < stats.size()) {
+                    const ResolverStats& s = stats[i];
+                    int total = s.successes + s.errors + s.timeouts + s.internal_errors;
+                    if (total > 0) {
+                        int time_delta = (s.last_sample_time > 0) ? now - s.last_sample_time : -1;
+                        dw.println("%s (%d, %d, %d, %d, %d, %dms, %ds)%s", servers[i].c_str(),
+                                   total, s.successes, s.errors, s.timeouts, s.internal_errors,
+                                   s.rtt_avg, time_delta, s.usable ? "" : " BROKEN");
+                    } else {
+                        dw.println("%s <no data>", servers[i].c_str());
+                    }
+                } else {
+                    dw.println("%s <no stats>", servers[i].c_str());
+                }
+            }
+            dw.decIndent();
+        }
+        if (domains.empty()) {
+            dw.println("No search domains defined");
+        } else {
+            std::string domains_str = android::base::Join(domains, ", ");
+            dw.println("search domains: %s", domains_str.c_str());
+        }
+        if (params.sample_validity != 0) {
+            dw.println(
+                    "DNS parameters: sample validity = %us, success threshold = %u%%, "
+                    "samples (min, max) = (%u, %u), base_timeout = %dmsec, retry count = "
+                    "%dtimes",
+                    params.sample_validity, params.success_threshold, params.min_samples,
+                    params.max_samples, params.base_timeout_msec, params.retry_count);
+        }
+
+        mDns64Configuration.dump(dw, netId);
+        ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
+        gPrivateDnsConfiguration.getStatus(netId, &privateDnsStatus);
+        dw.println("Private DNS mode: %s",
+                   getPrivateDnsModeString(static_cast<PrivateDnsMode>(privateDnsStatus.mode)));
+        if (!privateDnsStatus.numServers) {
+            dw.println("No Private DNS servers configured");
+        } else {
+            dw.println("Private DNS configuration (%u entries)", privateDnsStatus.numServers);
+            dw.incIndent();
+            for (int i = 0; i < privateDnsStatus.numServers; i++) {
+                dw.println("%s name{%s} status{%s}",
+                           addrToString(&(privateDnsStatus.serverStatus[i].ss)).c_str(),
+                           privateDnsStatus.serverStatus[i].hostname,
+                           validationStatusToString(static_cast<Validation>(
+                                   privateDnsStatus.serverStatus[i].validation)));
+            }
+            dw.decIndent();
+        }
+        dw.println("Concurrent DNS query timeout: %d", wait_for_pending_req_timeout_count[0]);
+    }
+    dw.decIndent();
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/ResolverController.h b/resolv/ResolverController.h
new file mode 100644
index 0000000..6d08cdb
--- /dev/null
+++ b/resolv/ResolverController.h
@@ -0,0 +1,67 @@
+/*
+ * 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 _RESOLVER_CONTROLLER_H_
+#define _RESOLVER_CONTROLLER_H_
+
+#include <list>
+#include <set>
+#include <vector>
+
+#include <aidl/android/net/ResolverParamsParcel.h>
+#include "Dns64Configuration.h"
+#include "netd_resolv/resolv.h"
+#include "netdutils/DumpWriter.h"
+
+struct res_params;
+
+namespace android {
+namespace net {
+
+struct ResolverStats;
+
+class ResolverController {
+  public:
+    ResolverController();
+    ~ResolverController() = default;
+
+    void destroyNetworkCache(unsigned netid);
+    int createNetworkCache(unsigned netid);
+
+    int getPrefix64(unsigned netId, netdutils::IPPrefix* prefix);
+
+    // Binder specific functions, which convert between the ResolverParamsParcel and the
+    // actual data structures, and call setDnsServer() / getDnsInfo() for the actual processing.
+    int setResolverConfiguration(const aidl::android::net::ResolverParamsParcel& resolverParams,
+                                 const std::set<std::vector<uint8_t>>& tlsFingerprints);
+
+    int getResolverInfo(int32_t netId, std::vector<std::string>* servers,
+                        std::vector<std::string>* domains, std::vector<std::string>* tlsServers,
+                        std::vector<int32_t>* params, std::vector<int32_t>* stats,
+                        std::vector<int32_t>* wait_for_pending_req_timeout_count);
+
+    void startPrefix64Discovery(int32_t netId);
+    void stopPrefix64Discovery(int32_t netId);
+
+    void dump(netdutils::DumpWriter& dw, unsigned netId);
+
+  private:
+    Dns64Configuration mDns64Configuration;
+};
+}  // namespace net
+}  // namespace android
+
+#endif /* _RESOLVER_CONTROLLER_H_ */
diff --git a/resolv/ResolverEventReporter.cpp b/resolv/ResolverEventReporter.cpp
new file mode 100644
index 0000000..ccec08b
--- /dev/null
+++ b/resolv/ResolverEventReporter.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "ResolverEventReporter"
+
+#include "ResolverEventReporter.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+
+using aidl::android::net::metrics::INetdEventListener;
+
+ResolverEventReporter& ResolverEventReporter::getInstance() {
+    // It should be initialized only once.
+    static ResolverEventReporter instance;
+
+    // Add framework metrics listener. Because the binder service "netd_listener" may be launched
+    // later than Netd, try to get binder handler in every instance query if any. The framework
+    // metrics listener should be added only once if it has been already added successfully.
+    instance.addDefaultListener();
+
+    return instance;
+}
+
+ResolverEventReporter::ListenerSet ResolverEventReporter::getListeners() const {
+    return getListenersImpl();
+}
+
+int ResolverEventReporter::addListener(const std::shared_ptr<INetdEventListener>& listener) {
+    return addListenerImpl(listener);
+}
+
+// TODO: Consider registering metrics listener from framework and remove this function.
+// Currently, the framework listener "netd_listener" is shared by netd and libnetd_resolv.
+// Consider breaking it into two listeners. Once it has done, may let framework register
+// the listener proactively.
+void ResolverEventReporter::addDefaultListener() {
+    std::lock_guard lock(mMutex);
+
+    static bool added = false;
+    if (added) return;
+
+    // Use the non-blocking call AServiceManager_checkService in order not to delay DNS
+    // lookup threads when the netd_listener service is not ready.
+    ndk::SpAIBinder binder = ndk::SpAIBinder(AServiceManager_checkService("netd_listener"));
+    std::shared_ptr<INetdEventListener> listener = INetdEventListener::fromBinder(binder);
+
+    if (listener == nullptr) return;
+
+    if (!addListenerImplLocked(listener)) added = true;
+}
+
+void ResolverEventReporter::handleBinderDied(const void* who) {
+    std::lock_guard lock(mMutex);
+
+    // Use the raw binder pointer address to be the identification of dead binder. Treat "who"
+    // which passes the raw address of dead binder as an identification only.
+    auto found = std::find_if(mListeners.begin(), mListeners.end(),
+                              [=](const auto& it) { return static_cast<void*>(it.get()) == who; });
+
+    if (found != mListeners.end()) mListeners.erase(found);
+}
+
+ResolverEventReporter::ListenerSet ResolverEventReporter::getListenersImpl() const {
+    std::lock_guard lock(mMutex);
+    return mListeners;
+}
+
+int ResolverEventReporter::addListenerImpl(const std::shared_ptr<INetdEventListener>& listener) {
+    std::lock_guard lock(mMutex);
+    return addListenerImplLocked(listener);
+}
+
+int ResolverEventReporter::addListenerImplLocked(
+        const std::shared_ptr<INetdEventListener>& listener) {
+    if (listener == nullptr) {
+        LOG(ERROR) << "The listener should not be null";
+        return -EINVAL;
+    }
+
+    // TODO: Perhaps ignore the listener which comes from the same binder.
+    const auto& it = mListeners.find(listener);
+    if (it != mListeners.end()) {
+        LOG(WARNING) << "The listener was already subscribed";
+        return -EEXIST;
+    }
+
+    static AIBinder_DeathRecipient* deathRecipient = nullptr;
+    if (deathRecipient == nullptr) {
+        // The AIBinder_DeathRecipient object is used to manage all death recipients for multiple
+        // binder objects. It doesn't released because there should have at least one binder object
+        // from framework.
+        // TODO: Considering to remove death recipient for the binder object from framework because
+        // it doesn't need death recipient actually.
+        deathRecipient = AIBinder_DeathRecipient_new([](void* cookie) {
+            ResolverEventReporter::getInstance().handleBinderDied(cookie);
+        });
+    }
+
+    // Pass the raw binder pointer address to be the cookie of the death recipient. While the death
+    // notification is fired, the cookie is used for identifying which binder was died. Because
+    // the NDK binder doesn't pass dead binder pointer to binder death handler, the binder death
+    // handler can't know who was died via wp<IBinder>. The reason for wp<IBinder> is not passed
+    // is that NDK binder can't transform a wp<IBinder> to a wp<AIBinder> in some cases.
+    // See more information in b/128712772.
+    auto binder = listener->asBinder().get();
+    auto cookie = static_cast<void*>(listener.get());  // Used for dead binder identification.
+    binder_status_t status = AIBinder_linkToDeath(binder, deathRecipient, cookie);
+
+    if (STATUS_OK != status) {
+        LOG(ERROR) << "Failed to register death notification for INetdEventListener";
+        return -EAGAIN;
+    }
+
+    mListeners.insert(listener);
+    return 0;
+}
\ No newline at end of file
diff --git a/resolv/ResolverEventReporter.h b/resolv/ResolverEventReporter.h
new file mode 100644
index 0000000..eb0d9cf
--- /dev/null
+++ b/resolv/ResolverEventReporter.h
@@ -0,0 +1,68 @@
+/*
+ * 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 NETD_RESOLV_EVENT_REPORTER_H
+#define NETD_RESOLV_EVENT_REPORTER_H
+
+#include <set>
+
+#include <android-base/thread_annotations.h>
+
+#include "aidl/android/net/metrics/INetdEventListener.h"
+
+/*
+ * This class can be used to get the binder reference to the netd events listener service
+ * via stable runtime ABI which is achieved from libbinder_ndk. It also allows that
+ * register an event listener.
+ */
+class ResolverEventReporter {
+  public:
+    ResolverEventReporter(ResolverEventReporter const&) = delete;
+    ResolverEventReporter(ResolverEventReporter&&) = delete;
+    ResolverEventReporter& operator=(ResolverEventReporter const&) = delete;
+    ResolverEventReporter& operator=(ResolverEventReporter&&) = delete;
+
+    using ListenerSet = std::set<std::shared_ptr<aidl::android::net::metrics::INetdEventListener>>;
+
+    // Get the instance of the singleton ResolverEventReporter.
+    static ResolverEventReporter& getInstance();
+
+    // Return the binder from the singleton ResolverEventReporter. This method is threadsafe.
+    ListenerSet getListeners() const;
+
+    // Add the binder to the singleton ResolverEventReporter. This method is threadsafe.
+    int addListener(
+            const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener);
+
+  private:
+    ResolverEventReporter() = default;
+    ~ResolverEventReporter() = default;
+
+    void addDefaultListener() EXCLUDES(mMutex);
+    int addListenerImpl(
+            const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
+            EXCLUDES(mMutex);
+    int addListenerImplLocked(
+            const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
+            REQUIRES(mMutex);
+    ListenerSet getListenersImpl() const EXCLUDES(mMutex);
+    void handleBinderDied(const void* who) EXCLUDES(mMutex);
+
+    mutable std::mutex mMutex;
+    ListenerSet mListeners GUARDED_BY(mMutex);
+};
+
+#endif  // NETD_RESOLV_EVENT_REPORTER_H
diff --git a/server/ResolverStats.h b/resolv/ResolverStats.h
similarity index 79%
rename from server/ResolverStats.h
rename to resolv/ResolverStats.h
index be63d88..9140af8 100644
--- a/server/ResolverStats.h
+++ b/resolv/ResolverStats.h
@@ -28,23 +28,23 @@
     // android_net_res_stats_get_info_for_net(), the usability is calculated by applying
     // android_net_res_stats_get_usable_servers() to this data.
     enum ResolverStatsOffsets {
-        STATS_SUCCESSES = 0,    // # successes counted for this server
-        STATS_ERRORS,           // # errors
-        STATS_TIMEOUTS,         // # timeouts
-        STATS_INTERNAL_ERRORS,  // # internal errors
-        STATS_RTT_AVG,          // average round-trip-time
-        STATS_LAST_SAMPLE_TIME, // time in s when the last sample was recorded
-        STATS_USABLE,           // whether the server is considered usable
-        STATS_COUNT             // total count of integers in the per-server data
+        STATS_SUCCESSES = 0,     // # successes counted for this server
+        STATS_ERRORS,            // # errors
+        STATS_TIMEOUTS,          // # timeouts
+        STATS_INTERNAL_ERRORS,   // # internal errors
+        STATS_RTT_AVG,           // average round-trip-time
+        STATS_LAST_SAMPLE_TIME,  // time in s when the last sample was recorded
+        STATS_USABLE,            // whether the server is considered usable
+        STATS_COUNT              // total count of integers in the per-server data
     };
 
-    int successes {-1};
-    int errors {-1};
-    int timeouts {-1};
-    int internal_errors {-1};
-    int rtt_avg {-1};
-    time_t last_sample_time {0};
-    bool usable {false};
+    int successes{-1};
+    int errors{-1};
+    int timeouts{-1};
+    int internal_errors{-1};
+    int rtt_avg{-1};
+    time_t last_sample_time{0};
+    bool usable{false};
 
     // Serialize the resolver stats to the end of |out|.
     void encode(std::vector<int32_t>* out) const;
@@ -61,7 +61,6 @@
     static bool decodeAll(const std::vector<int32_t>& in, std::vector<ResolverStats>* stats);
 };
 
-
 inline void ResolverStats::encode(std::vector<int32_t>* out) const {
     size_t ofs = out->size();
     out->resize(ofs + STATS_COUNT);
@@ -75,7 +74,7 @@
     cur[STATS_USABLE] = usable;
 }
 
-    // Read the serialized resolverstats starting at |in[ofs]|.
+// Read the serialized resolverstats starting at |in[ofs]|.
 inline ssize_t ResolverStats::decode(const std::vector<int32_t>& in, ssize_t ofs) {
     if (ofs < 0 || static_cast<size_t>(ofs) + STATS_COUNT > in.size()) {
         return -1;
@@ -92,7 +91,7 @@
 }
 
 inline void ResolverStats::encodeAll(const std::vector<ResolverStats>& stats,
-        std::vector<int32_t>* out) {
+                                     std::vector<int32_t>* out) {
     for (const auto& s : stats) {
         s.encode(out);
     }
@@ -100,7 +99,7 @@
 
 // TODO: Replace with a better representation, e.g. a Parcelable.
 inline bool ResolverStats::decodeAll(const std::vector<int32_t>& in,
-        std::vector<ResolverStats>* stats) {
+                                     std::vector<ResolverStats>* stats) {
     ssize_t size = in.size();
     if (size % STATS_COUNT) {
         return false;
diff --git a/resolv/aidl/dnsresolver/1/android/net/IDnsResolver.aidl b/resolv/aidl/dnsresolver/1/android/net/IDnsResolver.aidl
new file mode 100644
index 0000000..c4b2b6d
--- /dev/null
+++ b/resolv/aidl/dnsresolver/1/android/net/IDnsResolver.aidl
@@ -0,0 +1,27 @@
+package android.net;
+interface IDnsResolver {
+  boolean isAlive();
+  void registerEventListener(android.net.metrics.INetdEventListener listener);
+  void setResolverConfiguration(in android.net.ResolverParamsParcel resolverParams);
+  void getResolverInfo(int netId, out @utf8InCpp String[] servers, out @utf8InCpp String[] domains, out @utf8InCpp String[] tlsServers, out int[] params, out int[] stats, out int[] wait_for_pending_req_timeout_count);
+  void startPrefix64Discovery(int netId);
+  void stopPrefix64Discovery(int netId);
+  @utf8InCpp String getPrefix64(int netId);
+  void createNetworkCache(int netId);
+  void destroyNetworkCache(int netId);
+  const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
+  const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
+  const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
+  const int RESOLVER_PARAMS_MAX_SAMPLES = 3;
+  const int RESOLVER_PARAMS_BASE_TIMEOUT_MSEC = 4;
+  const int RESOLVER_PARAMS_RETRY_COUNT = 5;
+  const int RESOLVER_PARAMS_COUNT = 6;
+  const int RESOLVER_STATS_SUCCESSES = 0;
+  const int RESOLVER_STATS_ERRORS = 1;
+  const int RESOLVER_STATS_TIMEOUTS = 2;
+  const int RESOLVER_STATS_INTERNAL_ERRORS = 3;
+  const int RESOLVER_STATS_RTT_AVG = 4;
+  const int RESOLVER_STATS_LAST_SAMPLE_TIME = 5;
+  const int RESOLVER_STATS_USABLE = 6;
+  const int RESOLVER_STATS_COUNT = 7;
+}
diff --git a/resolv/aidl/dnsresolver/1/android/net/ResolverParamsParcel.aidl b/resolv/aidl/dnsresolver/1/android/net/ResolverParamsParcel.aidl
new file mode 100644
index 0000000..b808bae
--- /dev/null
+++ b/resolv/aidl/dnsresolver/1/android/net/ResolverParamsParcel.aidl
@@ -0,0 +1,15 @@
+package android.net;
+parcelable ResolverParamsParcel {
+  int netId;
+  int sampleValiditySeconds;
+  int successThreshold;
+  int minSamples;
+  int maxSamples;
+  int baseTimeoutMsec;
+  int retryCount;
+  @utf8InCpp String[] servers;
+  @utf8InCpp String[] domains;
+  @utf8InCpp String tlsName;
+  @utf8InCpp String[] tlsServers;
+  @utf8InCpp String[] tlsFingerprints;
+}
diff --git a/resolv/aidl/dnsresolver/2/android/net/IDnsResolver.aidl b/resolv/aidl/dnsresolver/2/android/net/IDnsResolver.aidl
new file mode 100644
index 0000000..846bbec
--- /dev/null
+++ b/resolv/aidl/dnsresolver/2/android/net/IDnsResolver.aidl
@@ -0,0 +1,50 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+interface IDnsResolver {
+  boolean isAlive();
+  void registerEventListener(android.net.metrics.INetdEventListener listener);
+  void setResolverConfiguration(in android.net.ResolverParamsParcel resolverParams);
+  void getResolverInfo(int netId, out @utf8InCpp String[] servers, out @utf8InCpp String[] domains, out @utf8InCpp String[] tlsServers, out int[] params, out int[] stats, out int[] wait_for_pending_req_timeout_count);
+  void startPrefix64Discovery(int netId);
+  void stopPrefix64Discovery(int netId);
+  @utf8InCpp String getPrefix64(int netId);
+  void createNetworkCache(int netId);
+  void destroyNetworkCache(int netId);
+  void setLogSeverity(int logSeverity);
+  const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
+  const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
+  const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
+  const int RESOLVER_PARAMS_MAX_SAMPLES = 3;
+  const int RESOLVER_PARAMS_BASE_TIMEOUT_MSEC = 4;
+  const int RESOLVER_PARAMS_RETRY_COUNT = 5;
+  const int RESOLVER_PARAMS_COUNT = 6;
+  const int RESOLVER_STATS_SUCCESSES = 0;
+  const int RESOLVER_STATS_ERRORS = 1;
+  const int RESOLVER_STATS_TIMEOUTS = 2;
+  const int RESOLVER_STATS_INTERNAL_ERRORS = 3;
+  const int RESOLVER_STATS_RTT_AVG = 4;
+  const int RESOLVER_STATS_LAST_SAMPLE_TIME = 5;
+  const int RESOLVER_STATS_USABLE = 6;
+  const int RESOLVER_STATS_COUNT = 7;
+  const int DNS_RESOLVER_LOG_VERBOSE = 0;
+  const int DNS_RESOLVER_LOG_DEBUG = 1;
+  const int DNS_RESOLVER_LOG_INFO = 2;
+  const int DNS_RESOLVER_LOG_WARNING = 3;
+  const int DNS_RESOLVER_LOG_ERROR = 4;
+}
diff --git a/resolv/aidl/dnsresolver/2/android/net/ResolverParamsParcel.aidl b/resolv/aidl/dnsresolver/2/android/net/ResolverParamsParcel.aidl
new file mode 100644
index 0000000..a37f938
--- /dev/null
+++ b/resolv/aidl/dnsresolver/2/android/net/ResolverParamsParcel.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 ResolverParamsParcel {
+  int netId;
+  int sampleValiditySeconds;
+  int successThreshold;
+  int minSamples;
+  int maxSamples;
+  int baseTimeoutMsec;
+  int retryCount;
+  @utf8InCpp String[] servers;
+  @utf8InCpp String[] domains;
+  @utf8InCpp String tlsName;
+  @utf8InCpp String[] tlsServers;
+  @utf8InCpp String[] tlsFingerprints;
+}
diff --git a/resolv/binder/android/net/IDnsResolver.aidl b/resolv/binder/android/net/IDnsResolver.aidl
new file mode 100644
index 0000000..f6f4092
--- /dev/null
+++ b/resolv/binder/android/net/IDnsResolver.aidl
@@ -0,0 +1,164 @@
+/**
+ * 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 android.net;
+
+import android.net.ResolverParamsParcel;
+import android.net.metrics.INetdEventListener;
+
+/** {@hide} */
+interface IDnsResolver {
+    /**
+     * Returns true if the service is responding.
+     */
+    boolean isAlive();
+
+   /**
+    * Register event listener
+    * DnsResolver supports multiple event listeners, but only one per unique address of the
+    * binder interface. A newer listener won't be registered if DnsResolver has an old one on
+    * the same address of the binder interface.
+    *
+    * @param listener event listener to register.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    void registerEventListener(INetdEventListener listener);
+
+    // TODO: Delete these from the public interface
+    // Array indices for resolver parameters.
+    const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
+    const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
+    const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
+    const int RESOLVER_PARAMS_MAX_SAMPLES = 3;
+    const int RESOLVER_PARAMS_BASE_TIMEOUT_MSEC = 4;
+    const int RESOLVER_PARAMS_RETRY_COUNT = 5;
+    const int RESOLVER_PARAMS_COUNT = 6;
+
+    /**
+     * Sets the name servers, search domains and resolver params for the given network. Flushes the
+     * cache as needed (i.e. when the servers or the number of samples to store changes).
+     *
+     * @param resolverParams the resolver parameters to be wrapped into parcel.
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void setResolverConfiguration(in ResolverParamsParcel resolverParams);
+
+    // Array indices for resolver stats.
+    const int RESOLVER_STATS_SUCCESSES = 0;
+    const int RESOLVER_STATS_ERRORS = 1;
+    const int RESOLVER_STATS_TIMEOUTS = 2;
+    const int RESOLVER_STATS_INTERNAL_ERRORS = 3;
+    const int RESOLVER_STATS_RTT_AVG = 4;
+    const int RESOLVER_STATS_LAST_SAMPLE_TIME = 5;
+    const int RESOLVER_STATS_USABLE = 6;
+    const int RESOLVER_STATS_COUNT = 7;
+
+    /**
+     * Retrieves the name servers, search domains and resolver stats associated with the given
+     * network ID.
+     *
+     * @param netId the network ID of the network for which information should be retrieved.
+     * @param servers the DNS servers that are currently configured for the network.
+     * @param domains the search domains currently configured.
+     * @param tlsServers the DNS-over-TLS servers that are currently configured for the network.
+     * @param params the resolver parameters configured, i.e. the contents of __res_params in order.
+     * @param stats the stats for each server in the order specified by RESOLVER_STATS_XXX
+     *         constants, serialized as an int array. The contents of this array are the number of
+     *         <ul>
+     *           <li> successes,
+     *           <li> errors,
+     *           <li> timeouts,
+     *           <li> internal errors,
+     *           <li> the RTT average,
+     *           <li> the time of the last recorded sample,
+     *           <li> and an integer indicating whether the server is usable (1) or broken (0).
+     *         </ul>
+     *         in this order. For example, the timeout counter for server N is stored at position
+     *         RESOLVER_STATS_COUNT*N + RESOLVER_STATS_TIMEOUTS
+     * @param wait_for_pending_req_timeout_count an internal counter used to count the number of
+     *        timeouts while resolver is handling concurrent DNS queries on the same hostname.
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     *
+     * TODO: Consider replacing stats and params with parcelables.
+     */
+    void getResolverInfo(int netId, out @utf8InCpp String[] servers,
+            out @utf8InCpp String[] domains, out @utf8InCpp String[] tlsServers, out int[] params,
+            out int[] stats, out int[] wait_for_pending_req_timeout_count);
+
+    /**
+     * Starts NAT64 prefix discovery on the given network.
+     *
+     * @param netId the netId to start prefix discovery on.
+     */
+    void startPrefix64Discovery(int netId);
+
+    /**
+     * Stops NAT64 prefix discovery on the given network.
+     *
+     * @param netId the netId to stop prefix discovery on.
+     */
+    void stopPrefix64Discovery(int netId);
+
+    /**
+     * Get NAT64 prefix in format Pref64::/n which is described in RFC6147 section 2. This
+     * interface is used for internal test only. Don't use it for other purposes because doing so
+     * would cause race conditions with the NAT64 prefix notifications.
+     *
+     * @param netId the network ID of the network to get the prefix
+     * @return the NAT64 prefix if the query operation was successful
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the the failure.
+     *
+     * TODO: Remove this once the tests have been updated to listen for onNat64PrefixEvent.
+     */
+    @utf8InCpp String getPrefix64(int netId);
+
+    /**
+     * Create cache for the given network.
+     *
+     * @param netId the network ID of the network to create.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void createNetworkCache(int netId);
+
+    /**
+     * Destroy cache for the given network.
+     *
+     * @param netId the network ID of the network to destroy.
+     */
+    void destroyNetworkCache(int netId);
+
+    // Refer to enum LogSeverity from system/core/base/include/android-base/logging.h
+    const int DNS_RESOLVER_LOG_VERBOSE = 0;
+    const int DNS_RESOLVER_LOG_DEBUG = 1;
+    const int DNS_RESOLVER_LOG_INFO = 2;
+    const int DNS_RESOLVER_LOG_WARNING = 3;
+    const int DNS_RESOLVER_LOG_ERROR = 4;
+
+    /**
+     * Set DNS resolver log severity
+     *
+     * @param logSeverity print log in "VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR".
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         POSIX errno.
+     */
+    void setLogSeverity(int logSeverity);
+}
diff --git a/resolv/binder/android/net/ResolverParamsParcel.aidl b/resolv/binder/android/net/ResolverParamsParcel.aidl
new file mode 100644
index 0000000..d25880f
--- /dev/null
+++ b/resolv/binder/android/net/ResolverParamsParcel.aidl
@@ -0,0 +1,86 @@
+/*
+ * 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 android.net;
+
+/**
+ * Configuration for a resolver parameters.
+ *
+ * {@hide}
+ */
+parcelable ResolverParamsParcel {
+    /**
+     * The network ID of the network for which information should be configured.
+     */
+    int netId;
+
+    /**
+     * Sample lifetime in seconds.
+     */
+    int sampleValiditySeconds;
+
+    /**
+     * Use to judge if the server is considered broken.
+     */
+    int successThreshold;
+
+    /**
+     * Min. # samples.
+     */
+    int minSamples;
+
+    /**
+     * Max # samples.
+     */
+    int maxSamples;
+
+    /**
+     * Retransmission interval in milliseconds.
+     */
+    int baseTimeoutMsec;
+
+    /**
+     * Number of retries.
+     */
+    int retryCount;
+
+    /**
+     * The DNS servers to configure for the network.
+     */
+    @utf8InCpp String[] servers;
+
+    /**
+     * The search domains to configure.
+     */
+    @utf8InCpp String[] domains;
+
+    /**
+     * The TLS subject name to require for all servers, or empty if there is none.
+     */
+    @utf8InCpp String   tlsName;
+
+    /**
+     * The DNS servers to configure for strict mode Private DNS.
+     */
+    @utf8InCpp String[] tlsServers;
+
+    /**
+     * An array containing TLS public key fingerprints (pins) of which each server must match
+     * at least one, or empty if there are no pinned keys.
+     */
+    // DEPRECATED: remove tlsFingerprints in new code
+    @utf8InCpp String[] tlsFingerprints;
+}
diff --git a/resolv/dns_responder/Android.bp b/resolv/dns_responder/Android.bp
new file mode 100644
index 0000000..4c28c21
--- /dev/null
+++ b/resolv/dns_responder/Android.bp
@@ -0,0 +1,24 @@
+cc_library_static {
+    name: "libnetd_test_dnsresponder",
+    defaults: ["netd_defaults"],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libnetd_client",
+        "libnetdutils",
+        "libssl",
+        "dnsresolver_aidl_interface-V2-cpp",
+        "netd_aidl_interface-V2-cpp",
+    ],
+    static_libs: ["libutils"],
+    include_dirs: [
+        "system/netd/server",
+    ],
+    srcs: [
+        "dns_responder.cpp",
+        "dns_responder_client.cpp",
+        "dns_tls_frontend.cpp",
+    ],
+    export_include_dirs: ["."],
+}
diff --git a/tests/dns_responder/dns_responder.cpp b/resolv/dns_responder/dns_responder.cpp
similarity index 67%
rename from tests/dns_responder/dns_responder.cpp
rename to resolv/dns_responder/dns_responder.cpp
index bcea9a8..5047255 100644
--- a/tests/dns_responder/dns_responder.cpp
+++ b/resolv/dns_responder/dns_responder.cpp
@@ -24,30 +24,41 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/epoll.h>
+#include <sys/eventfd.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <set>
 
 #include <iostream>
 #include <vector>
 
 #define LOG_TAG "DNSResponder"
+#include <android-base/strings.h>
 #include <log/log.h>
 #include <netdutils/SocketOption.h>
 
+#include "NetdConstants.h"
+
 using android::netdutils::enableSockopt;
 
 namespace test {
 
 std::string errno2str() {
     char error_msg[512] = { 0 };
-    if (strerror_r(errno, error_msg, sizeof(error_msg)))
-        return std::string();
-    return std::string(error_msg);
+    // It actually calls __gnu_strerror_r() which returns the type |char*| rather than |int|.
+    // PLOG is an option though it requires lots of changes from ALOGx() to LOG(x).
+    return strerror_r(errno, error_msg, sizeof(error_msg));
 }
 
 #define APLOGI(fmt, ...) ALOGI(fmt ": [%d] %s", __VA_ARGS__, errno, errno2str().c_str())
 
+#if 0
+#define DBGLOG(fmt, ...) ALOGI(fmt, __VA_ARGS__)
+#else
+#define DBGLOG(fmt, ...)
+#endif
+
 std::string str2hex(const char* buffer, size_t len) {
     std::string str(len*2, '\0');
     for (size_t i = 0 ; i < len ; ++i) {
@@ -140,7 +151,6 @@
     static const char* kUnknownStr{ "UNKNOWN" };
     if (it == kClassStrs.end()) return kUnknownStr;
     return it->second;
-    return "unknown";
 }
 
 struct DNSName {
@@ -501,7 +511,7 @@
                                   unsigned* qdcount, unsigned* ancount,
                                   unsigned* nscount, unsigned* arcount) {
     if (buffer + sizeof(Header) > buffer_end)
-        return 0;
+        return nullptr;
     const auto& header = *reinterpret_cast<const Header*>(buffer);
     // bytes 0-1
     id = ntohs(header.id);
@@ -525,41 +535,36 @@
 
 /* DNS responder */
 
-DNSResponder::DNSResponder(std::string listen_address,
-                           std::string listen_service, int poll_timeout_ms,
-                           uint16_t error_rcode, double response_probability) :
-    listen_address_(std::move(listen_address)), listen_service_(std::move(listen_service)),
-    poll_timeout_ms_(poll_timeout_ms), error_rcode_(error_rcode),
-    response_probability_(response_probability),
-    fail_on_edns_(false),
-    socket_(-1), epoll_fd_(-1), terminate_(false) { }
+DNSResponder::DNSResponder(std::string listen_address, std::string listen_service,
+                           int poll_timeout_ms, ns_rcode error_rcode)
+    : listen_address_(std::move(listen_address)),
+      listen_service_(std::move(listen_service)),
+      poll_timeout_ms_(poll_timeout_ms),
+      error_rcode_(error_rcode) {}
 
 DNSResponder::~DNSResponder() {
     stopServer();
 }
 
-void DNSResponder::addMapping(const char* name, ns_type type,
-        const char* addr) {
-    std::lock_guard<std::mutex> lock(mappings_mutex_);
+void DNSResponder::addMapping(const std::string& name, ns_type type, const std::string& addr) {
+    std::lock_guard lock(mappings_mutex_);
     auto it = mappings_.find(QueryKey(name, type));
     if (it != mappings_.end()) {
         ALOGI("Overwriting mapping for (%s, %s), previous address %s, new "
-            "address %s", name, dnstype2str(type), it->second.c_str(),
-            addr);
+              "address %s",
+              name.c_str(), dnstype2str(type), it->second.c_str(), addr.c_str());
         it->second = addr;
         return;
     }
-    mappings_.emplace(std::piecewise_construct,
-                      std::forward_as_tuple(name, type),
-                      std::forward_as_tuple(addr));
+    mappings_.try_emplace({name, type}, addr);
 }
 
-void DNSResponder::removeMapping(const char* name, ns_type type) {
-    std::lock_guard<std::mutex> lock(mappings_mutex_);
+void DNSResponder::removeMapping(const std::string& name, ns_type type) {
+    std::lock_guard lock(mappings_mutex_);
     auto it = mappings_.find(QueryKey(name, type));
     if (it != mappings_.end()) {
-        ALOGI("Cannot remove mapping mapping from (%s, %s), not present", name,
-            dnstype2str(type));
+        ALOGI("Cannot remove mapping mapping from (%s, %s), not present", name.c_str(),
+              dnstype2str(type));
         return;
     }
     mappings_.erase(it);
@@ -569,8 +574,12 @@
     response_probability_ = response_probability;
 }
 
+void DNSResponder::setEdns(Edns edns) {
+    edns_ = edns;
+}
+
 bool DNSResponder::running() const {
-    return socket_ != -1;
+    return socket_.get() != -1;
 }
 
 bool DNSResponder::startServer() {
@@ -578,72 +587,66 @@
         ALOGI("server already running");
         return false;
     }
+
+    // Set up UDP socket.
     addrinfo ai_hints{
         .ai_family = AF_UNSPEC,
         .ai_socktype = SOCK_DGRAM,
         .ai_flags = AI_PASSIVE
     };
-    addrinfo* ai_res;
+    addrinfo* ai_res = nullptr;
     int rv = getaddrinfo(listen_address_.c_str(), listen_service_.c_str(),
                          &ai_hints, &ai_res);
+    ScopedAddrinfo ai_res_cleanup(ai_res);
     if (rv) {
         ALOGI("getaddrinfo(%s, %s) failed: %s", listen_address_.c_str(),
             listen_service_.c_str(), gai_strerror(rv));
         return false;
     }
-    int s = -1;
     for (const addrinfo* ai = ai_res ; ai ; ai = ai->ai_next) {
-        s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-        if (s < 0) continue;
-        enableSockopt(s, SOL_SOCKET, SO_REUSEPORT);
-        enableSockopt(s, SOL_SOCKET, SO_REUSEADDR);
-        if (bind(s, ai->ai_addr, ai->ai_addrlen)) {
-            APLOGI("bind failed for socket %d", s);
-            close(s);
-            s = -1;
+        socket_.reset(socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK, ai->ai_protocol));
+        if (socket_.get() < 0) {
+            APLOGI("ignore creating socket %d failed", socket_.get());
             continue;
         }
+        enableSockopt(socket_.get(), SOL_SOCKET, SO_REUSEPORT).ignoreError();
+        enableSockopt(socket_.get(), SOL_SOCKET, SO_REUSEADDR).ignoreError();
         std::string host_str = addr2str(ai->ai_addr, ai->ai_addrlen);
+        if (bind(socket_.get(), ai->ai_addr, ai->ai_addrlen)) {
+            APLOGI("failed to bind UDP %s:%s", host_str.c_str(), listen_service_.c_str());
+            continue;
+        }
         ALOGI("bound to UDP %s:%s", host_str.c_str(), listen_service_.c_str());
         break;
     }
-    freeaddrinfo(ai_res);
-    if (s < 0) {
-        ALOGI("bind() failed");
+
+    // Set up eventfd socket.
+    event_fd_.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
+    if (event_fd_.get() == -1) {
+        APLOGI("failed to create eventfd %d", event_fd_.get());
         return false;
     }
 
-    int flags = fcntl(s, F_GETFL, 0);
-    if (flags < 0) flags = 0;
-    if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
-        APLOGI("fcntl(F_SETFL) failed for socket %d", s);
-        close(s);
+    // Set up epoll socket.
+    epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
+    if (epoll_fd_.get() < 0) {
+        APLOGI("epoll_create1() failed on fd %d", epoll_fd_.get());
         return false;
     }
 
-    int ep_fd = epoll_create(1);
-    if (ep_fd < 0) {
-        char error_msg[512] = { 0 };
-        if (strerror_r(errno, error_msg, sizeof(error_msg)))
-            strncpy(error_msg, "UNKNOWN", sizeof(error_msg));
-        APLOGI("epoll_create() failed: %s", error_msg);
-        close(s);
+    ALOGI("adding socket %d to epoll", socket_.get());
+    if (!addFd(socket_.get(), EPOLLIN)) {
+        ALOGE("failed to add the socket %d to epoll", socket_.get());
         return false;
     }
-    epoll_event ev;
-    ev.events = EPOLLIN;
-    ev.data.fd = s;
-    if (epoll_ctl(ep_fd, EPOLL_CTL_ADD, s, &ev) < 0) {
-        APLOGI("epoll_ctl() failed for socket %d", s);
-        close(ep_fd);
-        close(s);
+    ALOGI("adding eventfd %d to epoll", event_fd_.get());
+    if (!addFd(event_fd_.get(), EPOLLIN)) {
+        ALOGE("failed to add the eventfd %d to epoll", event_fd_.get());
         return false;
     }
 
-    epoll_fd_ = ep_fd;
-    socket_ = s;
     {
-        std::lock_guard<std::mutex> lock(update_mutex_);
+        std::lock_guard lock(update_mutex_);
         handler_thread_ = std::thread(&DNSResponder::requestHandler, this);
     }
     ALOGI("server started successfully");
@@ -651,80 +654,62 @@
 }
 
 bool DNSResponder::stopServer() {
-    std::lock_guard<std::mutex> lock(update_mutex_);
+    std::lock_guard lock(update_mutex_);
     if (!running()) {
         ALOGI("server not running");
         return false;
     }
-    if (terminate_) {
-        ALOGI("LOGIC ERROR");
+    ALOGI("stopping server");
+    if (!sendToEventFd()) {
         return false;
     }
-    ALOGI("stopping server");
-    terminate_ = true;
     handler_thread_.join();
-    close(epoll_fd_);
-    close(socket_);
-    terminate_ = false;
-    socket_ = -1;
+    epoll_fd_.reset();
+    socket_.reset();
     ALOGI("server stopped successfully");
     return true;
 }
 
 std::vector<std::pair<std::string, ns_type >> DNSResponder::queries() const {
-    std::lock_guard<std::mutex> lock(queries_mutex_);
+    std::lock_guard lock(queries_mutex_);
     return queries_;
 }
 
+std::string DNSResponder::dumpQueries() const {
+    std::lock_guard lock(queries_mutex_);
+    std::string out;
+    for (const auto& q : queries_) {
+        out += "{\"" + q.first + "\", " + std::to_string(q.second) + "} ";
+    }
+    return out;
+}
+
 void DNSResponder::clearQueries() {
-    std::lock_guard<std::mutex> lock(queries_mutex_);
+    std::lock_guard lock(queries_mutex_);
     queries_.clear();
 }
 
 void DNSResponder::requestHandler() {
-    epoll_event evs[1];
-    while (!terminate_) {
-        int n = epoll_wait(epoll_fd_, evs, 1, poll_timeout_ms_);
+    epoll_event evs[EPOLL_MAX_EVENTS];
+    while (true) {
+        int n = epoll_wait(epoll_fd_.get(), evs, EPOLL_MAX_EVENTS, poll_timeout_ms_);
         if (n == 0) continue;
         if (n < 0) {
-            ALOGI("epoll_wait() failed");
-            // TODO(imaipi): terminate on error.
+            APLOGI("epoll_wait() failed, n=%d", n);
             return;
         }
-        char buffer[4096];
-        sockaddr_storage sa;
-        socklen_t sa_len = sizeof(sa);
-        ssize_t len;
-        do {
-            len = recvfrom(socket_, buffer, sizeof(buffer), 0,
-                           (sockaddr*) &sa, &sa_len);
-        } while (len < 0 && (errno == EAGAIN || errno == EINTR));
-        if (len <= 0) {
-            ALOGI("recvfrom() failed");
-            continue;
-        }
-        ALOGI("read %zd bytes", len);
-        char response[4096];
-        size_t response_len = sizeof(response);
-        if (handleDNSRequest(buffer, len, response, &response_len) &&
-            response_len > 0) {
-            len = sendto(socket_, response, response_len, 0,
-                         reinterpret_cast<const sockaddr*>(&sa), sa_len);
-            std::string host_str =
-                addr2str(reinterpret_cast<const sockaddr*>(&sa), sa_len);
-            if (len > 0) {
-                ALOGI("sent %zu bytes to %s", len, host_str.c_str());
-            } else {
-                APLOGI("sendto() failed for %s", host_str.c_str());
-            }
-            // Test that the response is actually a correct DNS message.
-            const char* response_end = response + len;
-            DNSHeader header;
-            const char* cur = header.read(response, response_end);
-            if (cur == nullptr) ALOGI("response is flawed");
 
-        } else {
-            ALOGI("not responding");
+        for (int i = 0; i < n; i++) {
+            const int fd = evs[i].data.fd;
+            const uint32_t events = evs[i].events;
+            if (fd == event_fd_.get() && (events & (EPOLLIN | EPOLLERR))) {
+                handleEventFd();
+                return;
+            } else if (fd == socket_.get() && (events & (EPOLLIN | EPOLLERR))) {
+                handleQuery();
+            } else {
+                ALOGW("unexpected epoll events 0x%x on fd %d", events, fd);
+            }
         }
     }
 }
@@ -732,7 +717,7 @@
 bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len,
                                     char* response, size_t* response_len)
                                     const {
-    ALOGI("request: '%s'", str2hex(buffer, len).c_str());
+    DBGLOG("request: '%s'", str2hex(buffer, len).c_str());
     const char* buffer_end = buffer + len;
     DNSHeader header;
     const char* cur = header.read(buffer, buffer_end);
@@ -760,14 +745,24 @@
         return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response,
                                  response_len);
     }
-    if (!header.additionals.empty() && fail_on_edns_) {
+
+    if (edns_ == Edns::FORMERR_UNCOND) {
+        ALOGI("force to return RCODE FORMERR");
+        return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response, response_len);
+    }
+
+    if (!header.additionals.empty() && edns_ != Edns::ON) {
         ALOGI("DNS request has an additional section (assumed EDNS). "
-              "Simulating an ancient (pre-EDNS) server.");
-        return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response,
-                                 response_len);
+              "Simulating an ancient (pre-EDNS) server, and returning %s",
+              edns_ == Edns::FORMERR_ON_EDNS ? "RCODE FORMERR." : "no response.");
+        if (edns_ == Edns::FORMERR_ON_EDNS) {
+            return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response, response_len);
+        }
+        // No response.
+        return false;
     }
     {
-        std::lock_guard<std::mutex> lock(queries_mutex_);
+        std::lock_guard lock(queries_mutex_);
         for (const DNSQuestion& question : header.questions) {
             queries_.push_back(make_pair(question.qname.name,
                                          ns_type(question.qtype)));
@@ -776,10 +771,15 @@
 
     // Ignore requests with the preset probability.
     auto constexpr bound = std::numeric_limits<unsigned>::max();
-    if (arc4random_uniform(bound) > bound*response_probability_) {
-        ALOGI("returning SRVFAIL in accordance with probability distribution");
-        return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response,
-                                 response_len);
+    if (arc4random_uniform(bound) > bound * response_probability_) {
+        if (error_rcode_ < 0) {
+            ALOGI("Returning no response");
+            return false;
+        } else {
+            ALOGI("returning RCODE %d in accordance with probability distribution",
+                  static_cast<int>(error_rcode_));
+            return makeErrorResponse(&header, error_rcode_, response, response_len);
+        }
     }
 
     for (const DNSQuestion& question : header.questions) {
@@ -789,11 +789,12 @@
             return makeErrorResponse(&header, ns_rcode::ns_r_notimpl, response,
                                      response_len);
         }
+
         if (!addAnswerRecords(question, &header.answers)) {
-            return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response,
-                                     response_len);
+            return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response, response_len);
         }
     }
+
     header.qr = true;
     char* response_cur = header.write(response, response + *response_len);
     if (response_cur == nullptr) {
@@ -805,38 +806,96 @@
 
 bool DNSResponder::addAnswerRecords(const DNSQuestion& question,
                                     std::vector<DNSRecord>* answers) const {
-    std::lock_guard<std::mutex> guard(mappings_mutex_);
-    auto it = mappings_.find(QueryKey(question.qname.name, question.qtype));
-    if (it == mappings_.end()) {
+    std::lock_guard guard(mappings_mutex_);
+    std::string rname = question.qname.name;
+    std::vector<int> rtypes;
+
+    if (question.qtype == ns_type::ns_t_a || question.qtype == ns_type::ns_t_aaaa)
+        rtypes.push_back(ns_type::ns_t_cname);
+    rtypes.push_back(question.qtype);
+    for (int rtype : rtypes) {
+        std::set<std::string> cnames_Loop;
+        std::unordered_map<QueryKey, std::string, QueryKeyHash>::const_iterator it;
+        while ((it = mappings_.find(QueryKey(rname, rtype))) != mappings_.end()) {
+            if (rtype == ns_type::ns_t_cname) {
+                // When detect CNAME infinite loops by cnames_Loop, it won't save the duplicate one.
+                // As following, the query will stop on loop3 by detecting the same cname.
+                // loop1.{"a.xxx.com", ns_type::ns_t_cname, "b.xxx.com"}(insert in answer record)
+                // loop2.{"b.xxx.com", ns_type::ns_t_cname, "a.xxx.com"}(insert in answer record)
+                // loop3.{"a.xxx.com", ns_type::ns_t_cname, "b.xxx.com"}(When the same cname record
+                //   is found in cnames_Loop already, break the query loop.)
+                if (cnames_Loop.find(it->first.name) != cnames_Loop.end()) break;
+                cnames_Loop.insert(it->first.name);
+            }
+            DNSRecord record{
+                    .name = {.name = it->first.name},
+                    .rtype = it->first.type,
+                    .rclass = ns_class::ns_c_in,
+                    .ttl = 5,  // seconds
+            };
+            fillAnswerRdata(it->second, record);
+            answers->push_back(std::move(record));
+            if (rtype != ns_type::ns_t_cname) break;
+            rname = it->second;
+        }
+    }
+
+    if (answers->size() == 0) {
         // TODO(imaipi): handle correctly
         ALOGI("no mapping found for %s %s, lazily refusing to add an answer",
-            question.qname.name.c_str(), dnstype2str(question.qtype));
-        return true;
+              question.qname.name.c_str(), dnstype2str(question.qtype));
     }
-    ALOGI("mapping found for %s %s: %s", question.qname.name.c_str(),
-        dnstype2str(question.qtype), it->second.c_str());
-    DNSRecord record;
-    record.name = question.qname;
-    record.rtype = question.qtype;
-    record.rclass = ns_class::ns_c_in;
-    record.ttl = 5;  // seconds
-    if (question.qtype == ns_type::ns_t_a) {
+
+    return true;
+}
+
+bool DNSResponder::fillAnswerRdata(const std::string& rdatastr, DNSRecord& record) const {
+    if (record.rtype == ns_type::ns_t_a) {
         record.rdata.resize(4);
-        if (inet_pton(AF_INET, it->second.c_str(), record.rdata.data()) != 1) {
-            ALOGI("inet_pton(AF_INET, %s) failed", it->second.c_str());
+        if (inet_pton(AF_INET, rdatastr.c_str(), record.rdata.data()) != 1) {
+            ALOGI("inet_pton(AF_INET, %s) failed", rdatastr.c_str());
             return false;
         }
-    } else if (question.qtype == ns_type::ns_t_aaaa) {
+    } else if (record.rtype == ns_type::ns_t_aaaa) {
         record.rdata.resize(16);
-        if (inet_pton(AF_INET6, it->second.c_str(), record.rdata.data()) != 1) {
-            ALOGI("inet_pton(AF_INET6, %s) failed", it->second.c_str());
+        if (inet_pton(AF_INET6, rdatastr.c_str(), record.rdata.data()) != 1) {
+            ALOGI("inet_pton(AF_INET6, %s) failed", rdatastr.c_str());
             return false;
         }
+    } else if ((record.rtype == ns_type::ns_t_ptr) || (record.rtype == ns_type::ns_t_cname)) {
+        constexpr char delimiter = '.';
+        std::string name = rdatastr;
+        std::vector<char> rdata;
+
+        // Generating PTRDNAME field(section 3.3.12) or CNAME field(section 3.3.1) in rfc1035.
+        // The "name" should be an absolute domain name which ends in a dot.
+        if (name.back() != delimiter) {
+            ALOGI("invalid absolute domain name");
+            return false;
+        }
+        name.pop_back();  // remove the dot in tail
+        for (const std::string& label : android::base::Split(name, {delimiter})) {
+            // The length of label is limited to 63 octets or less. See RFC 1035 section 3.1.
+            if (label.length() == 0 || label.length() > 63) {
+                ALOGI("invalid label length");
+                return false;
+            }
+
+            rdata.push_back(label.length());
+            rdata.insert(rdata.end(), label.begin(), label.end());
+        }
+        rdata.push_back(0);  // Length byte of zero terminates the label list
+
+        // The length of domain name is limited to 255 octets or less. See RFC 1035 section 3.1.
+        if (rdata.size() > 255) {
+            ALOGI("invalid name length");
+            return false;
+        }
+        record.rdata = move(rdata);
     } else {
-        ALOGI("unhandled qtype %s", dnstype2str(question.qtype));
+        ALOGI("unhandled qtype %s", dnstype2str(record.rtype));
         return false;
     }
-    answers->push_back(std::move(record));
     return true;
 }
 
@@ -854,5 +913,82 @@
     return true;
 }
 
-}  // namespace test
+void DNSResponder::setDeferredResp(bool deferred_resp) {
+    std::lock_guard<std::mutex> guard(cv_mutex_for_deferred_resp_);
+    deferred_resp_ = deferred_resp;
+    if (!deferred_resp_) {
+        cv_for_deferred_resp_.notify_one();
+    }
+}
 
+bool DNSResponder::addFd(int fd, uint32_t events) {
+    epoll_event ev;
+    ev.events = events;
+    ev.data.fd = fd;
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, fd, &ev) < 0) {
+        APLOGI("epoll_ctl() for socket %d failed", fd);
+        return false;
+    }
+    return true;
+}
+
+void DNSResponder::handleQuery() {
+    char buffer[4096];
+    sockaddr_storage sa;
+    socklen_t sa_len = sizeof(sa);
+    ssize_t len;
+    do {
+        len = recvfrom(socket_.get(), buffer, sizeof(buffer), 0, (sockaddr*)&sa, &sa_len);
+    } while (len < 0 && (errno == EAGAIN || errno == EINTR));
+    if (len <= 0) {
+        APLOGI("recvfrom() failed, len=%zu", len);
+        return;
+    }
+    DBGLOG("read %zd bytes", len);
+    std::lock_guard lock(cv_mutex_);
+    char response[4096];
+    size_t response_len = sizeof(response);
+    if (handleDNSRequest(buffer, len, response, &response_len) && response_len > 0) {
+        // place wait_for after handleDNSRequest() so we can check the number of queries in
+        // test case before it got responded.
+        std::unique_lock guard(cv_mutex_for_deferred_resp_);
+        cv_for_deferred_resp_.wait(
+                guard, [this]() REQUIRES(cv_mutex_for_deferred_resp_) { return !deferred_resp_; });
+
+        len = sendto(socket_.get(), response, response_len, 0,
+                     reinterpret_cast<const sockaddr*>(&sa), sa_len);
+        std::string host_str = addr2str(reinterpret_cast<const sockaddr*>(&sa), sa_len);
+        if (len > 0) {
+            DBGLOG("sent %zu bytes to %s", len, host_str.c_str());
+        } else {
+            APLOGI("sendto() failed for %s", host_str.c_str());
+        }
+        // Test that the response is actually a correct DNS message.
+        const char* response_end = response + len;
+        DNSHeader header;
+        const char* cur = header.read(response, response_end);
+        if (cur == nullptr) ALOGW("response is flawed");
+    } else {
+        ALOGW("not responding");
+    }
+    cv.notify_one();
+    return;
+}
+
+bool DNSResponder::sendToEventFd() {
+    const uint64_t data = 1;
+    if (const ssize_t rt = write(event_fd_.get(), &data, sizeof(data)); rt != sizeof(data)) {
+        APLOGI("failed to write eventfd, rt=%zd", rt);
+        return false;
+    }
+    return true;
+}
+
+void DNSResponder::handleEventFd() {
+    int64_t data;
+    if (const ssize_t rt = read(event_fd_.get(), &data, sizeof(data)); rt != sizeof(data)) {
+        APLOGI("ignore reading eventfd failed, rt=%zd", rt);
+    }
+}
+
+}  // namespace test
diff --git a/resolv/dns_responder/dns_responder.h b/resolv/dns_responder/dns_responder.h
new file mode 100644
index 0000000..99b601f
--- /dev/null
+++ b/resolv/dns_responder/dns_responder.h
@@ -0,0 +1,186 @@
+/*
+ * 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 requied 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 DNS_RESPONDER_H
+#define DNS_RESPONDER_H
+
+#include <arpa/nameser.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+#include "android-base/unique_fd.h"
+
+namespace test {
+
+struct DNSHeader;
+struct DNSQuestion;
+struct DNSRecord;
+
+inline const std::string kDefaultListenAddr = "127.0.0.3";
+inline const std::string kDefaultListenService = "53";
+inline const int kDefaultPollTimoutMillis = -1;
+
+/*
+ * Simple DNS responder, which replies to queries with the registered response
+ * for that type. Class is assumed to be IN. If no response is registered, the
+ * default error response code is returned.
+ */
+class DNSResponder {
+  public:
+    DNSResponder(std::string listen_address = kDefaultListenAddr,
+                 std::string listen_service = kDefaultListenService,
+                 int poll_timeout_ms = kDefaultPollTimoutMillis,
+                 ns_rcode error_rcode = ns_rcode::ns_r_servfail);
+    ~DNSResponder();
+
+    enum class Edns : uint8_t {
+        ON,
+        FORMERR_ON_EDNS, // DNS server not supporting EDNS will reply FORMERR.
+        FORMERR_UNCOND,  // DNS server reply FORMERR unconditionally
+        DROP             // DNS server not supporting EDNS will not do any response.
+    };
+
+    void addMapping(const std::string& name, ns_type type, const std::string& addr);
+    void removeMapping(const std::string& name, ns_type type);
+    void setResponseProbability(double response_probability);
+    void setEdns(Edns edns);
+    bool running() const;
+    bool startServer();
+    bool stopServer();
+    const std::string& listen_address() const {
+        return listen_address_;
+    }
+    const std::string& listen_service() const {
+        return listen_service_;
+    }
+    std::vector<std::pair<std::string, ns_type>> queries() const;
+    std::string dumpQueries() const;
+    void clearQueries();
+    std::condition_variable& getCv() { return cv; }
+    std::mutex& getCvMutex() { return cv_mutex_; }
+    void setDeferredResp(bool deferred_resp);
+
+  private:
+    // Key used for accessing mappings.
+    struct QueryKey {
+        std::string name;
+        unsigned type;
+
+        QueryKey(std::string n, unsigned t) : name(move(n)), type(t) {}
+        bool operator == (const QueryKey& o) const {
+            return name == o.name && type == o.type;
+        }
+        bool operator < (const QueryKey& o) const {
+            if (name < o.name) return true;
+            if (name > o.name) return false;
+            return type < o.type;
+        }
+    };
+
+    struct QueryKeyHash {
+        size_t operator() (const QueryKey& key) const {
+            return std::hash<std::string>()(key.name) +
+                   static_cast<size_t>(key.type);
+        }
+    };
+
+    void requestHandler();
+
+    // Parses and generates a response message for incoming DNS requests.
+    // Returns false to ignore the request, which might be due to either parsing error
+    // or unresponsiveness.
+    bool handleDNSRequest(const char* buffer, ssize_t buffer_len,
+                          char* response, size_t* response_len) const;
+
+    bool addAnswerRecords(const DNSQuestion& question, std::vector<DNSRecord>* answers) const;
+
+    bool fillAnswerRdata(const std::string& rdatastr, DNSRecord& record) const;
+
+    bool generateErrorResponse(DNSHeader* header, ns_rcode rcode,
+                               char* response, size_t* response_len) const;
+    bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
+                           size_t* response_len) const;
+
+    // Add a new file descriptor to be polled by the handler thread.
+    bool addFd(int fd, uint32_t events);
+
+    // Read the query sent from the client and send the answer back to the client. It
+    // makes sure the I/O communicated with the client is correct.
+    void handleQuery();
+
+    // Trigger the handler thread to terminate.
+    bool sendToEventFd();
+
+    // Used in the handler thread for the termination signal.
+    void handleEventFd();
+
+    // Address and service to listen on, currently limited to UDP.
+    const std::string listen_address_;
+    const std::string listen_service_;
+    // epoll_wait() timeout in ms.
+    const int poll_timeout_ms_;
+    // Error code to return for requests for an unknown name.
+    const ns_rcode error_rcode_;
+    // Probability that a valid response is being sent instead of being sent
+    // instead of returning error_rcode_.
+    std::atomic<double> response_probability_ = 1.0;
+    // Maximum number of fds for epoll.
+    const int EPOLL_MAX_EVENTS = 2;
+
+    // Control how the DNS server behaves when it receives the requests containing OPT RR.
+    // If it's set Edns::ON, the server can recognize and reply the response; if it's set
+    // Edns::FORMERR_ON_EDNS, the server behaves like an old DNS server that doesn't support EDNS0,
+    // and replying FORMERR; if it's Edns::DROP, the server doesn't support EDNS0 either, and
+    // ignoring the requests.
+    std::atomic<Edns> edns_ = Edns::ON;
+
+    // Mappings from (name, type) to registered response and the
+    // mutex protecting them.
+    std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_
+        GUARDED_BY(mappings_mutex_);
+    mutable std::mutex mappings_mutex_;
+    // Query names received so far and the corresponding mutex.
+    mutable std::vector<std::pair<std::string, ns_type>> queries_
+        GUARDED_BY(queries_mutex_);
+    mutable std::mutex queries_mutex_;
+    // Socket on which the server is listening.
+    android::base::unique_fd socket_;
+    // File descriptor for epoll.
+    android::base::unique_fd epoll_fd_;
+    // Eventfd used to signal for the handler thread termination.
+    android::base::unique_fd event_fd_;
+    // Thread for handling incoming threads.
+    std::thread handler_thread_ GUARDED_BY(update_mutex_);
+    std::mutex update_mutex_;
+    std::condition_variable cv;
+    std::mutex cv_mutex_;
+
+    std::condition_variable cv_for_deferred_resp_;
+    std::mutex cv_mutex_for_deferred_resp_;
+    bool deferred_resp_ GUARDED_BY(cv_mutex_for_deferred_resp_) = false;
+};
+
+}  // namespace test
+
+#endif  // DNS_RESPONDER_H
diff --git a/resolv/dns_responder/dns_responder_client.cpp b/resolv/dns_responder/dns_responder_client.cpp
new file mode 100644
index 0000000..8c71e04
--- /dev/null
+++ b/resolv/dns_responder/dns_responder_client.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "dns_responder_client"
+#include "dns_responder_client.h"
+
+#include <android-base/stringprintf.h>
+#include <utils/Log.h>
+
+// TODO: make this dynamic and stop depending on implementation details.
+#define TEST_OEM_NETWORK "oem29"
+#define TEST_NETID 30
+
+// TODO: move this somewhere shared.
+static const char* ANDROID_DNS_MODE = "ANDROID_DNS_MODE";
+
+using android::base::StringPrintf;
+using android::net::INetd;
+using android::net::ResolverParamsParcel;
+
+void DnsResponderClient::SetupMappings(unsigned num_hosts, const std::vector<std::string>& domains,
+        std::vector<Mapping>* mappings) {
+    mappings->resize(num_hosts * domains.size());
+    auto mappings_it = mappings->begin();
+    for (unsigned i = 0 ; i < num_hosts ; ++i) {
+        for (const auto& domain : domains) {
+            mappings_it->host = StringPrintf("host%u", i);
+            mappings_it->entry = StringPrintf("%s.%s.", mappings_it->host.c_str(),
+                    domain.c_str());
+            mappings_it->ip4 = StringPrintf("192.0.2.%u", i%253 + 1);
+            mappings_it->ip6 = StringPrintf("2001:db8::%x", i%65534 + 1);
+            ++mappings_it;
+        }
+    }
+}
+
+// TODO: Use SetResolverConfiguration() with ResolverParamsParcel struct directly.
+// DEPRECATED: Use SetResolverConfiguration() in new code
+static ResolverParamsParcel makeResolverParamsParcel(
+        int netId, const std::vector<int>& params, const std::vector<std::string>& servers,
+        const std::vector<std::string>& domains, const std::string& tlsHostname,
+        const std::vector<std::string>& tlsServers,
+        const std::vector<std::string>& tlsFingerprints) {
+    using android::net::IDnsResolver;
+    ResolverParamsParcel paramsParcel;
+
+    paramsParcel.netId = netId;
+    paramsParcel.sampleValiditySeconds = params[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY];
+    paramsParcel.successThreshold = params[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD];
+    paramsParcel.minSamples = params[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES];
+    paramsParcel.maxSamples = params[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES];
+    if (params.size() > IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC) {
+        paramsParcel.baseTimeoutMsec = params[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC];
+    } else {
+        paramsParcel.baseTimeoutMsec = 0;
+    }
+    if (params.size() > IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT) {
+        paramsParcel.retryCount = params[IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT];
+    } else {
+        paramsParcel.retryCount = 0;
+    }
+    paramsParcel.servers = servers;
+    paramsParcel.domains = domains;
+    paramsParcel.tlsName = tlsHostname;
+    paramsParcel.tlsServers = tlsServers;
+    paramsParcel.tlsFingerprints = tlsFingerprints;
+
+    return paramsParcel;
+}
+
+bool DnsResponderClient::SetResolversForNetwork(const std::vector<std::string>& servers,
+        const std::vector<std::string>& domains, const std::vector<int>& params) {
+    const auto& resolverParams =
+            makeResolverParamsParcel(TEST_NETID, params, servers, domains, "", {}, {});
+    const auto rv = mDnsResolvSrv->setResolverConfiguration(resolverParams);
+    return rv.isOk();
+}
+
+bool DnsResponderClient::SetResolversWithTls(const std::vector<std::string>& servers,
+        const std::vector<std::string>& domains, const std::vector<int>& params,
+        const std::vector<std::string>& tlsServers,
+        const std::string& name, const std::vector<std::string>& fingerprints) {
+    const auto& resolverParams = makeResolverParamsParcel(TEST_NETID, params, servers, domains,
+                                                          name, tlsServers, fingerprints);
+    const auto rv = mDnsResolvSrv->setResolverConfiguration(resolverParams);
+    if (!rv.isOk()) ALOGI("SetResolversWithTls() -> %s", rv.toString8().c_str());
+    return rv.isOk();
+}
+
+void DnsResponderClient::SetupDNSServers(unsigned num_servers, const std::vector<Mapping>& mappings,
+        std::vector<std::unique_ptr<test::DNSResponder>>* dns,
+        std::vector<std::string>* servers) {
+    const char* listen_srv = "53";
+    dns->resize(num_servers);
+    servers->resize(num_servers);
+    for (unsigned i = 0 ; i < num_servers ; ++i) {
+        auto& server = (*servers)[i];
+        auto& d = (*dns)[i];
+        server = StringPrintf("127.0.0.%u", i + 100);
+        d = std::make_unique<test::DNSResponder>(server, listen_srv, 250, ns_rcode::ns_r_servfail);
+        for (const auto& mapping : mappings) {
+            d->addMapping(mapping.entry.c_str(), ns_type::ns_t_a, mapping.ip4.c_str());
+            d->addMapping(mapping.entry.c_str(), ns_type::ns_t_aaaa, mapping.ip6.c_str());
+        }
+        d->startServer();
+    }
+}
+
+int DnsResponderClient::SetupOemNetwork() {
+    mNetdSrv->networkDestroy(TEST_NETID);
+    mDnsResolvSrv->destroyNetworkCache(TEST_NETID);
+    auto ret = mNetdSrv->networkCreatePhysical(TEST_NETID, INetd::PERMISSION_NONE);
+    if (!ret.isOk()) {
+        fprintf(stderr, "Creating physical network %d failed, %s\n", TEST_NETID,
+                ret.toString8().string());
+        return -1;
+    }
+    ret = mDnsResolvSrv->createNetworkCache(TEST_NETID);
+    if (!ret.isOk()) {
+        fprintf(stderr, "Creating network cache %d failed, %s\n", TEST_NETID,
+                ret.toString8().string());
+        return -1;
+    }
+    setNetworkForProcess(TEST_NETID);
+    if ((unsigned)TEST_NETID != getNetworkForProcess()) {
+        return -1;
+    }
+    return TEST_NETID;
+}
+
+void DnsResponderClient::TearDownOemNetwork(int oemNetId) {
+    if (oemNetId != -1) {
+        mNetdSrv->networkDestroy(oemNetId);
+        mDnsResolvSrv->destroyNetworkCache(oemNetId);
+    }
+}
+
+void DnsResponderClient::SetUp() {
+    // binder setup
+    auto binder = android::defaultServiceManager()->getService(android::String16("netd"));
+    mNetdSrv = android::interface_cast<android::net::INetd>(binder);
+
+    auto resolvBinder =
+            android::defaultServiceManager()->getService(android::String16("dnsresolver"));
+    mDnsResolvSrv = android::interface_cast<android::net::IDnsResolver>(resolvBinder);
+
+    // Ensure resolutions go via proxy.
+    setenv(ANDROID_DNS_MODE, "", 1);
+    mOemNetId = SetupOemNetwork();
+}
+
+void DnsResponderClient::TearDown() {
+    TearDownOemNetwork(mOemNetId);
+}
diff --git a/resolv/dns_responder/dns_responder_client.h b/resolv/dns_responder/dns_responder_client.h
new file mode 100644
index 0000000..17bc961
--- /dev/null
+++ b/resolv/dns_responder/dns_responder_client.h
@@ -0,0 +1,106 @@
+/*
+ * 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 requied 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 DNS_RESPONDER_CLIENT_H
+#define DNS_RESPONDER_CLIENT_H
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cutils/sockets.h>
+
+#include <private/android_filesystem_config.h>
+#include <utils/StrongPointer.h>
+
+#include "NetdClient.h"
+#include "android/net/IDnsResolver.h"
+#include "android/net/INetd.h"
+#include "binder/IServiceManager.h"
+#include "dns_responder.h"
+
+inline const std::vector<std::string> kDefaultServers = {"127.0.0.3"};
+inline const std::vector<std::string> kDefaultSearchDomains = {"example.com"};
+inline const std::vector<int> kDefaultParams = {
+        300,      // sample validity in seconds
+        25,       // success threshod in percent
+        8,    8,  // {MIN,MAX}_SAMPLES
+        1000,     // BASE_TIMEOUT_MSEC
+        2,        // retry count
+};
+
+class DnsResponderClient {
+public:
+    struct Mapping {
+        std::string host;
+        std::string entry;
+        std::string ip4;
+        std::string ip6;
+    };
+
+    virtual ~DnsResponderClient() = default;
+
+    static void SetupMappings(unsigned num_hosts, const std::vector<std::string>& domains,
+            std::vector<Mapping>* mappings);
+
+    bool SetResolversForNetwork(const std::vector<std::string>& servers = kDefaultServers,
+                                const std::vector<std::string>& domains = kDefaultSearchDomains,
+                                const std::vector<int>& params = kDefaultParams);
+
+    bool SetResolversForNetwork(const std::vector<std::string>& servers,
+            const std::vector<std::string>& searchDomains,
+            const std::string& params);
+
+    bool SetResolversWithTls(const std::vector<std::string>& servers,
+            const std::vector<std::string>& searchDomains,
+            const std::vector<int>& params,
+            const std::string& name,
+            const std::vector<std::string>& fingerprints) {
+        // Pass servers as both network-assigned and TLS servers.  Tests can
+        // determine on which server and by which protocol queries arrived.
+        return SetResolversWithTls(servers, searchDomains, params,
+                                   servers, name, fingerprints);
+    }
+
+    bool SetResolversWithTls(const std::vector<std::string>& servers,
+            const std::vector<std::string>& searchDomains,
+            const std::vector<int>& params,
+            const std::vector<std::string>& tlsServers,
+            const std::string& name,
+            const std::vector<std::string>& fingerprints);
+
+    static void SetupDNSServers(unsigned num_servers, const std::vector<Mapping>& mappings,
+            std::vector<std::unique_ptr<test::DNSResponder>>* dns,
+            std::vector<std::string>* servers);
+
+    int SetupOemNetwork();
+
+    void TearDownOemNetwork(int oemNetId);
+
+    virtual void SetUp();
+    virtual void TearDown();
+
+    android::net::IDnsResolver* resolvService() const { return mDnsResolvSrv.get(); }
+    android::net::INetd* netdService() const { return mNetdSrv.get(); }
+
+  private:
+    android::sp<android::net::INetd> mNetdSrv = nullptr;
+    android::sp<android::net::IDnsResolver> mDnsResolvSrv = nullptr;
+    int mOemNetId = -1;
+};
+
+#endif  // DNS_RESPONDER_CLIENT_H
diff --git a/tests/dns_responder/dns_tls_frontend.cpp b/resolv/dns_responder/dns_tls_frontend.cpp
similarity index 68%
rename from tests/dns_responder/dns_tls_frontend.cpp
rename to resolv/dns_responder/dns_tls_frontend.cpp
index d5b581a..83e36ed 100644
--- a/tests/dns_responder/dns_tls_frontend.cpp
+++ b/resolv/dns_responder/dns_tls_frontend.cpp
@@ -16,38 +16,39 @@
 
 #include "dns_tls_frontend.h"
 
-#include <netdb.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/poll.h>
-#include <sys/socket.h>
-#include <sys/types.h>
 #include <arpa/inet.h>
+#include <netdb.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
 #include <openssl/ssl.h>
+#include <sys/eventfd.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #define LOG_TAG "DnsTlsFrontend"
 #include <log/log.h>
 #include <netdutils/SocketOption.h>
 
+#include "NetdConstants.h"  // SHA256_SIZE
+
 using android::netdutils::enableSockopt;
 
 namespace {
 
 // Copied from DnsTlsTransport.
 bool getSPKIDigest(const X509* cert, std::vector<uint8_t>* out) {
-    int spki_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
+    int spki_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), nullptr);
     unsigned char spki[spki_len];
     unsigned char* temp = spki;
     if (spki_len != i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp)) {
         ALOGE("SPKI length mismatch");
         return false;
     }
-    out->resize(test::SHA256_SIZE);
+    out->resize(SHA256_SIZE);
     unsigned int digest_len = 0;
-    int ret = EVP_Digest(spki, spki_len, out->data(), &digest_len, EVP_sha256(), NULL);
+    int ret = EVP_Digest(spki, spki_len, out->data(), &digest_len, EVP_sha256(), nullptr);
     if (ret != 1) {
         ALOGE("Server cert digest extraction failed");
         return false;
@@ -61,9 +62,7 @@
 
 std::string errno2str() {
     char error_msg[512] = { 0 };
-    if (strerror_r(errno, error_msg, sizeof(error_msg)))
-        return std::string();
-    return std::string(error_msg);
+    return strerror_r(errno, error_msg, sizeof(error_msg));
 }
 
 #define APLOGI(fmt, ...) ALOGI(fmt ": [%d] %s", __VA_ARGS__, errno, errno2str().c_str())
@@ -92,7 +91,7 @@
         ALOGE("RSA_new failed");
         return nullptr;
     }
-    if (!RSA_generate_key_ex(rsa.get(), 2048, e.get(), NULL)) {
+    if (!RSA_generate_key_ex(rsa.get(), 2048, e.get(), nullptr)) {
         ALOGE("RSA_generate_key_ex failed");
         return nullptr;
     }
@@ -195,68 +194,73 @@
         .ai_socktype = SOCK_STREAM,
         .ai_flags = AI_PASSIVE
     };
-    addrinfo* frontend_ai_res;
+    addrinfo* frontend_ai_res = nullptr;
     int rv = getaddrinfo(listen_address_.c_str(), listen_service_.c_str(),
                          &frontend_ai_hints, &frontend_ai_res);
+    ScopedAddrinfo frontend_ai_res_cleanup(frontend_ai_res);
     if (rv) {
         ALOGE("frontend getaddrinfo(%s, %s) failed: %s", listen_address_.c_str(),
             listen_service_.c_str(), gai_strerror(rv));
         return false;
     }
 
-    int s = -1;
     for (const addrinfo* ai = frontend_ai_res ; ai ; ai = ai->ai_next) {
-        s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-        if (s < 0) continue;
-        enableSockopt(s, SOL_SOCKET, SO_REUSEPORT);
-        enableSockopt(s, SOL_SOCKET, SO_REUSEADDR);
-        if (bind(s, ai->ai_addr, ai->ai_addrlen)) {
-            APLOGI("bind failed for socket %d", s);
-            close(s);
-            s = -1;
+        android::base::unique_fd s(socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol));
+        if (s.get() < 0) {
+            APLOGI("ignore creating socket failed %d", s.get());
             continue;
         }
+        enableSockopt(s.get(), SOL_SOCKET, SO_REUSEPORT).ignoreError();
+        enableSockopt(s.get(), SOL_SOCKET, SO_REUSEADDR).ignoreError();
         std::string host_str = addr2str(ai->ai_addr, ai->ai_addrlen);
+        if (bind(s.get(), ai->ai_addr, ai->ai_addrlen)) {
+            APLOGI("failed to bind TCP %s:%s", host_str.c_str(), listen_service_.c_str());
+            continue;
+        }
         ALOGI("bound to TCP %s:%s", host_str.c_str(), listen_service_.c_str());
+        socket_ = std::move(s);
         break;
     }
-    freeaddrinfo(frontend_ai_res);
-    if (s < 0) {
-        ALOGE("server socket creation failed");
+
+    if (listen(socket_.get(), 1) < 0) {
+        APLOGI("failed to listen socket %d", socket_.get());
         return false;
     }
 
-    if (listen(s, 1) < 0) {
-        ALOGE("listen failed");
-        return false;
-    }
-
-    socket_ = s;
-
     // Set up UDP client socket to backend.
     addrinfo backend_ai_hints{
         .ai_family = AF_UNSPEC,
         .ai_socktype = SOCK_DGRAM
     };
-    addrinfo* backend_ai_res;
+    addrinfo* backend_ai_res = nullptr;
     rv = getaddrinfo(backend_address_.c_str(), backend_service_.c_str(),
                          &backend_ai_hints, &backend_ai_res);
+    ScopedAddrinfo backend_ai_res_cleanup(backend_ai_res);
     if (rv) {
         ALOGE("backend getaddrinfo(%s, %s) failed: %s", listen_address_.c_str(),
             listen_service_.c_str(), gai_strerror(rv));
         return false;
     }
-    backend_socket_ = socket(backend_ai_res->ai_family, backend_ai_res->ai_socktype,
-        backend_ai_res->ai_protocol);
-    if (backend_socket_ < 0) {
-        ALOGE("backend socket creation failed");
+    backend_socket_.reset(socket(backend_ai_res->ai_family, backend_ai_res->ai_socktype,
+                                 backend_ai_res->ai_protocol));
+    if (backend_socket_.get() < 0) {
+        APLOGI("backend socket %d creation failed", backend_socket_.get());
         return false;
     }
-    connect(backend_socket_, backend_ai_res->ai_addr, backend_ai_res->ai_addrlen);
-    freeaddrinfo(backend_ai_res);
+
+    // connect() always fails in the test DnsTlsSocketTest.SlowDestructor because of
+    // no backend server. Don't check it.
+    connect(backend_socket_.get(), backend_ai_res->ai_addr, backend_ai_res->ai_addrlen);
+
+    // Set up eventfd socket.
+    event_fd_.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
+    if (event_fd_.get() == -1) {
+        APLOGI("failed to create eventfd %d", event_fd_.get());
+        return false;
+    }
 
     {
-        std::lock_guard<std::mutex> lock(update_mutex_);
+        std::lock_guard lock(update_mutex_);
         handler_thread_ = std::thread(&DnsTlsFrontend::requestHandler, this);
     }
     ALOGI("server started successfully");
@@ -265,51 +269,55 @@
 
 void DnsTlsFrontend::requestHandler() {
     ALOGD("Request handler started");
-    struct pollfd fds[1] = {{ .fd = socket_, .events = POLLIN }};
+    enum { EVENT_FD = 0, LISTEN_FD = 1 };
+    pollfd fds[2] = {{.fd = event_fd_.get(), .events = POLLIN},
+                     {.fd = socket_.get(), .events = POLLIN}};
 
-    while (!terminate_) {
-        int poll_code = poll(fds, 1, 10 /* ms */);
-        if (poll_code == 0) {
-            // Timeout.  Poll again.
-            continue;
-        } else if (poll_code < 0) {
-            ALOGW("Poll failed with error %d", poll_code);
-            // Error.
-            break;
-        }
-        sockaddr_storage addr;
-        socklen_t len = sizeof(addr);
-
-        ALOGD("Trying to accept a client");
-        int client = accept(socket_, reinterpret_cast<sockaddr*>(&addr), &len);
-        ALOGD("Got client socket %d", client);
-        if (client < 0) {
-            // Stop
+    while (true) {
+        int poll_code = poll(fds, std::size(fds), -1);
+        if (poll_code <= 0) {
+            APLOGI("Poll failed with error %d", poll_code);
             break;
         }
 
-        bssl::UniquePtr<SSL> ssl(SSL_new(ctx_.get()));
-        SSL_set_fd(ssl.get(), client);
-
-        ALOGD("Doing SSL handshake");
-        bool success = false;
-        if (SSL_accept(ssl.get()) <= 0) {
-            ALOGI("SSL negotiation failure");
-        } else {
-            ALOGD("SSL handshake complete");
-            success = handleOneRequest(ssl.get());
+        if (fds[EVENT_FD].revents & (POLLIN | POLLERR)) {
+            handleEventFd();
+            break;
         }
+        if (fds[LISTEN_FD].revents & (POLLIN | POLLERR)) {
+            sockaddr_storage addr;
+            socklen_t len = sizeof(addr);
 
-        close(client);
+            ALOGD("Trying to accept a client");
+            android::base::unique_fd client(
+                    accept4(socket_.get(), reinterpret_cast<sockaddr*>(&addr), &len, SOCK_CLOEXEC));
+            if (client.get() < 0) {
+                // Stop
+                APLOGI("failed to accept client socket %d", client.get());
+                break;
+            }
 
-        if (success) {
-            // Increment queries_ as late as possible, because it represents
-            // a query that is fully processed, and the response returned to the
-            // client, including cleanup actions.
-            ++queries_;
+            bssl::UniquePtr<SSL> ssl(SSL_new(ctx_.get()));
+            SSL_set_fd(ssl.get(), client.get());
+
+            ALOGD("Doing SSL handshake");
+            bool success = false;
+            if (SSL_accept(ssl.get()) <= 0) {
+                ALOGI("SSL negotiation failure");
+            } else {
+                ALOGD("SSL handshake complete");
+                success = handleOneRequest(ssl.get());
+            }
+
+            if (success) {
+                // Increment queries_ as late as possible, because it represents
+                // a query that is fully processed, and the response returned to the
+                // client, including cleanup actions.
+                ++queries_;
+            }
         }
     }
-    ALOGD("Request handler terminating");
+    ALOGD("Ending loop");
 }
 
 bool DnsTlsFrontend::handleOneRequest(SSL* ssl) {
@@ -329,14 +337,14 @@
         }
         qbytes += ret;
     }
-    int sent = send(backend_socket_, query, qlen, 0);
+    int sent = send(backend_socket_.get(), query, qlen, 0);
     if (sent != qlen) {
         ALOGI("Failed to send query");
         return false;
     }
     const int max_size = 4096;
     uint8_t recv_buffer[max_size];
-    int rlen = recv(backend_socket_, recv_buffer, max_size, 0);
+    int rlen = recv(backend_socket_.get(), recv_buffer, max_size, 0);
     if (rlen <= 0) {
         ALOGI("Failed to receive response");
         return false;
@@ -356,23 +364,20 @@
 }
 
 bool DnsTlsFrontend::stopServer() {
-    std::lock_guard<std::mutex> lock(update_mutex_);
+    std::lock_guard lock(update_mutex_);
     if (!running()) {
         ALOGI("server not running");
         return false;
     }
-    if (terminate_) {
-        ALOGI("LOGIC ERROR");
+
+    ALOGI("stopping frontend");
+    if (!sendToEventFd()) {
         return false;
     }
-    ALOGI("stopping frontend");
-    terminate_ = true;
     handler_thread_.join();
-    close(socket_);
-    close(backend_socket_);
-    terminate_ = false;
-    socket_ = -1;
-    backend_socket_ = -1;
+    socket_.reset();
+    backend_socket_.reset();
+    event_fd_.reset();
     ctx_.reset();
     fingerprint_.clear();
     ALOGI("frontend stopped successfully");
@@ -397,4 +402,20 @@
     return false;
 }
 
+bool DnsTlsFrontend::sendToEventFd() {
+    const uint64_t data = 1;
+    if (const ssize_t rt = write(event_fd_.get(), &data, sizeof(data)); rt != sizeof(data)) {
+        APLOGI("failed to write eventfd, rt=%zd", rt);
+        return false;
+    }
+    return true;
+}
+
+void DnsTlsFrontend::handleEventFd() {
+    int64_t data;
+    if (const ssize_t rt = read(event_fd_.get(), &data, sizeof(data)); rt != sizeof(data)) {
+        APLOGI("ignore reading eventfd failed, rt=%zd", rt);
+    }
+}
+
 }  // namespace test
diff --git a/tests/dns_responder/dns_tls_frontend.h b/resolv/dns_responder/dns_tls_frontend.h
similarity index 71%
rename from tests/dns_responder/dns_tls_frontend.h
rename to resolv/dns_responder/dns_tls_frontend.h
index bf6bb80..32a2771 100644
--- a/tests/dns_responder/dns_tls_frontend.h
+++ b/resolv/dns_responder/dns_tls_frontend.h
@@ -28,23 +28,23 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <android-base/unique_fd.h>
 #include <openssl/ssl.h>
 
 namespace test {
 
-constexpr int SHA256_SIZE = 32;
-
 /*
  * Simple DNS over TLS reverse proxy that forwards to a UDP backend.
  * Only handles a single request at a time.
  */
 class DnsTlsFrontend {
-public:
+  public:
     DnsTlsFrontend(const std::string& listen_address, const std::string& listen_service,
-            const std::string& backend_address, const std::string& backend_service) :
-            listen_address_(listen_address), listen_service_(listen_service),
-            backend_address_(backend_address), backend_service_(backend_service),
-            queries_(0), terminate_(false) { }
+                   const std::string& backend_address, const std::string& backend_service)
+        : listen_address_(listen_address),
+          listen_service_(listen_service),
+          backend_address_(backend_address),
+          backend_service_(backend_service) {}
     ~DnsTlsFrontend() {
         stopServer();
     }
@@ -60,24 +60,34 @@
     bool startServer();
     bool stopServer();
     int queries() const { return queries_; }
+    void clearQueries() { queries_ = 0; }
     bool waitForQueries(int number, int timeoutMs) const;
     void set_chain_length(int length) { chain_length_ = length; }
     // Represents a fingerprint from the middle of the certificate chain.
     const std::vector<uint8_t>& fingerprint() const { return fingerprint_; }
 
-private:
+  private:
     void requestHandler();
     bool handleOneRequest(SSL* ssl);
 
+    // Trigger the handler thread to terminate.
+    bool sendToEventFd();
+
+    // Used in the handler thread for the termination signal.
+    void handleEventFd();
+
     std::string listen_address_;
     std::string listen_service_;
     std::string backend_address_;
     std::string backend_service_;
     bssl::UniquePtr<SSL_CTX> ctx_;
-    int socket_ = -1;
-    int backend_socket_ = -1;
-    std::atomic<int> queries_;
-    std::atomic<bool> terminate_;
+    // Socket on which the server is listening for a TCP connection with a client.
+    android::base::unique_fd socket_;
+    // Socket used to communicate with the backend DNS server.
+    android::base::unique_fd backend_socket_;
+    // Eventfd used to signal for the handler thread termination.
+    android::base::unique_fd event_fd_;
+    std::atomic<int> queries_ = 0;
     std::thread handler_thread_ GUARDED_BY(update_mutex_);
     std::mutex update_mutex_;
     int chain_length_ = 1;
diff --git a/tests/dns_tls_test.cpp b/resolv/dns_tls_test.cpp
similarity index 82%
rename from tests/dns_tls_test.cpp
rename to resolv/dns_tls_test.cpp
index bb5bfe5..0bcad30 100644
--- a/tests/dns_tls_test.cpp
+++ b/resolv/dns_tls_test.cpp
@@ -15,18 +15,21 @@
  */
 
 #define LOG_TAG "dns_tls_test"
+#define LOG_NDEBUG 1  // Set to 0 to enable verbose debug logging
 
 #include <gtest/gtest.h>
 
-#include "dns/DnsTlsDispatcher.h"
-#include "dns/DnsTlsQueryMap.h"
-#include "dns/DnsTlsServer.h"
-#include "dns/DnsTlsSessionCache.h"
-#include "dns/DnsTlsSocket.h"
-#include "dns/DnsTlsTransport.h"
-#include "dns/IDnsTlsSocket.h"
-#include "dns/IDnsTlsSocketFactory.h"
-#include "dns/IDnsTlsSocketObserver.h"
+#include "DnsTlsDispatcher.h"
+#include "DnsTlsQueryMap.h"
+#include "DnsTlsServer.h"
+#include "DnsTlsSessionCache.h"
+#include "DnsTlsSocket.h"
+#include "DnsTlsTransport.h"
+#include "IDnsTlsSocket.h"
+#include "IDnsTlsSocketFactory.h"
+#include "IDnsTlsSocketObserver.h"
+
+#include "dns_responder/dns_tls_frontend.h"
 
 #include <chrono>
 #include <arpa/inet.h>
@@ -69,7 +72,7 @@
 
 // BaseTest just provides constants that are useful for the tests.
 class BaseTest : public ::testing::Test {
-protected:
+  protected:
     BaseTest() {
         parseServer("192.0.2.1", 853, &V4ADDR1);
         parseServer("192.0.2.2", 853, &V4ADDR2);
@@ -108,7 +111,7 @@
 
 template <class T>
 class FakeSocketFactory : public IDnsTlsSocketFactory {
-public:
+  public:
     FakeSocketFactory() {}
     std::unique_ptr<IDnsTlsSocket> createDnsTlsSocket(
             const DnsTlsServer& server ATTRIBUTE_UNUSED,
@@ -130,14 +133,15 @@
 
 // Simplest possible fake server.  This just echoes the query as the response.
 class FakeSocketEcho : public IDnsTlsSocket {
-public:
-    FakeSocketEcho(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
+  public:
+    explicit FakeSocketEcho(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
     bool query(uint16_t id, const Slice query) override {
         // Return the response immediately (asynchronously).
         std::thread(&IDnsTlsSocketObserver::onResponse, mObserver, make_echo(id, query)).detach();
         return true;
     }
-private:
+
+  private:
     IDnsTlsSocketObserver* const mObserver;
 };
 
@@ -152,15 +156,43 @@
     EXPECT_EQ(QUERY, r.response);
 }
 
-TEST_F(TransportTest, SerialQueries_100000) {
-    FakeSocketFactory<FakeSocketEcho> factory;
-    DnsTlsTransport transport(SERVER1, MARK, &factory);
-    // Send more than 65536 queries serially.
-    for (int i = 0; i < 100000; ++i) {
-        auto r = transport.query(makeSlice(QUERY)).get();
+// Fake Socket that echoes the observed query ID as the response body.
+class FakeSocketId : public IDnsTlsSocket {
+  public:
+    explicit FakeSocketId(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
+    bool query(uint16_t id, const Slice query ATTRIBUTE_UNUSED) override {
+        // Return the response immediately (asynchronously).
+        bytevec response(4);
+        // Echo the ID in the header to match the response to the query.
+        // This will be overwritten by DnsTlsQueryMap.
+        response[0] = id >> 8;
+        response[1] = id;
+        // Echo the ID in the body, so that the test can verify which ID was used by
+        // DnsTlsQueryMap.
+        response[2] = id >> 8;
+        response[3] = id;
+        std::thread(&IDnsTlsSocketObserver::onResponse, mObserver, response).detach();
+        return true;
+    }
 
+  private:
+    IDnsTlsSocketObserver* const mObserver;
+};
+
+// Test that IDs are properly reused
+TEST_F(TransportTest, IdReuse) {
+    FakeSocketFactory<FakeSocketId> factory;
+    DnsTlsTransport transport(SERVER1, MARK, &factory);
+    for (int i = 0; i < 100; ++i) {
+        // Send a query.
+        std::future<DnsTlsServer::Result> f = transport.query(makeSlice(QUERY));
+        // Wait for the response.
+        DnsTlsServer::Result r = f.get();
         EXPECT_EQ(DnsTlsTransport::Response::success, r.code);
-        EXPECT_EQ(QUERY, r.response);
+
+        // All queries should have an observed ID of zero, because it is returned to the ID pool
+        // after each use.
+        EXPECT_EQ(0, (r.response[2] << 8) | r.response[3]);
     }
 }
 
@@ -171,7 +203,9 @@
     DnsTlsTransport transport(SERVER1, MARK, &factory);
     std::vector<std::future<DnsTlsTransport::Result>> results;
     // Fewer than 65536 queries to avoid ID exhaustion.
-    for (int i = 0; i < 10000; ++i) {
+    const int num_queries = 10000;
+    results.reserve(num_queries);
+    for (int i = 0; i < num_queries; ++i) {
         results.push_back(transport.query(makeSlice(QUERY)));
     }
     for (auto& result : results) {
@@ -183,15 +217,15 @@
 
 // A server that waits until sDelay queries are queued before responding.
 class FakeSocketDelay : public IDnsTlsSocket {
-public:
-    FakeSocketDelay(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
-    ~FakeSocketDelay() { std::lock_guard<std::mutex> guard(mLock); }
+  public:
+    explicit FakeSocketDelay(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
+    ~FakeSocketDelay() { std::lock_guard guard(mLock); }
     static size_t sDelay;
     static bool sReverse;
 
     bool query(uint16_t id, const Slice query) override {
-        ALOGD("FakeSocketDelay got query with ID %d", int(id));
-        std::lock_guard<std::mutex> guard(mLock);
+        ALOGV("FakeSocketDelay got query with ID %d", int(id));
+        std::lock_guard guard(mLock);
         // Check for duplicate IDs.
         EXPECT_EQ(0U, mIds.count(id));
         mIds.insert(id);
@@ -199,15 +233,16 @@
         // Store response.
         mResponses.push_back(make_echo(id, query));
 
-        ALOGD("Up to %zu out of %zu queries", mResponses.size(), sDelay);
+        ALOGV("Up to %zu out of %zu queries", mResponses.size(), sDelay);
         if (mResponses.size() == sDelay) {
             std::thread(&FakeSocketDelay::sendResponses, this).detach();
         }
         return true;
     }
-private:
+
+  private:
     void sendResponses() {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         if (sReverse) {
             std::reverse(std::begin(mResponses), std::end(mResponses));
         }
@@ -234,6 +269,7 @@
     DnsTlsTransport transport(SERVER1, MARK, &factory);
     std::vector<std::future<DnsTlsTransport::Result>> results;
     // Fewer than 65536 queries to avoid ID exhaustion.
+    results.reserve(FakeSocketDelay::sDelay);
     for (size_t i = 0; i < FakeSocketDelay::sDelay; ++i) {
         results.push_back(transport.query(makeSlice(QUERY)));
     }
@@ -252,6 +288,7 @@
     std::vector<std::future<DnsTlsTransport::Result>> results;
     // Exactly 65536 queries should still be possible in parallel,
     // even if they all have the same original ID.
+    results.reserve(FakeSocketDelay::sDelay);
     for (size_t i = 0; i < FakeSocketDelay::sDelay; ++i) {
         results.push_back(transport.query(makeSlice(QUERY)));
     }
@@ -269,6 +306,7 @@
     DnsTlsTransport transport(SERVER1, MARK, &factory);
     std::vector<bytevec> queries(FakeSocketDelay::sDelay);
     std::vector<std::future<DnsTlsTransport::Result>> results;
+    results.reserve(FakeSocketDelay::sDelay);
     for (size_t i = 0; i < FakeSocketDelay::sDelay; ++i) {
         queries[i] = make_query(i, SIZE);
         results.push_back(transport.query(makeSlice(queries[i])));
@@ -289,6 +327,7 @@
     std::vector<std::future<DnsTlsTransport::Result>> results;
     // Exactly 65536 queries should still be possible in parallel,
     // and they should all be mapped correctly back to the original ID.
+    results.reserve(FakeSocketDelay::sDelay);
     for (size_t i = 0; i < FakeSocketDelay::sDelay; ++i) {
         queries[i] = make_query(i, SIZE);
         results.push_back(transport.query(makeSlice(queries[i])));
@@ -301,15 +340,17 @@
 }
 
 TEST_F(TransportTest, IdExhaustion) {
+    const int num_queries = 65536;
     // A delay of 65537 is unreachable, because the maximum number
     // of outstanding queries is 65536.
-    FakeSocketDelay::sDelay = 65537;
+    FakeSocketDelay::sDelay = num_queries + 1;
     FakeSocketDelay::sReverse = false;
     FakeSocketFactory<FakeSocketDelay> factory;
     DnsTlsTransport transport(SERVER1, MARK, &factory);
     std::vector<std::future<DnsTlsTransport::Result>> results;
     // Issue the maximum number of queries.
-    for (int i = 0; i < 65536; ++i) {
+    results.reserve(num_queries);
+    for (int i = 0; i < num_queries; ++i) {
         results.push_back(transport.query(makeSlice(QUERY)));
     }
 
@@ -334,6 +375,7 @@
     DnsTlsTransport transport(SERVER1, MARK, &factory);
     std::vector<bytevec> queries(FakeSocketDelay::sDelay);
     std::vector<std::future<DnsTlsTransport::Result>> results;
+    results.reserve(FakeSocketDelay::sDelay);
     for (size_t i = 0; i < FakeSocketDelay::sDelay; ++i) {
         queries[i] = make_query(i, SIZE);
         results.push_back(transport.query(makeSlice(queries[i])));
@@ -352,6 +394,7 @@
     DnsTlsTransport transport(SERVER1, MARK, &factory);
     std::vector<bytevec> queries(FakeSocketDelay::sDelay);
     std::vector<std::future<DnsTlsTransport::Result>> results;
+    results.reserve(FakeSocketDelay::sDelay);
     for (size_t i = 0; i < FakeSocketDelay::sDelay; ++i) {
         queries[i] = make_query(i, SIZE);
         results.push_back(transport.query(makeSlice(queries[i])));
@@ -365,7 +408,7 @@
 
 // Returning null from the factory indicates a connection failure.
 class NullSocketFactory : public IDnsTlsSocketFactory {
-public:
+  public:
     NullSocketFactory() {}
     std::unique_ptr<IDnsTlsSocket> createDnsTlsSocket(
             const DnsTlsServer& server ATTRIBUTE_UNUSED,
@@ -388,15 +431,16 @@
 // Simulate a socket that connects but then immediately receives a server
 // close notification.
 class FakeSocketClose : public IDnsTlsSocket {
-public:
-    FakeSocketClose(IDnsTlsSocketObserver* observer) :
-            mCloser(&IDnsTlsSocketObserver::onClosed, observer) {}
+  public:
+    explicit FakeSocketClose(IDnsTlsSocketObserver* observer)
+        : mCloser(&IDnsTlsSocketObserver::onClosed, observer) {}
     ~FakeSocketClose() { mCloser.join(); }
     bool query(uint16_t id ATTRIBUTE_UNUSED,
                const Slice query ATTRIBUTE_UNUSED) override {
         return true;
     }
-private:
+
+  private:
     std::thread mCloser;
 };
 
@@ -412,38 +456,38 @@
 // Simulate a server that occasionally closes the connection and silently
 // drops some queries.
 class FakeSocketLimited : public IDnsTlsSocket {
-public:
+  public:
     static int sLimit;  // Number of queries to answer per socket.
     static size_t sMaxSize;  // Silently discard queries greater than this size.
-    FakeSocketLimited(IDnsTlsSocketObserver* observer) :
-            mObserver(observer), mQueries(0) {}
+    explicit FakeSocketLimited(IDnsTlsSocketObserver* observer)
+        : mObserver(observer), mQueries(0) {}
     ~FakeSocketLimited() {
         {
-            ALOGD("~FakeSocketLimited acquiring mLock");
-            std::lock_guard<std::mutex> guard(mLock);
-            ALOGD("~FakeSocketLimited acquired mLock");
+            ALOGV("~FakeSocketLimited acquiring mLock");
+            std::lock_guard guard(mLock);
+            ALOGV("~FakeSocketLimited acquired mLock");
             for (auto& thread : mThreads) {
-                ALOGD("~FakeSocketLimited joining response thread");
+                ALOGV("~FakeSocketLimited joining response thread");
                 thread.join();
-                ALOGD("~FakeSocketLimited joined response thread");
+                ALOGV("~FakeSocketLimited joined response thread");
             }
             mThreads.clear();
         }
 
         if (mCloser) {
-            ALOGD("~FakeSocketLimited joining closer thread");
+            ALOGV("~FakeSocketLimited joining closer thread");
             mCloser->join();
-            ALOGD("~FakeSocketLimited joined closer thread");
+            ALOGV("~FakeSocketLimited joined closer thread");
         }
     }
     bool query(uint16_t id, const Slice query) override {
-        ALOGD("FakeSocketLimited::query acquiring mLock");
-        std::lock_guard<std::mutex> guard(mLock);
-        ALOGD("FakeSocketLimited::query acquired mLock");
+        ALOGV("FakeSocketLimited::query acquiring mLock");
+        std::lock_guard guard(mLock);
+        ALOGV("FakeSocketLimited::query acquired mLock");
         ++mQueries;
 
         if (mQueries <= sLimit) {
-            ALOGD("size %zu vs. limit of %zu", query.size(), sMaxSize);
+            ALOGV("size %zu vs. limit of %zu", query.size(), sMaxSize);
             if (query.size() <= sMaxSize) {
                 // Return the response immediately (asynchronously).
                 mThreads.emplace_back(&IDnsTlsSocketObserver::onResponse, mObserver, make_echo(id, query));
@@ -454,16 +498,17 @@
         }
         return mQueries <= sLimit;
     }
-private:
+
+  private:
     void sendClose() {
         {
-            ALOGD("FakeSocketLimited::sendClose acquiring mLock");
-            std::lock_guard<std::mutex> guard(mLock);
-            ALOGD("FakeSocketLimited::sendClose acquired mLock");
+            ALOGV("FakeSocketLimited::sendClose acquiring mLock");
+            std::lock_guard guard(mLock);
+            ALOGV("FakeSocketLimited::sendClose acquired mLock");
             for (auto& thread : mThreads) {
-                ALOGD("FakeSocketLimited::sendClose joining response thread");
+                ALOGV("FakeSocketLimited::sendClose joining response thread");
                 thread.join();
-                ALOGD("FakeSocketLimited::sendClose joined response thread");
+                ALOGV("FakeSocketLimited::sendClose joined response thread");
             }
             mThreads.clear();
         }
@@ -489,6 +534,7 @@
     // the socket will close.  Transport will retry them all, until they
     // all hit the retry limit and expire.
     std::vector<std::future<DnsTlsTransport::Result>> results;
+    results.reserve(FakeSocketLimited::sLimit);
     for (int i = 0; i < FakeSocketLimited::sLimit; ++i) {
         results.push_back(transport.query(makeSlice(QUERY)));
     }
@@ -507,9 +553,10 @@
 
     // Queue up 100 queries, alternating "short" which will be served and "long"
     // which will be dropped.
-    int num_queries = 10 * FakeSocketLimited::sLimit;
+    const int num_queries = 10 * FakeSocketLimited::sLimit;
     std::vector<bytevec> queries(num_queries);
     std::vector<std::future<DnsTlsTransport::Result>> results;
+    results.reserve(num_queries);
     for (int i = 0; i < num_queries; ++i) {
         queries[i] = make_query(i, SIZE + (i % 2));
         results.push_back(transport.query(makeSlice(queries[i])));
@@ -526,19 +573,19 @@
 // responses to queries that were not asked.  This will cause wrong answers but
 // must not crash the Transport.
 class FakeSocketGarbage : public IDnsTlsSocket {
-public:
-    FakeSocketGarbage(IDnsTlsSocketObserver* observer) : mObserver(observer) {
+  public:
+    explicit FakeSocketGarbage(IDnsTlsSocketObserver* observer) : mObserver(observer) {
         // Inject a garbage event.
         mThreads.emplace_back(&IDnsTlsSocketObserver::onResponse, mObserver, make_query(ID + 1, SIZE));
     }
     ~FakeSocketGarbage() {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         for (auto& thread : mThreads) {
             thread.join();
         }
     }
     bool query(uint16_t id, const Slice query) override {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         // Return the response twice.
         auto echo = make_echo(id, query);
         mThreads.emplace_back(&IDnsTlsSocketObserver::onResponse, mObserver, echo);
@@ -547,7 +594,8 @@
         mThreads.emplace_back(&IDnsTlsSocketObserver::onResponse, mObserver, make_query(id + 1, query.size() + 2));
         return true;
     }
-private:
+
+  private:
     std::mutex mLock;
     std::vector<std::thread> mThreads GUARDED_BY(mLock);
     IDnsTlsSocketObserver* const mObserver;
@@ -596,19 +644,20 @@
 
 template<class T>
 class TrackingFakeSocketFactory : public IDnsTlsSocketFactory {
-public:
+  public:
     TrackingFakeSocketFactory() {}
     std::unique_ptr<IDnsTlsSocket> createDnsTlsSocket(
             const DnsTlsServer& server,
             unsigned mark,
             IDnsTlsSocketObserver* observer,
             DnsTlsSessionCache* cache ATTRIBUTE_UNUSED) override {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         keys.emplace(mark, server);
         return std::make_unique<T>(observer);
     }
     std::multiset<std::pair<unsigned, DnsTlsServer>> keys;
-private:
+
+  private:
     std::mutex mLock;
 };
 
@@ -871,5 +920,44 @@
     EXPECT_FALSE(map.recordQuery(makeSlice(QUERY)));
 }
 
+class StubObserver : public IDnsTlsSocketObserver {
+  public:
+    bool closed = false;
+    void onResponse(std::vector<uint8_t>) override {}
+
+    void onClosed() override { closed = true; }
+};
+
+TEST(DnsTlsSocketTest, SlowDestructor) {
+    constexpr char tls_addr[] = "127.0.0.3";
+    constexpr char tls_port[] = "8530";  // High-numbered port so root isn't required.
+    // This test doesn't perform any queries, so the backend address can be invalid.
+    constexpr char backend_addr[] = "192.0.2.1";
+    constexpr char backend_port[] = "1";
+
+    test::DnsTlsFrontend tls(tls_addr, tls_port, backend_addr, backend_port);
+    ASSERT_TRUE(tls.startServer());
+
+    DnsTlsServer server;
+    parseServer(tls_addr, 8530, &server.ss);
+
+    StubObserver observer;
+    ASSERT_FALSE(observer.closed);
+    DnsTlsSessionCache cache;
+    auto socket = std::make_unique<DnsTlsSocket>(server, MARK, &observer, &cache);
+    ASSERT_TRUE(socket->initialize());
+
+    // Test: Time the socket destructor.  This should be fast.
+    auto before = std::chrono::steady_clock::now();
+    socket.reset();
+    auto after = std::chrono::steady_clock::now();
+    auto delay = after - before;
+    ALOGV("Shutdown took %lld ns", delay / std::chrono::nanoseconds{1});
+    EXPECT_TRUE(observer.closed);
+    // Shutdown should complete in milliseconds, but if the shutdown signal is lost
+    // it will wait for the timeout, which is expected to take 20seconds.
+    EXPECT_LT(delay, std::chrono::seconds{5});
+}
+
 } // end of namespace net
 } // end of namespace android
diff --git a/resolv/dnsresolver_binder_test.cpp b/resolv/dnsresolver_binder_test.cpp
new file mode 100644
index 0000000..0a2de0e
--- /dev/null
+++ b/resolv/dnsresolver_binder_test.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright 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.
+ *
+ * binder_test.cpp - unit tests for netd binder RPCs.
+ */
+
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <vector>
+
+#include <openssl/base64.h>
+
+#include <android-base/strings.h>
+#include <android/net/IDnsResolver.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+#include <netdb.h>
+#include <netdutils/Stopwatch.h>
+#include "tests/BaseTestMetricsListener.h"
+#include "tests/TestMetrics.h"
+
+#include "NetdConstants.h"  // SHA256_SIZE
+#include "ResolverStats.h"
+#include "dns_responder.h"
+#include "dns_responder_client.h"
+
+namespace binder = android::binder;
+
+using android::IBinder;
+using android::IServiceManager;
+using android::ProcessState;
+using android::sp;
+using android::String16;
+using android::String8;
+using android::net::IDnsResolver;
+using android::net::ResolverParamsParcel;
+using android::net::ResolverStats;
+using android::net::metrics::INetdEventListener;
+using android::net::metrics::TestOnDnsEvent;
+using android::netdutils::Stopwatch;
+
+// TODO: make this dynamic and stop depending on implementation details.
+// Sync from TEST_NETID in dns_responder_client.cpp as resolver_test.cpp does.
+constexpr int TEST_NETID = 30;
+
+class DnsResolverBinderTest : public ::testing::Test {
+  public:
+    DnsResolverBinderTest() {
+        sp<IServiceManager> sm = android::defaultServiceManager();
+        sp<IBinder> binder = sm->getService(String16("dnsresolver"));
+        if (binder != nullptr) {
+            mDnsResolver = android::interface_cast<IDnsResolver>(binder);
+        }
+        assert(nullptr != mDnsResolver.get());
+        // Create cache for test
+        mDnsResolver->createNetworkCache(TEST_NETID);
+    }
+
+    ~DnsResolverBinderTest() {
+        // Destroy cache for test
+        mDnsResolver->destroyNetworkCache(TEST_NETID);
+    }
+
+  protected:
+    sp<IDnsResolver> mDnsResolver;
+};
+
+class TimedOperation : public Stopwatch {
+  public:
+    explicit TimedOperation(const std::string& name) : mName(name) {}
+    virtual ~TimedOperation() { fprintf(stderr, "    %s: %6.1f ms\n", mName.c_str(), timeTaken()); }
+
+  private:
+    std::string mName;
+};
+
+namespace {
+
+std::string base64Encode(const std::vector<uint8_t>& input) {
+    size_t out_len;
+    EXPECT_EQ(1, EVP_EncodedLength(&out_len, input.size()));
+    // out_len includes the trailing NULL.
+    uint8_t output_bytes[out_len];
+    EXPECT_EQ(out_len - 1, EVP_EncodeBlock(output_bytes, input.data(), input.size()));
+    return std::string(reinterpret_cast<char*>(output_bytes));
+}
+
+// TODO: Convert tests to ResolverParamsParcel and delete this stub.
+ResolverParamsParcel makeResolverParamsParcel(int netId, const std::vector<int>& params,
+                                              const std::vector<std::string>& servers,
+                                              const std::vector<std::string>& domains,
+                                              const std::string& tlsHostname,
+                                              const std::vector<std::string>& tlsServers,
+                                              const std::vector<std::string>& tlsFingerprints) {
+    using android::net::IDnsResolver;
+    ResolverParamsParcel paramsParcel;
+
+    paramsParcel.netId = netId;
+    paramsParcel.sampleValiditySeconds = params[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY];
+    paramsParcel.successThreshold = params[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD];
+    paramsParcel.minSamples = params[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES];
+    paramsParcel.maxSamples = params[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES];
+    if (params.size() > IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC) {
+        paramsParcel.baseTimeoutMsec = params[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC];
+    } else {
+        paramsParcel.baseTimeoutMsec = 0;
+    }
+    if (params.size() > IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT) {
+        paramsParcel.retryCount = params[IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT];
+    } else {
+        paramsParcel.retryCount = 0;
+    }
+    paramsParcel.servers = servers;
+    paramsParcel.domains = domains;
+    paramsParcel.tlsName = tlsHostname;
+    paramsParcel.tlsServers = tlsServers;
+    paramsParcel.tlsFingerprints = tlsFingerprints;
+
+    return paramsParcel;
+}
+
+}  // namespace
+
+TEST_F(DnsResolverBinderTest, IsAlive) {
+    TimedOperation t("isAlive RPC");
+    bool isAlive = false;
+    mDnsResolver->isAlive(&isAlive);
+    ASSERT_TRUE(isAlive);
+}
+
+// TODO: Move this test to resolver_test.cpp
+TEST_F(DnsResolverBinderTest, EventListener_onDnsEvent) {
+    // The test configs are used to trigger expected events. The expected results are defined in
+    // expectedResults.
+    static const struct TestConfig {
+        std::string hostname;
+        int returnCode;
+    } testConfigs[] = {
+            {"hi", 0 /*success*/},
+            {"nonexistent", EAI_NODATA},
+    };
+
+    // The expected results define expected event content for test verification.
+    static const std::vector<TestOnDnsEvent::TestResult> expectedResults = {
+            {TEST_NETID, INetdEventListener::EVENT_GETADDRINFO, 0 /*success*/, 1, "hi", "1.2.3.4"},
+            {TEST_NETID, INetdEventListener::EVENT_GETADDRINFO, EAI_NODATA, 0, "nonexistent", ""},
+    };
+
+    // Start the Binder thread pool.
+    // TODO: Consider doing this once if there has another event listener unit test.
+    android::ProcessState::self()->startThreadPool();
+
+    // Setup network.
+    // TODO: Setup device configuration and DNS responser server as resolver test does.
+    // Currently, leave DNS related configuration in this test because only it needs DNS
+    // client-server testing environment.
+    DnsResponderClient dnsClient;
+    dnsClient.SetUp();
+
+    // Setup DNS responder server.
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns.addMapping("hi.example.com.", ns_type::ns_t_a, "1.2.3.4");
+    ASSERT_TRUE(dns.startServer());
+
+    // Setup DNS configuration.
+    const std::vector<std::string> test_servers = {listen_addr};
+    std::vector<std::string> test_domains = {"example.com"};
+    std::vector<int> test_params = {300 /*sample_validity*/, 25 /*success_threshold*/,
+                                    8 /*min_samples*/, 8 /*max_samples*/};
+
+    ASSERT_TRUE(dnsClient.SetResolversForNetwork(test_servers, test_domains, test_params));
+    dns.clearQueries();
+
+    // Register event listener.
+    android::sp<TestOnDnsEvent> testOnDnsEvent = new TestOnDnsEvent(expectedResults);
+    android::binder::Status status = mDnsResolver->registerEventListener(
+            android::interface_cast<INetdEventListener>(testOnDnsEvent));
+    ASSERT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    // DNS queries.
+    // Once all expected events of expectedResults are received by the listener, the unit test will
+    // be notified. Otherwise, notified with a timeout expired failure.
+    auto& cv = testOnDnsEvent->getCv();
+    auto& cvMutex = testOnDnsEvent->getCvMutex();
+    {
+        std::unique_lock lock(cvMutex);
+
+        for (const auto& config : testConfigs) {
+            SCOPED_TRACE(config.hostname);
+
+            addrinfo* result = nullptr;
+            addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+            int status = getaddrinfo(config.hostname.c_str(), nullptr, &hints, &result);
+            EXPECT_EQ(config.returnCode, status);
+
+            if (result) freeaddrinfo(result);
+        }
+
+        // Wait for receiving expected events.
+        EXPECT_EQ(std::cv_status::no_timeout, cv.wait_for(lock, std::chrono::seconds(2)));
+    }
+
+    // Verify that all testcases are passed.
+    EXPECT_TRUE(testOnDnsEvent->isVerified());
+
+    dnsClient.TearDown();
+}
+
+TEST_F(DnsResolverBinderTest, SetResolverConfiguration_Tls) {
+    const std::vector<std::string> LOCALLY_ASSIGNED_DNS{"8.8.8.8", "2001:4860:4860::8888"};
+    std::vector<uint8_t> fp(SHA256_SIZE);
+    std::vector<uint8_t> short_fp(1);
+    std::vector<uint8_t> long_fp(SHA256_SIZE + 1);
+    std::vector<std::string> test_domains;
+    std::vector<int> test_params = {300, 25, 8, 8};
+    static const struct TestData {
+        const std::vector<std::string> servers;
+        const std::string tlsName;
+        const std::vector<std::vector<uint8_t>> tlsFingerprints;
+        const int expectedReturnCode;
+    } kTlsTestData[] = {
+            {{"192.0.2.1"}, "", {}, 0},
+            {{"2001:db8::2"}, "host.name", {}, 0},
+            {{"192.0.2.3"}, "@@@@", {fp}, 0},
+            {{"2001:db8::4"}, "", {fp}, 0},
+            {{}, "", {}, 0},
+            {{""}, "", {}, EINVAL},
+            {{"192.0.*.5"}, "", {}, EINVAL},
+            {{"2001:dg8::6"}, "", {}, EINVAL},
+            {{"2001:db8::c"}, "", {short_fp}, EINVAL},
+            {{"192.0.2.12"}, "", {long_fp}, EINVAL},
+            {{"2001:db8::e"}, "", {fp, fp, fp}, 0},
+            {{"192.0.2.14"}, "", {fp, short_fp}, EINVAL},
+    };
+
+    for (size_t i = 0; i < std::size(kTlsTestData); i++) {
+        const auto& td = kTlsTestData[i];
+
+        std::vector<std::string> fingerprints;
+        for (const auto& fingerprint : td.tlsFingerprints) {
+            fingerprints.push_back(base64Encode(fingerprint));
+        }
+        const auto resolverParams =
+                makeResolverParamsParcel(TEST_NETID, test_params, LOCALLY_ASSIGNED_DNS,
+                                         test_domains, td.tlsName, td.servers, fingerprints);
+        binder::Status status = mDnsResolver->setResolverConfiguration(resolverParams);
+
+        if (td.expectedReturnCode == 0) {
+            SCOPED_TRACE(String8::format("test case %zu should have passed", i));
+            SCOPED_TRACE(status.toString8());
+            EXPECT_EQ(0, status.exceptionCode());
+        } else {
+            SCOPED_TRACE(String8::format("test case %zu should have failed", i));
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_EQ(td.expectedReturnCode, status.serviceSpecificErrorCode());
+        }
+    }
+}
+
+TEST_F(DnsResolverBinderTest, GetResolverInfo) {
+    std::vector<std::string> servers = {"127.0.0.1", "127.0.0.2"};
+    std::vector<std::string> domains = {"example.com"};
+    std::vector<int> testParams = {
+            300,     // sample validity in seconds
+            25,      // success threshod in percent
+            8,   8,  // {MIN,MAX}_SAMPLES
+            100,     // BASE_TIMEOUT_MSEC
+            2,       // retry count
+    };
+    const auto resolverParams =
+            makeResolverParamsParcel(TEST_NETID, testParams, servers, domains, "", {}, {});
+    binder::Status status = mDnsResolver->setResolverConfiguration(resolverParams);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    std::vector<std::string> res_servers;
+    std::vector<std::string> res_domains;
+    std::vector<std::string> res_tls_servers;
+    std::vector<int32_t> params32;
+    std::vector<int32_t> stats32;
+    std::vector<int32_t> wait_for_pending_req_timeout_count32{0};
+    status = mDnsResolver->getResolverInfo(TEST_NETID, &res_servers, &res_domains, &res_tls_servers,
+                                           &params32, &stats32,
+                                           &wait_for_pending_req_timeout_count32);
+
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    EXPECT_EQ(servers.size(), res_servers.size());
+    EXPECT_EQ(domains.size(), res_domains.size());
+    EXPECT_EQ(0U, res_tls_servers.size());
+    ASSERT_EQ(static_cast<size_t>(IDnsResolver::RESOLVER_PARAMS_COUNT), testParams.size());
+    EXPECT_EQ(testParams[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY],
+              params32[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY]);
+    EXPECT_EQ(testParams[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD],
+              params32[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD]);
+    EXPECT_EQ(testParams[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES],
+              params32[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES]);
+    EXPECT_EQ(testParams[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES],
+              params32[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES]);
+    EXPECT_EQ(testParams[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC],
+              params32[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC]);
+
+    std::vector<ResolverStats> stats;
+    ResolverStats::decodeAll(stats32, &stats);
+
+    EXPECT_EQ(servers.size(), stats.size());
+
+    EXPECT_THAT(res_servers, testing::UnorderedElementsAreArray(servers));
+    EXPECT_THAT(res_domains, testing::UnorderedElementsAreArray(domains));
+}
+
+TEST_F(DnsResolverBinderTest, CreateDestroyNetworkCache) {
+    // Must not be the same as TEST_NETID
+    const int ANOTHER_TEST_NETID = TEST_NETID + 1;
+
+    // Create a new network cache.
+    EXPECT_TRUE(mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).isOk());
+
+    // create it again, expect a EEXIST.
+    EXPECT_EQ(EEXIST,
+              mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).serviceSpecificErrorCode());
+
+    // destroy it.
+    EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+
+    // re-create it
+    EXPECT_TRUE(mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).isOk());
+
+    // destroy it.
+    EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+
+    // re-destroy it
+    EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+}
+
+TEST_F(DnsResolverBinderTest, setLogSeverity) {
+    // Expect fail
+    EXPECT_EQ(EINVAL, mDnsResolver->setLogSeverity(-1).serviceSpecificErrorCode());
+
+    // Test set different log level
+    EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_VERBOSE).isOk());
+
+    EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_DEBUG).isOk());
+
+    EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_INFO).isOk());
+
+    EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_WARNING).isOk());
+
+    EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_ERROR).isOk());
+
+    // Set back to default
+    EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_WARNING).isOk());
+}
diff --git a/resolv/getaddrinfo.cpp b/resolv/getaddrinfo.cpp
new file mode 100644
index 0000000..1d5038e
--- /dev/null
+++ b/resolv/getaddrinfo.cpp
@@ -0,0 +1,1824 @@
+/*	$NetBSD: getaddrinfo.c,v 1.82 2006/03/25 12:09:40 rpaulo Exp $	*/
+/*	$KAME: getaddrinfo.c,v 1.29 2000/08/31 17:26:57 itojun Exp $	*/
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define LOG_TAG "getaddrinfo"
+
+#include "getaddrinfo.h"
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+
+#include "netd_resolv/resolv.h"
+#include "resolv_cache.h"
+#include "resolv_private.h"
+
+#define ANY 0
+
+const char in_addrany[] = {0, 0, 0, 0};
+const char in_loopback[] = {127, 0, 0, 1};
+const char in6_addrany[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const char in6_loopback[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
+
+const struct afd {
+    int a_af;
+    int a_addrlen;
+    int a_socklen;
+    int a_off;
+    const char* a_addrany;
+    const char* a_loopback;
+    int a_scoped;
+} afdl[] = {
+        {PF_INET6, sizeof(struct in6_addr), sizeof(struct sockaddr_in6),
+         offsetof(struct sockaddr_in6, sin6_addr), in6_addrany, in6_loopback, 1},
+        {PF_INET, sizeof(struct in_addr), sizeof(struct sockaddr_in),
+         offsetof(struct sockaddr_in, sin_addr), in_addrany, in_loopback, 0},
+        {0, 0, 0, 0, NULL, NULL, 0},
+};
+
+struct Explore {
+    int e_af;
+    int e_socktype;
+    int e_protocol;
+    int e_wild;
+#define WILD_AF(ex) ((ex).e_wild & 0x01)
+#define WILD_SOCKTYPE(ex) ((ex).e_wild & 0x02)
+#define WILD_PROTOCOL(ex) ((ex).e_wild & 0x04)
+};
+
+const Explore explore_options[] = {
+        {PF_INET6, SOCK_DGRAM, IPPROTO_UDP, 0x07},
+        {PF_INET6, SOCK_STREAM, IPPROTO_TCP, 0x07},
+        {PF_INET6, SOCK_RAW, ANY, 0x05},
+        {PF_INET, SOCK_DGRAM, IPPROTO_UDP, 0x07},
+        {PF_INET, SOCK_STREAM, IPPROTO_TCP, 0x07},
+        {PF_INET, SOCK_RAW, ANY, 0x05},
+        {PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, 0x07},
+        {PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0x07},
+        {PF_UNSPEC, SOCK_RAW, ANY, 0x05},
+};
+
+#define PTON_MAX 16
+#define MAXPACKET (8 * 1024)
+
+typedef union {
+    HEADER hdr;
+    u_char buf[MAXPACKET];
+} querybuf;
+
+struct res_target {
+    struct res_target* next;
+    const char* name;  /* domain name */
+    int qclass, qtype; /* class and type of query */
+    u_char* answer;    /* buffer to put answer */
+    int anslen;        /* size of answer buffer */
+    int n;             /* result length */
+};
+
+static int str2number(const char*);
+static int explore_fqdn(const struct addrinfo*, const char*, const char*, struct addrinfo**,
+                        const struct android_net_context*);
+static int explore_null(const struct addrinfo*, const char*, struct addrinfo**);
+static int explore_numeric(const struct addrinfo*, const char*, const char*, struct addrinfo**,
+                           const char*);
+static int explore_numeric_scope(const struct addrinfo*, const char*, const char*,
+                                 struct addrinfo**);
+static int get_canonname(const struct addrinfo*, struct addrinfo*, const char*);
+static struct addrinfo* get_ai(const struct addrinfo*, const struct afd*, const char*);
+static int get_portmatch(const struct addrinfo*, const char*);
+static int get_port(const struct addrinfo*, const char*, int);
+static const struct afd* find_afd(int);
+static int ip6_str2scopeid(const char*, struct sockaddr_in6*, u_int32_t*);
+
+static struct addrinfo* getanswer(const querybuf*, int, const char*, int, const struct addrinfo*,
+                                  int* herrno);
+static int dns_getaddrinfo(const char* name, const addrinfo* pai,
+                           const android_net_context* netcontext, addrinfo** rv);
+static void _sethtent(FILE**);
+static void _endhtent(FILE**);
+static struct addrinfo* _gethtent(FILE**, const char*, const struct addrinfo*);
+static bool files_getaddrinfo(const char* name, const addrinfo* pai, addrinfo** res);
+static int _find_src_addr(const struct sockaddr*, struct sockaddr*, unsigned, uid_t);
+
+static int res_queryN(const char* name, res_target* target, res_state res, int* herrno);
+static int res_searchN(const char* name, res_target* target, res_state res, int* herrno);
+static int res_querydomainN(const char* name, const char* domain, res_target* target, res_state res,
+                            int* herrno);
+
+const char* const ai_errlist[] = {
+        "Success",
+        "Address family for hostname not supported",    /* EAI_ADDRFAMILY */
+        "Temporary failure in name resolution",         /* EAI_AGAIN      */
+        "Invalid value for ai_flags",                   /* EAI_BADFLAGS   */
+        "Non-recoverable failure in name resolution",   /* EAI_FAIL       */
+        "ai_family not supported",                      /* EAI_FAMILY     */
+        "Memory allocation failure",                    /* EAI_MEMORY     */
+        "No address associated with hostname",          /* EAI_NODATA     */
+        "hostname nor servname provided, or not known", /* EAI_NONAME     */
+        "servname not supported for ai_socktype",       /* EAI_SERVICE    */
+        "ai_socktype not supported",                    /* EAI_SOCKTYPE   */
+        "System error returned in errno",               /* EAI_SYSTEM     */
+        "Invalid value for hints",                      /* EAI_BADHINTS	  */
+        "Resolved protocol is unknown",                 /* EAI_PROTOCOL   */
+        "Argument buffer overflow",                     /* EAI_OVERFLOW   */
+        "Unknown error",                                /* EAI_MAX        */
+};
+
+/* XXX macros that make external reference is BAD. */
+
+#define GET_AI(ai, afd, addr)                                \
+    do {                                                     \
+        /* external reference: pai, error, and label free */ \
+        (ai) = get_ai(pai, (afd), (addr));                   \
+        if ((ai) == NULL) {                                  \
+            error = EAI_MEMORY;                              \
+            goto free;                                       \
+        }                                                    \
+    } while (0)
+
+#define GET_PORT(ai, serv)                             \
+    do {                                               \
+        /* external reference: error and label free */ \
+        error = get_port((ai), (serv), 0);             \
+        if (error != 0) goto free;                     \
+    } while (0)
+
+#define MATCH_FAMILY(x, y, w) \
+    ((x) == (y) || ((w) && ((x) == PF_UNSPEC || (y) == PF_UNSPEC)))
+#define MATCH(x, y, w) ((x) == (y) || ((w) && ((x) == ANY || (y) == ANY)))
+
+const char* gai_strerror(int ecode) {
+    if (ecode < 0 || ecode > EAI_MAX) ecode = EAI_MAX;
+    return ai_errlist[ecode];
+}
+
+void freeaddrinfo(struct addrinfo* ai) {
+    while (ai) {
+        struct addrinfo* next = ai->ai_next;
+        if (ai->ai_canonname) free(ai->ai_canonname);
+        // Also frees ai->ai_addr which points to extra space beyond addrinfo
+        free(ai);
+        ai = next;
+    }
+}
+
+static int str2number(const char* p) {
+    char* ep;
+    unsigned long v;
+
+    assert(p != NULL);
+
+    if (*p == '\0') return -1;
+    ep = NULL;
+    errno = 0;
+    v = strtoul(p, &ep, 10);
+    if (errno == 0 && ep && *ep == '\0' && v <= UINT_MAX)
+        return v;
+    else
+        return -1;
+}
+
+/*
+ * The following functions determine whether IPv4 or IPv6 connectivity is
+ * available in order to implement AI_ADDRCONFIG.
+ *
+ * Strictly speaking, AI_ADDRCONFIG should not look at whether connectivity is
+ * available, but whether addresses of the specified family are "configured
+ * on the local system". However, bionic doesn't currently support getifaddrs,
+ * so checking for connectivity is the next best thing.
+ */
+static int have_ipv6(unsigned mark, uid_t uid) {
+    static const struct sockaddr_in6 sin6_test = {
+            .sin6_family = AF_INET6,
+            .sin6_addr.s6_addr = {// 2000::
+                                  0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
+    sockaddr_union addr = {.sin6 = sin6_test};
+    return _find_src_addr(&addr.sa, NULL, mark, uid) == 1;
+}
+
+static int have_ipv4(unsigned mark, uid_t uid) {
+    static const struct sockaddr_in sin_test = {
+            .sin_family = AF_INET,
+            .sin_addr.s_addr = __constant_htonl(0x08080808L)  // 8.8.8.8
+    };
+    sockaddr_union addr = {.sin = sin_test};
+    return _find_src_addr(&addr.sa, NULL, mark, uid) == 1;
+}
+
+// Internal version of getaddrinfo(), but limited to AI_NUMERICHOST.
+// NOTE: also called by resolv_set_nameservers_for_net().
+int getaddrinfo_numeric(const char* hostname, const char* servname, addrinfo hints,
+                        addrinfo** result) {
+    hints.ai_flags = AI_NUMERICHOST;
+    const android_net_context netcontext = {
+            .app_netid = NETID_UNSET,
+            .app_mark = MARK_UNSET,
+            .dns_netid = NETID_UNSET,
+            .dns_mark = MARK_UNSET,
+            .uid = NET_CONTEXT_INVALID_UID,
+    };
+    return android_getaddrinfofornetcontext(hostname, servname, &hints, &netcontext, result);
+}
+
+int android_getaddrinfofornetcontext(const char* hostname, const char* servname,
+                                     const struct addrinfo* hints,
+                                     const struct android_net_context* netcontext,
+                                     struct addrinfo** res) {
+    struct addrinfo sentinel = {};
+    struct addrinfo* cur = &sentinel;
+    int error = 0;
+
+    // hostname is allowed to be nullptr
+    // servname is allowed to be nullptr
+    // hints is allowed to be nullptr
+    assert(res != nullptr);
+    assert(netcontext != nullptr);
+
+    struct addrinfo ai = {
+            .ai_flags = 0,
+            .ai_family = PF_UNSPEC,
+            .ai_socktype = ANY,
+            .ai_protocol = ANY,
+            .ai_addrlen = 0,
+            .ai_canonname = nullptr,
+            .ai_addr = nullptr,
+            .ai_next = nullptr,
+    };
+
+    do {
+        if (hostname == NULL && servname == NULL) {
+            error = EAI_NONAME;
+            break;
+        }
+        if (hints) {
+            /* error check for hints */
+            if (hints->ai_addrlen || hints->ai_canonname || hints->ai_addr || hints->ai_next) {
+                error = EAI_BADHINTS;
+                break;
+            }
+            if (hints->ai_flags & ~AI_MASK) {
+                error = EAI_BADFLAGS;
+                break;
+            }
+
+            if (!(hints->ai_family == PF_UNSPEC || hints->ai_family == PF_INET ||
+                  hints->ai_family == PF_INET6)) {
+                error = EAI_FAMILY;
+                break;
+            }
+
+            ai = *hints;
+
+            /*
+             * if both socktype/protocol are specified, check if they
+             * are meaningful combination.
+             */
+            if (ai.ai_socktype != ANY && ai.ai_protocol != ANY) {
+                for (const Explore& ex : explore_options) {
+                    if (ai.ai_family != ex.e_af) continue;
+                    if (ex.e_socktype == ANY) continue;
+                    if (ex.e_protocol == ANY) continue;
+                    if (ai.ai_socktype == ex.e_socktype && ai.ai_protocol != ex.e_protocol) {
+                        error = EAI_BADHINTS;
+                        break;
+                    }
+                }
+                if (error) break;
+            }
+        }
+
+        /*
+         * Check for special cases:
+         * (1) numeric servname is disallowed if socktype/protocol are left unspecified.
+         * (2) servname is disallowed for raw and other inet{,6} sockets.
+         */
+        if (MATCH_FAMILY(ai.ai_family, PF_INET, 1) || MATCH_FAMILY(ai.ai_family, PF_INET6, 1)) {
+            struct addrinfo tmp = ai;
+            if (tmp.ai_family == PF_UNSPEC) {
+                tmp.ai_family = PF_INET6;
+            }
+            error = get_portmatch(&tmp, servname);
+            if (error) break;
+        }
+
+        // NULL hostname, or numeric hostname
+        for (const Explore& ex : explore_options) {
+            /* PF_UNSPEC entries are prepared for DNS queries only */
+            if (ex.e_af == PF_UNSPEC) continue;
+
+            if (!MATCH_FAMILY(ai.ai_family, ex.e_af, WILD_AF(ex))) continue;
+            if (!MATCH(ai.ai_socktype, ex.e_socktype, WILD_SOCKTYPE(ex))) continue;
+            if (!MATCH(ai.ai_protocol, ex.e_protocol, WILD_PROTOCOL(ex))) continue;
+
+            struct addrinfo tmp = ai;
+            if (tmp.ai_family == PF_UNSPEC) tmp.ai_family = ex.e_af;
+            if (tmp.ai_socktype == ANY && ex.e_socktype != ANY) tmp.ai_socktype = ex.e_socktype;
+            if (tmp.ai_protocol == ANY && ex.e_protocol != ANY) tmp.ai_protocol = ex.e_protocol;
+
+            LOG(DEBUG) << __func__ << ": explore_numeric: ai_family=" << tmp.ai_family
+                       << " ai_socktype=" << tmp.ai_socktype << " ai_protocol=" << tmp.ai_protocol;
+            if (hostname == nullptr)
+                error = explore_null(&tmp, servname, &cur->ai_next);
+            else
+                error = explore_numeric_scope(&tmp, hostname, servname, &cur->ai_next);
+
+            if (error) break;
+
+            while (cur->ai_next) cur = cur->ai_next;
+        }
+        if (error) break;
+
+        /*
+         * XXX
+         * If numeric representation of AF1 can be interpreted as FQDN
+         * representation of AF2, we need to think again about the code below.
+         */
+        if (sentinel.ai_next) break;
+
+        if (hostname == nullptr) {
+            error = EAI_NODATA;
+            break;
+        }
+        if (ai.ai_flags & AI_NUMERICHOST) {
+            error = EAI_NONAME;
+            break;
+        }
+
+        /*
+         * hostname as alphabetical name.
+         * We would like to prefer AF_INET6 over AF_INET, so we'll make a outer loop by AFs.
+         */
+        for (const Explore& ex : explore_options) {
+            // Require exact match for family field
+            if (ai.ai_family != ex.e_af) continue;
+
+            if (!MATCH(ai.ai_socktype, ex.e_socktype, WILD_SOCKTYPE(ex))) {
+                continue;
+            }
+            if (!MATCH(ai.ai_protocol, ex.e_protocol, WILD_PROTOCOL(ex))) {
+                continue;
+            }
+
+            struct addrinfo tmp = ai;
+            if (tmp.ai_socktype == ANY && ex.e_socktype != ANY) tmp.ai_socktype = ex.e_socktype;
+            if (tmp.ai_protocol == ANY && ex.e_protocol != ANY) tmp.ai_protocol = ex.e_protocol;
+
+            LOG(DEBUG) << __func__ << ": explore_fqdn(): ai_family=" << tmp.ai_family
+                       << " ai_socktype=" << tmp.ai_socktype << " ai_protocol=" << tmp.ai_protocol;
+            error = explore_fqdn(&tmp, hostname, servname, &cur->ai_next, netcontext);
+
+            while (cur->ai_next) cur = cur->ai_next;
+        }
+
+        if (sentinel.ai_next) {
+            error = 0;
+        } else if (error == 0) {
+            error = EAI_FAIL;
+        }
+    } while (0);
+
+    if (error) {
+        freeaddrinfo(sentinel.ai_next);
+        *res = nullptr;
+    } else {
+        *res = sentinel.ai_next;
+    }
+    return error;
+}
+
+// FQDN hostname, DNS lookup
+static int explore_fqdn(const struct addrinfo* pai, const char* hostname, const char* servname,
+                        struct addrinfo** res, const struct android_net_context* netcontext) {
+    struct addrinfo* result;
+    int error = 0;
+
+    assert(pai != NULL);
+    /* hostname may be NULL */
+    /* servname may be NULL */
+    assert(res != NULL);
+
+    result = NULL;
+
+    // If the servname does not match socktype/protocol, ignore it.
+    if (get_portmatch(pai, servname) != 0) return 0;
+
+    if (!files_getaddrinfo(hostname, pai, &result)) {
+        error = dns_getaddrinfo(hostname, pai, netcontext, &result);
+    }
+    if (!error) {
+        struct addrinfo* cur;
+        for (cur = result; cur; cur = cur->ai_next) {
+            GET_PORT(cur, servname);
+            /* canonname should be filled already */
+        }
+        *res = result;
+        return 0;
+    }
+
+free:
+    freeaddrinfo(result);
+    return error;
+}
+
+/*
+ * hostname == NULL.
+ * passive socket -> anyaddr (0.0.0.0 or ::)
+ * non-passive socket -> localhost (127.0.0.1 or ::1)
+ */
+static int explore_null(const struct addrinfo* pai, const char* servname, struct addrinfo** res) {
+    int s;
+    const struct afd* afd;
+    struct addrinfo* cur;
+    struct addrinfo sentinel;
+    int error;
+
+    LOG(DEBUG) << __func__;
+
+    assert(pai != NULL);
+    /* servname may be NULL */
+    assert(res != NULL);
+
+    *res = NULL;
+    sentinel.ai_next = NULL;
+    cur = &sentinel;
+
+    /*
+     * filter out AFs that are not supported by the kernel
+     * XXX errno?
+     */
+    s = socket(pai->ai_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s < 0) {
+        if (errno != EMFILE) return 0;
+    } else
+        close(s);
+
+    /*
+     * if the servname does not match socktype/protocol, ignore it.
+     */
+    if (get_portmatch(pai, servname) != 0) return 0;
+
+    afd = find_afd(pai->ai_family);
+    if (afd == NULL) return 0;
+
+    if (pai->ai_flags & AI_PASSIVE) {
+        GET_AI(cur->ai_next, afd, afd->a_addrany);
+        GET_PORT(cur->ai_next, servname);
+    } else {
+        GET_AI(cur->ai_next, afd, afd->a_loopback);
+        GET_PORT(cur->ai_next, servname);
+    }
+    cur = cur->ai_next;
+
+    *res = sentinel.ai_next;
+    return 0;
+
+free:
+    freeaddrinfo(sentinel.ai_next);
+    return error;
+}
+
+/*
+ * numeric hostname
+ */
+static int explore_numeric(const struct addrinfo* pai, const char* hostname, const char* servname,
+                           struct addrinfo** res, const char* canonname) {
+    const struct afd* afd;
+    struct addrinfo* cur;
+    struct addrinfo sentinel;
+    int error;
+    char pton[PTON_MAX];
+
+    assert(pai != NULL);
+    /* hostname may be NULL */
+    /* servname may be NULL */
+    assert(res != NULL);
+
+    *res = NULL;
+    sentinel.ai_next = NULL;
+    cur = &sentinel;
+
+    /*
+     * if the servname does not match socktype/protocol, ignore it.
+     */
+    if (get_portmatch(pai, servname) != 0) return 0;
+
+    afd = find_afd(pai->ai_family);
+    if (afd == NULL) return 0;
+
+    if (inet_pton(afd->a_af, hostname, pton) == 1) {
+        if (pai->ai_family == afd->a_af || pai->ai_family == PF_UNSPEC /*?*/) {
+            GET_AI(cur->ai_next, afd, pton);
+            GET_PORT(cur->ai_next, servname);
+            if ((pai->ai_flags & AI_CANONNAME)) {
+                /*
+                 * Set the numeric address itself as
+                 * the canonical name, based on a
+                 * clarification in rfc2553bis-03.
+                 */
+                error = get_canonname(pai, cur->ai_next, canonname);
+                if (error != 0) {
+                    freeaddrinfo(sentinel.ai_next);
+                    return error;
+                }
+            }
+            while (cur->ai_next) cur = cur->ai_next;
+        } else
+            return EAI_FAMILY;
+    }
+
+    *res = sentinel.ai_next;
+    return 0;
+
+free:
+    freeaddrinfo(sentinel.ai_next);
+    return error;
+}
+
+/*
+ * numeric hostname with scope
+ */
+static int explore_numeric_scope(const struct addrinfo* pai, const char* hostname,
+                                 const char* servname, struct addrinfo** res) {
+    const struct afd* afd;
+    struct addrinfo* cur;
+    int error;
+    const char *cp, *scope, *addr;
+    struct sockaddr_in6* sin6;
+
+    LOG(DEBUG) << __func__;
+
+    assert(pai != NULL);
+    /* hostname may be NULL */
+    /* servname may be NULL */
+    assert(res != NULL);
+
+    /*
+     * if the servname does not match socktype/protocol, ignore it.
+     */
+    if (get_portmatch(pai, servname) != 0) return 0;
+
+    afd = find_afd(pai->ai_family);
+    if (afd == NULL) return 0;
+
+    if (!afd->a_scoped) return explore_numeric(pai, hostname, servname, res, hostname);
+
+    cp = strchr(hostname, SCOPE_DELIMITER);
+    if (cp == NULL) return explore_numeric(pai, hostname, servname, res, hostname);
+
+    /*
+     * Handle special case of <scoped_address><delimiter><scope id>
+     */
+    char* hostname2 = strdup(hostname);
+    if (hostname2 == NULL) return EAI_MEMORY;
+    /* terminate at the delimiter */
+    hostname2[cp - hostname] = '\0';
+    addr = hostname2;
+    scope = cp + 1;
+
+    error = explore_numeric(pai, addr, servname, res, hostname);
+    if (error == 0) {
+        u_int32_t scopeid;
+
+        for (cur = *res; cur; cur = cur->ai_next) {
+            if (cur->ai_family != AF_INET6) continue;
+            sin6 = (struct sockaddr_in6*) (void*) cur->ai_addr;
+            if (ip6_str2scopeid(scope, sin6, &scopeid) == -1) {
+                free(hostname2);
+                return (EAI_NODATA); /* XXX: is return OK? */
+            }
+            sin6->sin6_scope_id = scopeid;
+        }
+    }
+
+    free(hostname2);
+
+    return error;
+}
+
+static int get_canonname(const struct addrinfo* pai, struct addrinfo* ai, const char* str) {
+    assert(pai != NULL);
+    assert(ai != NULL);
+    assert(str != NULL);
+
+    if ((pai->ai_flags & AI_CANONNAME) != 0) {
+        ai->ai_canonname = strdup(str);
+        if (ai->ai_canonname == NULL) return EAI_MEMORY;
+    }
+    return 0;
+}
+
+static struct addrinfo* get_ai(const struct addrinfo* pai, const struct afd* afd,
+                               const char* addr) {
+    char* p;
+    struct addrinfo* ai;
+
+    assert(pai != NULL);
+    assert(afd != NULL);
+    assert(addr != NULL);
+
+    ai = (struct addrinfo*) malloc(sizeof(struct addrinfo) + sizeof(sockaddr_union));
+    if (ai == NULL) return NULL;
+
+    memcpy(ai, pai, sizeof(struct addrinfo));
+    ai->ai_addr = (struct sockaddr*) (void*) (ai + 1);
+    memset(ai->ai_addr, 0, sizeof(sockaddr_union));
+
+    ai->ai_addrlen = afd->a_socklen;
+    ai->ai_addr->sa_family = ai->ai_family = afd->a_af;
+    p = (char*) (void*) (ai->ai_addr);
+    memcpy(p + afd->a_off, addr, (size_t) afd->a_addrlen);
+    return ai;
+}
+
+static int get_portmatch(const struct addrinfo* ai, const char* servname) {
+    assert(ai != NULL);
+    /* servname may be NULL */
+
+    return get_port(ai, servname, 1);
+}
+
+static int get_port(const struct addrinfo* ai, const char* servname, int matchonly) {
+    const char* proto;
+    struct servent* sp;
+    int port;
+    int allownumeric;
+
+    assert(ai != NULL);
+    /* servname may be NULL */
+
+    if (servname == NULL) return 0;
+    switch (ai->ai_family) {
+        case AF_INET:
+        case AF_INET6:
+            break;
+        default:
+            return 0;
+    }
+
+    switch (ai->ai_socktype) {
+        case SOCK_RAW:
+            return EAI_SERVICE;
+        case SOCK_DGRAM:
+        case SOCK_STREAM:
+            allownumeric = 1;
+            break;
+        case ANY:
+            allownumeric = 1;
+            break;
+        default:
+            return EAI_SOCKTYPE;
+    }
+
+    port = str2number(servname);
+    if (port >= 0) {
+        if (!allownumeric) return EAI_SERVICE;
+        if (port < 0 || port > 65535) return EAI_SERVICE;
+        port = htons(port);
+    } else {
+        if (ai->ai_flags & AI_NUMERICSERV) return EAI_NONAME;
+
+        switch (ai->ai_socktype) {
+            case SOCK_DGRAM:
+                proto = "udp";
+                break;
+            case SOCK_STREAM:
+                proto = "tcp";
+                break;
+            default:
+                proto = NULL;
+                break;
+        }
+
+        if ((sp = getservbyname(servname, proto)) == NULL) return EAI_SERVICE;
+        port = sp->s_port;
+    }
+
+    if (!matchonly) {
+        switch (ai->ai_family) {
+            case AF_INET:
+                ((struct sockaddr_in*) (void*) ai->ai_addr)->sin_port = port;
+                break;
+            case AF_INET6:
+                ((struct sockaddr_in6*) (void*) ai->ai_addr)->sin6_port = port;
+                break;
+        }
+    }
+
+    return 0;
+}
+
+static const struct afd* find_afd(int af) {
+    const struct afd* afd;
+
+    if (af == PF_UNSPEC) return NULL;
+    for (afd = afdl; afd->a_af; afd++) {
+        if (afd->a_af == af) return afd;
+    }
+    return NULL;
+}
+
+// Convert a string to a scope identifier.
+static int ip6_str2scopeid(const char* scope, struct sockaddr_in6* sin6, u_int32_t* scopeid) {
+    u_long lscopeid;
+    struct in6_addr* a6;
+    char* ep;
+
+    assert(scope != NULL);
+    assert(sin6 != NULL);
+    assert(scopeid != NULL);
+
+    a6 = &sin6->sin6_addr;
+
+    /* empty scopeid portion is invalid */
+    if (*scope == '\0') return -1;
+
+    if (IN6_IS_ADDR_LINKLOCAL(a6) || IN6_IS_ADDR_MC_LINKLOCAL(a6)) {
+        /*
+         * We currently assume a one-to-one mapping between links
+         * and interfaces, so we simply use interface indices for
+         * like-local scopes.
+         */
+        *scopeid = if_nametoindex(scope);
+        if (*scopeid == 0) goto trynumeric;
+        return 0;
+    }
+
+    /* still unclear about literal, allow numeric only - placeholder */
+    if (IN6_IS_ADDR_SITELOCAL(a6) || IN6_IS_ADDR_MC_SITELOCAL(a6)) goto trynumeric;
+    if (IN6_IS_ADDR_MC_ORGLOCAL(a6))
+        goto trynumeric;
+    else
+        goto trynumeric; /* global */
+
+    /* try to convert to a numeric id as a last resort */
+trynumeric:
+    errno = 0;
+    lscopeid = strtoul(scope, &ep, 10);
+    *scopeid = (u_int32_t)(lscopeid & 0xffffffffUL);
+    if (errno == 0 && ep && *ep == '\0' && *scopeid == lscopeid)
+        return 0;
+    else
+        return -1;
+}
+
+/* code duplicate with gethnamaddr.c */
+
+#define BOUNDED_INCR(x)      \
+    do {                     \
+        BOUNDS_CHECK(cp, x); \
+        cp += (x);           \
+    } while (0)
+
+#define BOUNDS_CHECK(ptr, count)     \
+    do {                             \
+        if (eom - (ptr) < (count)) { \
+            *herrno = NO_RECOVERY;   \
+            return NULL;             \
+        }                            \
+    } while (0)
+
+static struct addrinfo* getanswer(const querybuf* answer, int anslen, const char* qname, int qtype,
+                                  const struct addrinfo* pai, int* herrno) {
+    struct addrinfo sentinel = {};
+    struct addrinfo *cur;
+    struct addrinfo ai;
+    const struct afd* afd;
+    char* canonname;
+    const HEADER* hp;
+    const u_char* cp;
+    int n;
+    const u_char* eom;
+    char *bp, *ep;
+    int type, ancount, qdcount;
+    int haveanswer, had_error;
+    char tbuf[MAXDNAME];
+    int (*name_ok)(const char*);
+    char hostbuf[8 * 1024];
+
+    assert(answer != NULL);
+    assert(qname != NULL);
+    assert(pai != NULL);
+
+    cur = &sentinel;
+
+    canonname = NULL;
+    eom = answer->buf + anslen;
+    switch (qtype) {
+        case T_A:
+        case T_AAAA:
+        case T_ANY: /*use T_ANY only for T_A/T_AAAA lookup*/
+            name_ok = res_hnok;
+            break;
+        default:
+            return NULL; /* XXX should be abort(); */
+    }
+    /*
+     * find first satisfactory answer
+     */
+    hp = &answer->hdr;
+    ancount = ntohs(hp->ancount);
+    qdcount = ntohs(hp->qdcount);
+    bp = hostbuf;
+    ep = hostbuf + sizeof hostbuf;
+    cp = answer->buf;
+    BOUNDED_INCR(HFIXEDSZ);
+    if (qdcount != 1) {
+        *herrno = NO_RECOVERY;
+        return (NULL);
+    }
+    n = dn_expand(answer->buf, eom, cp, bp, ep - bp);
+    if ((n < 0) || !(*name_ok)(bp)) {
+        *herrno = NO_RECOVERY;
+        return (NULL);
+    }
+    BOUNDED_INCR(n + QFIXEDSZ);
+    if (qtype == T_A || qtype == T_AAAA || qtype == T_ANY) {
+        /* res_send() has already verified that the query name is the
+         * same as the one we sent; this just gets the expanded name
+         * (i.e., with the succeeding search-domain tacked on).
+         */
+        n = strlen(bp) + 1; /* for the \0 */
+        if (n >= MAXHOSTNAMELEN) {
+            *herrno = NO_RECOVERY;
+            return (NULL);
+        }
+        canonname = bp;
+        bp += n;
+        /* The qname can be abbreviated, but h_name is now absolute. */
+        qname = canonname;
+    }
+    haveanswer = 0;
+    had_error = 0;
+    while (ancount-- > 0 && cp < eom && !had_error) {
+        n = dn_expand(answer->buf, eom, cp, bp, ep - bp);
+        if ((n < 0) || !(*name_ok)(bp)) {
+            had_error++;
+            continue;
+        }
+        cp += n; /* name */
+        BOUNDS_CHECK(cp, 3 * INT16SZ + INT32SZ);
+        type = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ; /* type */
+        int cl = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ + INT32SZ; /* class, TTL */
+        n = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ; /* len */
+        BOUNDS_CHECK(cp, n);
+        if (cl != C_IN) {
+            /* XXX - debug? syslog? */
+            cp += n;
+            continue; /* XXX - had_error++ ? */
+        }
+        if ((qtype == T_A || qtype == T_AAAA || qtype == T_ANY) && type == T_CNAME) {
+            n = dn_expand(answer->buf, eom, cp, tbuf, sizeof tbuf);
+            if ((n < 0) || !(*name_ok)(tbuf)) {
+                had_error++;
+                continue;
+            }
+            cp += n;
+            /* Get canonical name. */
+            n = strlen(tbuf) + 1; /* for the \0 */
+            if (n > ep - bp || n >= MAXHOSTNAMELEN) {
+                had_error++;
+                continue;
+            }
+            strlcpy(bp, tbuf, (size_t)(ep - bp));
+            canonname = bp;
+            bp += n;
+            continue;
+        }
+        if (qtype == T_ANY) {
+            if (!(type == T_A || type == T_AAAA)) {
+                cp += n;
+                continue;
+            }
+        } else if (type != qtype) {
+            if (type != T_KEY && type != T_SIG)
+                LOG(DEBUG) << __func__ << ": asked for \"" << qname << " " << p_class(C_IN) << " "
+                           << p_type(qtype) << "\", got type \"" << p_type(type) << "\"";
+            cp += n;
+            continue; /* XXX - had_error++ ? */
+        }
+        switch (type) {
+            case T_A:
+            case T_AAAA:
+                if (strcasecmp(canonname, bp) != 0) {
+                    LOG(DEBUG) << __func__ << ": asked for \"" << canonname << "\", got \"" << bp
+                               << "\"";
+                    cp += n;
+                    continue; /* XXX - had_error++ ? */
+                }
+                if (type == T_A && n != INADDRSZ) {
+                    cp += n;
+                    continue;
+                }
+                if (type == T_AAAA && n != IN6ADDRSZ) {
+                    cp += n;
+                    continue;
+                }
+                if (type == T_AAAA) {
+                    struct in6_addr in6;
+                    memcpy(&in6, cp, IN6ADDRSZ);
+                    if (IN6_IS_ADDR_V4MAPPED(&in6)) {
+                        cp += n;
+                        continue;
+                    }
+                }
+                if (!haveanswer) {
+                    int nn;
+
+                    canonname = bp;
+                    nn = strlen(bp) + 1; /* for the \0 */
+                    bp += nn;
+                }
+
+                /* don't overwrite pai */
+                ai = *pai;
+                ai.ai_family = (type == T_A) ? AF_INET : AF_INET6;
+                afd = find_afd(ai.ai_family);
+                if (afd == NULL) {
+                    cp += n;
+                    continue;
+                }
+                cur->ai_next = get_ai(&ai, afd, (const char*) cp);
+                if (cur->ai_next == NULL) had_error++;
+                while (cur && cur->ai_next) cur = cur->ai_next;
+                cp += n;
+                break;
+            default:
+                abort();
+        }
+        if (!had_error) haveanswer++;
+    }
+    if (haveanswer) {
+        if (!canonname)
+            (void) get_canonname(pai, sentinel.ai_next, qname);
+        else
+            (void) get_canonname(pai, sentinel.ai_next, canonname);
+        *herrno = NETDB_SUCCESS;
+        return sentinel.ai_next;
+    }
+
+    *herrno = NO_RECOVERY;
+    return NULL;
+}
+
+struct addrinfo_sort_elem {
+    struct addrinfo* ai;
+    int has_src_addr;
+    sockaddr_union src_addr;
+    int original_order;
+};
+
+static int _get_scope(const struct sockaddr* addr) {
+    if (addr->sa_family == AF_INET6) {
+        const struct sockaddr_in6* addr6 = (const struct sockaddr_in6*) addr;
+        if (IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr)) {
+            return IPV6_ADDR_MC_SCOPE(&addr6->sin6_addr);
+        } else if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr) ||
+                   IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr)) {
+            /*
+             * RFC 4291 section 2.5.3 says loopback is to be treated as having
+             * link-local scope.
+             */
+            return IPV6_ADDR_SCOPE_LINKLOCAL;
+        } else if (IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr)) {
+            return IPV6_ADDR_SCOPE_SITELOCAL;
+        } else {
+            return IPV6_ADDR_SCOPE_GLOBAL;
+        }
+    } else if (addr->sa_family == AF_INET) {
+        const struct sockaddr_in* addr4 = (const struct sockaddr_in*) addr;
+        unsigned long int na = ntohl(addr4->sin_addr.s_addr);
+
+        if (IN_LOOPBACK(na) ||                 /* 127.0.0.0/8 */
+            (na & 0xffff0000) == 0xa9fe0000) { /* 169.254.0.0/16 */
+            return IPV6_ADDR_SCOPE_LINKLOCAL;
+        } else {
+            /*
+             * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
+             * and shared addresses (100.64.0.0/10), are assigned global scope.
+             */
+            return IPV6_ADDR_SCOPE_GLOBAL;
+        }
+    } else {
+        /*
+         * This should never happen.
+         * Return a scope with low priority as a last resort.
+         */
+        return IPV6_ADDR_SCOPE_NODELOCAL;
+    }
+}
+
+/* These macros are modelled after the ones in <netinet/in6.h>. */
+
+/* RFC 4380, section 2.6 */
+#define IN6_IS_ADDR_TEREDO(a) \
+    ((*(const uint32_t*) (const void*) (&(a)->s6_addr[0]) == ntohl(0x20010000)))
+
+/* RFC 3056, section 2. */
+#define IN6_IS_ADDR_6TO4(a) (((a)->s6_addr[0] == 0x20) && ((a)->s6_addr[1] == 0x02))
+
+/* 6bone testing address area (3ffe::/16), deprecated in RFC 3701. */
+#define IN6_IS_ADDR_6BONE(a) (((a)->s6_addr[0] == 0x3f) && ((a)->s6_addr[1] == 0xfe))
+
+/*
+ * Get the label for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ */
+
+static int _get_label(const struct sockaddr* addr) {
+    if (addr->sa_family == AF_INET) {
+        return 4;
+    } else if (addr->sa_family == AF_INET6) {
+        const struct sockaddr_in6* addr6 = (const struct sockaddr_in6*) addr;
+        if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr)) {
+            return 0;
+        } else if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
+            return 4;
+        } else if (IN6_IS_ADDR_6TO4(&addr6->sin6_addr)) {
+            return 2;
+        } else if (IN6_IS_ADDR_TEREDO(&addr6->sin6_addr)) {
+            return 5;
+        } else if (IN6_IS_ADDR_ULA(&addr6->sin6_addr)) {
+            return 13;
+        } else if (IN6_IS_ADDR_V4COMPAT(&addr6->sin6_addr)) {
+            return 3;
+        } else if (IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr)) {
+            return 11;
+        } else if (IN6_IS_ADDR_6BONE(&addr6->sin6_addr)) {
+            return 12;
+        } else {
+            /* All other IPv6 addresses, including global unicast addresses. */
+            return 1;
+        }
+    } else {
+        /*
+         * This should never happen.
+         * Return a semi-random label as a last resort.
+         */
+        return 1;
+    }
+}
+
+/*
+ * Get the precedence for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ */
+
+static int _get_precedence(const struct sockaddr* addr) {
+    if (addr->sa_family == AF_INET) {
+        return 35;
+    } else if (addr->sa_family == AF_INET6) {
+        const struct sockaddr_in6* addr6 = (const struct sockaddr_in6*) addr;
+        if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr)) {
+            return 50;
+        } else if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
+            return 35;
+        } else if (IN6_IS_ADDR_6TO4(&addr6->sin6_addr)) {
+            return 30;
+        } else if (IN6_IS_ADDR_TEREDO(&addr6->sin6_addr)) {
+            return 5;
+        } else if (IN6_IS_ADDR_ULA(&addr6->sin6_addr)) {
+            return 3;
+        } else if (IN6_IS_ADDR_V4COMPAT(&addr6->sin6_addr) ||
+                   IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr) ||
+                   IN6_IS_ADDR_6BONE(&addr6->sin6_addr)) {
+            return 1;
+        } else {
+            /* All other IPv6 addresses, including global unicast addresses. */
+            return 40;
+        }
+    } else {
+        return 1;
+    }
+}
+
+/*
+ * Find number of matching initial bits between the two addresses a1 and a2.
+ */
+
+static int _common_prefix_len(const struct in6_addr* a1, const struct in6_addr* a2) {
+    const char* p1 = (const char*) a1;
+    const char* p2 = (const char*) a2;
+    unsigned i;
+
+    for (i = 0; i < sizeof(*a1); ++i) {
+        int x, j;
+
+        if (p1[i] == p2[i]) {
+            continue;
+        }
+        x = p1[i] ^ p2[i];
+        for (j = 0; j < CHAR_BIT; ++j) {
+            if (x & (1 << (CHAR_BIT - 1))) {
+                return i * CHAR_BIT + j;
+            }
+            x <<= 1;
+        }
+    }
+    return sizeof(*a1) * CHAR_BIT;
+}
+
+/*
+ * Compare two source/destination address pairs.
+ * RFC 6724, section 6.
+ */
+
+static int _rfc6724_compare(const void* ptr1, const void* ptr2) {
+    const struct addrinfo_sort_elem* a1 = (const struct addrinfo_sort_elem*) ptr1;
+    const struct addrinfo_sort_elem* a2 = (const struct addrinfo_sort_elem*) ptr2;
+    int scope_src1, scope_dst1, scope_match1;
+    int scope_src2, scope_dst2, scope_match2;
+    int label_src1, label_dst1, label_match1;
+    int label_src2, label_dst2, label_match2;
+    int precedence1, precedence2;
+    int prefixlen1, prefixlen2;
+
+    /* Rule 1: Avoid unusable destinations. */
+    if (a1->has_src_addr != a2->has_src_addr) {
+        return a2->has_src_addr - a1->has_src_addr;
+    }
+
+    /* Rule 2: Prefer matching scope. */
+    scope_src1 = _get_scope(&a1->src_addr.sa);
+    scope_dst1 = _get_scope(a1->ai->ai_addr);
+    scope_match1 = (scope_src1 == scope_dst1);
+
+    scope_src2 = _get_scope(&a2->src_addr.sa);
+    scope_dst2 = _get_scope(a2->ai->ai_addr);
+    scope_match2 = (scope_src2 == scope_dst2);
+
+    if (scope_match1 != scope_match2) {
+        return scope_match2 - scope_match1;
+    }
+
+    /*
+     * Rule 3: Avoid deprecated addresses.
+     * TODO(sesse): We don't currently have a good way of finding this.
+     */
+
+    /*
+     * Rule 4: Prefer home addresses.
+     * TODO(sesse): We don't currently have a good way of finding this.
+     */
+
+    /* Rule 5: Prefer matching label. */
+    label_src1 = _get_label(&a1->src_addr.sa);
+    label_dst1 = _get_label(a1->ai->ai_addr);
+    label_match1 = (label_src1 == label_dst1);
+
+    label_src2 = _get_label(&a2->src_addr.sa);
+    label_dst2 = _get_label(a2->ai->ai_addr);
+    label_match2 = (label_src2 == label_dst2);
+
+    if (label_match1 != label_match2) {
+        return label_match2 - label_match1;
+    }
+
+    /* Rule 6: Prefer higher precedence. */
+    precedence1 = _get_precedence(a1->ai->ai_addr);
+    precedence2 = _get_precedence(a2->ai->ai_addr);
+    if (precedence1 != precedence2) {
+        return precedence2 - precedence1;
+    }
+
+    /*
+     * Rule 7: Prefer native transport.
+     * TODO(sesse): We don't currently have a good way of finding this.
+     */
+
+    /* Rule 8: Prefer smaller scope. */
+    if (scope_dst1 != scope_dst2) {
+        return scope_dst1 - scope_dst2;
+    }
+
+    /*
+     * Rule 9: Use longest matching prefix.
+     * We implement this for IPv6 only, as the rules in RFC 6724 don't seem
+     * to work very well directly applied to IPv4. (glibc uses information from
+     * the routing table for a custom IPv4 implementation here.)
+     */
+    if (a1->has_src_addr && a1->ai->ai_addr->sa_family == AF_INET6 && a2->has_src_addr &&
+        a2->ai->ai_addr->sa_family == AF_INET6) {
+        const struct sockaddr_in6* a1_src = &a1->src_addr.sin6;
+        const struct sockaddr_in6* a1_dst = (const struct sockaddr_in6*) a1->ai->ai_addr;
+        const struct sockaddr_in6* a2_src = &a2->src_addr.sin6;
+        const struct sockaddr_in6* a2_dst = (const struct sockaddr_in6*) a2->ai->ai_addr;
+        prefixlen1 = _common_prefix_len(&a1_src->sin6_addr, &a1_dst->sin6_addr);
+        prefixlen2 = _common_prefix_len(&a2_src->sin6_addr, &a2_dst->sin6_addr);
+        if (prefixlen1 != prefixlen2) {
+            return prefixlen2 - prefixlen1;
+        }
+    }
+
+    /*
+     * Rule 10: Leave the order unchanged.
+     * We need this since qsort() is not necessarily stable.
+     */
+    return a1->original_order - a2->original_order;
+}
+
+/*
+ * Find the source address that will be used if trying to connect to the given
+ * address. src_addr must be large enough to hold a struct sockaddr_in6.
+ *
+ * Returns 1 if a source address was found, 0 if the address is unreachable,
+ * and -1 if a fatal error occurred. If 0 or -1, the contents of src_addr are
+ * undefined.
+ */
+
+static int _find_src_addr(const struct sockaddr* addr, struct sockaddr* src_addr, unsigned mark,
+                          uid_t uid) {
+    int sock;
+    int ret;
+    socklen_t len;
+
+    switch (addr->sa_family) {
+        case AF_INET:
+            len = sizeof(struct sockaddr_in);
+            break;
+        case AF_INET6:
+            len = sizeof(struct sockaddr_in6);
+            break;
+        default:
+            /* No known usable source address for non-INET families. */
+            return 0;
+    }
+
+    sock = socket(addr->sa_family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+    if (sock == -1) {
+        if (errno == EAFNOSUPPORT) {
+            return 0;
+        } else {
+            return -1;
+        }
+    }
+    if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+        close(sock);
+        return 0;
+    }
+    if (uid > 0 && uid != NET_CONTEXT_INVALID_UID && fchown(sock, uid, (gid_t) -1) < 0) {
+        close(sock);
+        return 0;
+    }
+    do {
+        ret = connect(sock, addr, len);
+    } while (ret == -1 && errno == EINTR);
+
+    if (ret == -1) {
+        close(sock);
+        return 0;
+    }
+
+    if (src_addr && getsockname(sock, src_addr, &len) == -1) {
+        close(sock);
+        return -1;
+    }
+    close(sock);
+    return 1;
+}
+
+/*
+ * Sort the linked list starting at sentinel->ai_next in RFC6724 order.
+ * Will leave the list unchanged if an error occurs.
+ */
+
+static void _rfc6724_sort(struct addrinfo* list_sentinel, unsigned mark, uid_t uid) {
+    struct addrinfo* cur;
+    int nelem = 0, i;
+    struct addrinfo_sort_elem* elems;
+
+    cur = list_sentinel->ai_next;
+    while (cur) {
+        ++nelem;
+        cur = cur->ai_next;
+    }
+
+    elems = (struct addrinfo_sort_elem*) malloc(nelem * sizeof(struct addrinfo_sort_elem));
+    if (elems == NULL) {
+        goto error;
+    }
+
+    /*
+     * Convert the linked list to an array that also contains the candidate
+     * source address for each destination address.
+     */
+    for (i = 0, cur = list_sentinel->ai_next; i < nelem; ++i, cur = cur->ai_next) {
+        int has_src_addr;
+        assert(cur != NULL);
+        elems[i].ai = cur;
+        elems[i].original_order = i;
+
+        has_src_addr = _find_src_addr(cur->ai_addr, &elems[i].src_addr.sa, mark, uid);
+        if (has_src_addr == -1) {
+            goto error;
+        }
+        elems[i].has_src_addr = has_src_addr;
+    }
+
+    /* Sort the addresses, and rearrange the linked list so it matches the sorted order. */
+    qsort((void*) elems, nelem, sizeof(struct addrinfo_sort_elem), _rfc6724_compare);
+
+    list_sentinel->ai_next = elems[0].ai;
+    for (i = 0; i < nelem - 1; ++i) {
+        elems[i].ai->ai_next = elems[i + 1].ai;
+    }
+    elems[nelem - 1].ai->ai_next = NULL;
+
+error:
+    free(elems);
+}
+
+static int dns_getaddrinfo(const char* name, const addrinfo* pai,
+                           const android_net_context* netcontext, addrinfo** rv) {
+    res_target q = {};
+    res_target q2 = {};
+
+    auto buf = std::make_unique<querybuf>();
+    auto buf2 = std::make_unique<querybuf>();
+
+    switch (pai->ai_family) {
+        case AF_UNSPEC: {
+            /* prefer IPv6 */
+            q.name = name;
+            q.qclass = C_IN;
+            q.answer = buf->buf;
+            q.anslen = sizeof(buf->buf);
+            int query_ipv6 = 1, query_ipv4 = 1;
+            if (pai->ai_flags & AI_ADDRCONFIG) {
+                query_ipv6 = have_ipv6(netcontext->app_mark, netcontext->uid);
+                query_ipv4 = have_ipv4(netcontext->app_mark, netcontext->uid);
+            }
+            if (query_ipv6) {
+                q.qtype = T_AAAA;
+                if (query_ipv4) {
+                    q.next = &q2;
+                    q2.name = name;
+                    q2.qclass = C_IN;
+                    q2.qtype = T_A;
+                    q2.answer = buf2->buf;
+                    q2.anslen = sizeof(buf2->buf);
+                }
+            } else if (query_ipv4) {
+                q.qtype = T_A;
+            } else {
+                return EAI_NODATA;
+            }
+            break;
+        }
+        case AF_INET:
+            q.name = name;
+            q.qclass = C_IN;
+            q.qtype = T_A;
+            q.answer = buf->buf;
+            q.anslen = sizeof(buf->buf);
+            break;
+        case AF_INET6:
+            q.name = name;
+            q.qclass = C_IN;
+            q.qtype = T_AAAA;
+            q.answer = buf->buf;
+            q.anslen = sizeof(buf->buf);
+            break;
+        default:
+            return EAI_FAMILY;
+    }
+
+    res_state res = res_get_state();
+    if (!res) return EAI_MEMORY;
+
+    /* this just sets our netid val in the thread private data so we don't have to
+     * modify the api's all the way down to res_send.c's res_nsend.  We could
+     * fully populate the thread private data here, but if we get down there
+     * and have a cache hit that would be wasted, so we do the rest there on miss
+     */
+    res_setnetcontext(res, netcontext);
+
+    int he;
+    if (res_searchN(name, &q, res, &he) < 0) {
+        // Return h_errno (he) to catch more detailed errors rather than EAI_NODATA.
+        // Note that res_searchN() doesn't set the pair NETDB_INTERNAL and errno.
+        // See also herrnoToAiErrno().
+        return herrnoToAiErrno(he);
+    }
+
+    addrinfo sentinel = {};
+    addrinfo* cur = &sentinel;
+    addrinfo* ai = getanswer(buf.get(), q.n, q.name, q.qtype, pai, &he);
+    if (ai) {
+        cur->ai_next = ai;
+        while (cur && cur->ai_next) cur = cur->ai_next;
+    }
+    if (q.next) {
+        ai = getanswer(buf2.get(), q2.n, q2.name, q2.qtype, pai, &he);
+        if (ai) cur->ai_next = ai;
+    }
+    if (sentinel.ai_next == NULL) {
+        // Note that getanswer() doesn't set the pair NETDB_INTERNAL and errno.
+        // See also herrnoToAiErrno().
+        return herrnoToAiErrno(he);
+    }
+
+    _rfc6724_sort(&sentinel, netcontext->app_mark, netcontext->uid);
+
+    *rv = sentinel.ai_next;
+    return 0;
+}
+
+static void _sethtent(FILE** hostf) {
+    if (!*hostf)
+        *hostf = fopen(_PATH_HOSTS, "re");
+    else
+        rewind(*hostf);
+}
+
+static void _endhtent(FILE** hostf) {
+    if (*hostf) {
+        (void) fclose(*hostf);
+        *hostf = NULL;
+    }
+}
+
+static struct addrinfo* _gethtent(FILE** hostf, const char* name, const struct addrinfo* pai) {
+    char* p;
+    char *cp, *tname, *cname;
+    struct addrinfo *res0, *res;
+    int error;
+    const char* addr;
+    char hostbuf[8 * 1024];
+
+    assert(name != NULL);
+    assert(pai != NULL);
+
+    if (!*hostf && !(*hostf = fopen(_PATH_HOSTS, "re"))) return (NULL);
+again:
+    if (!(p = fgets(hostbuf, sizeof hostbuf, *hostf))) return (NULL);
+    if (*p == '#') goto again;
+    if (!(cp = strpbrk(p, "#\n"))) goto again;
+    *cp = '\0';
+    if (!(cp = strpbrk(p, " \t"))) goto again;
+    *cp++ = '\0';
+    addr = p;
+    /* if this is not something we're looking for, skip it. */
+    cname = NULL;
+    while (cp && *cp) {
+        if (*cp == ' ' || *cp == '\t') {
+            cp++;
+            continue;
+        }
+        if (!cname) cname = cp;
+        tname = cp;
+        if ((cp = strpbrk(cp, " \t")) != NULL) *cp++ = '\0';
+        if (strcasecmp(name, tname) == 0) goto found;
+    }
+    goto again;
+
+found:
+    error = getaddrinfo_numeric(addr, nullptr, *pai, &res0);
+    if (error) goto again;
+    for (res = res0; res; res = res->ai_next) {
+        /* cover it up */
+        res->ai_flags = pai->ai_flags;
+
+        if (pai->ai_flags & AI_CANONNAME) {
+            if (get_canonname(pai, res, cname) != 0) {
+                freeaddrinfo(res0);
+                goto again;
+            }
+        }
+    }
+    return res0;
+}
+
+static bool files_getaddrinfo(const char* name, const addrinfo* pai, addrinfo** res) {
+    struct addrinfo sentinel = {};
+    struct addrinfo *p, *cur;
+    FILE* hostf = NULL;
+
+    cur = &sentinel;
+
+    _sethtent(&hostf);
+    while ((p = _gethtent(&hostf, name, pai)) != NULL) {
+        cur->ai_next = p;
+        while (cur && cur->ai_next) cur = cur->ai_next;
+    }
+    _endhtent(&hostf);
+
+    *res = sentinel.ai_next;
+    return sentinel.ai_next != NULL;
+}
+
+/* resolver logic */
+
+/*
+ * Formulate a normal query, send, and await answer.
+ * Returned answer is placed in supplied buffer "answer".
+ * Perform preliminary check of answer, returning success only
+ * if no error is indicated and the answer count is nonzero.
+ * Return the size of the response on success, -1 on error.
+ * Error number is left in *herrno.
+ *
+ * Caller must parse answer and determine whether it answers the question.
+ */
+static int res_queryN(const char* name, res_target* target, res_state res, int* herrno) {
+    u_char buf[MAXPACKET];
+    HEADER* hp;
+    int n;
+    struct res_target* t;
+    int rcode;
+    int ancount;
+
+    assert(name != NULL);
+    /* XXX: target may be NULL??? */
+
+    rcode = NOERROR;
+    ancount = 0;
+
+    for (t = target; t; t = t->next) {
+        u_char* answer;
+        int anslen;
+
+        hp = (HEADER*) (void*) t->answer;
+        bool retried = false;
+    again:
+        hp->rcode = NOERROR; /* default */
+
+        /* make it easier... */
+        int cl = t->qclass;
+        int type = t->qtype;
+        answer = t->answer;
+        anslen = t->anslen;
+
+        LOG(DEBUG) << __func__ << ": (" << cl << ", " << type << ")";
+
+        n = res_nmkquery(res, QUERY, name, cl, type, NULL, 0, NULL, buf, sizeof(buf));
+        if (n > 0 && (res->options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) != 0 && !retried)
+            n = res_nopt(res, n, buf, sizeof(buf), anslen);
+        if (n <= 0) {
+            LOG(ERROR) << __func__ << ": res_nmkquery failed";
+            *herrno = NO_RECOVERY;
+            return n;
+        }
+
+        n = res_nsend(res, buf, n, answer, anslen, &rcode, 0);
+        if (n < 0 || hp->rcode != NOERROR || ntohs(hp->ancount) == 0) {
+            // Record rcode from DNS response header only if no timeout.
+            // Keep rcode timeout for reporting later if any.
+            if (rcode != RCODE_TIMEOUT) rcode = hp->rcode; /* record most recent error */
+            /* if the query choked with EDNS0, retry without EDNS0 */
+            if ((res->options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) != 0 &&
+                (res->_flags & RES_F_EDNS0ERR) && !retried) {
+                LOG(DEBUG) << __func__ << ": retry without EDNS0";
+                retried = true;
+                goto again;
+            }
+            LOG(DEBUG) << __func__ << ": rcode=" << hp->rcode << ", ancount=" << ntohs(hp->ancount);
+            continue;
+        }
+
+        ancount += ntohs(hp->ancount);
+
+        t->n = n;
+    }
+
+    if (ancount == 0) {
+        switch (rcode) {
+            // Not defined in RFC.
+            case RCODE_TIMEOUT:
+                // DNS metrics monitors DNS query timeout.
+                *herrno = NETD_RESOLV_H_ERRNO_EXT_TIMEOUT;  // extended h_errno.
+                break;
+            // Defined in RFC 1035 section 4.1.1.
+            case NXDOMAIN:
+                *herrno = HOST_NOT_FOUND;
+                break;
+            case SERVFAIL:
+                *herrno = TRY_AGAIN;
+                break;
+            case NOERROR:
+                *herrno = NO_DATA;
+                break;
+            case FORMERR:
+            case NOTIMP:
+            case REFUSED:
+            default:
+                *herrno = NO_RECOVERY;
+                break;
+        }
+        return -1;
+    }
+    return ancount;
+}
+
+/*
+ * Formulate a normal query, send, and retrieve answer in supplied buffer.
+ * Return the size of the response on success, -1 on error.
+ * If enabled, implement search rules until answer or unrecoverable failure
+ * is detected.  Error code, if any, is left in *herrno.
+ */
+static int res_searchN(const char* name, res_target* target, res_state res, int* herrno) {
+    const char *cp, *const *domain;
+    HEADER* hp;
+    u_int dots;
+    int trailing_dot, ret, saved_herrno;
+    int got_nodata = 0, got_servfail = 0, tried_as_is = 0;
+
+    assert(name != NULL);
+    assert(target != NULL);
+
+    hp = (HEADER*) (void*) target->answer; /*XXX*/
+
+    errno = 0;
+    *herrno = HOST_NOT_FOUND; /* default, if we never query */
+    dots = 0;
+    for (cp = name; *cp; cp++) dots += (*cp == '.');
+    trailing_dot = 0;
+    if (cp > name && *--cp == '.') trailing_dot++;
+
+    /*
+     * If there are dots in the name already, let's just give it a try
+     * 'as is'.  The threshold can be set with the "ndots" option.
+     */
+    saved_herrno = -1;
+    if (dots >= res->ndots) {
+        ret = res_querydomainN(name, NULL, target, res, herrno);
+        if (ret > 0) return (ret);
+        saved_herrno = *herrno;
+        tried_as_is++;
+    }
+
+    /*
+     * We do at least one level of search if
+     *	- there is no dot and RES_DEFNAME is set, or
+     *	- there is at least one dot, there is no trailing dot,
+     *	  and RES_DNSRCH is set.
+     */
+    if ((!dots && (res->options & RES_DEFNAMES)) ||
+        (dots && !trailing_dot && (res->options & RES_DNSRCH))) {
+        int done = 0;
+
+        /* Unfortunately we need to set stuff up before
+         * the domain stuff is tried.  Will have a better
+         * fix after thread pools are used.
+         */
+        _resolv_populate_res_for_net(res);
+
+        for (domain = (const char* const*) res->dnsrch; *domain && !done; domain++) {
+            ret = res_querydomainN(name, *domain, target, res, herrno);
+            if (ret > 0) return ret;
+
+            /*
+             * If no server present, give up.
+             * If name isn't found in this domain,
+             * keep trying higher domains in the search list
+             * (if that's enabled).
+             * On a NO_DATA error, keep trying, otherwise
+             * a wildcard entry of another type could keep us
+             * from finding this entry higher in the domain.
+             * If we get some other error (negative answer or
+             * server failure), then stop searching up,
+             * but try the input name below in case it's
+             * fully-qualified.
+             */
+            if (errno == ECONNREFUSED) {
+                *herrno = TRY_AGAIN;
+                return -1;
+            }
+
+            switch (*herrno) {
+                case NO_DATA:
+                    got_nodata++;
+                    [[fallthrough]];
+                case HOST_NOT_FOUND:
+                    /* keep trying */
+                    break;
+                case TRY_AGAIN:
+                    if (hp->rcode == SERVFAIL) {
+                        /* try next search element, if any */
+                        got_servfail++;
+                        break;
+                    }
+                    [[fallthrough]];
+                default:
+                    /* anything else implies that we're done */
+                    done++;
+            }
+            /*
+             * if we got here for some reason other than DNSRCH,
+             * we only wanted one iteration of the loop, so stop.
+             */
+            if (!(res->options & RES_DNSRCH)) done++;
+        }
+    }
+
+    /*
+     * if we have not already tried the name "as is", do that now.
+     * note that we do this regardless of how many dots were in the
+     * name or whether it ends with a dot.
+     */
+    if (!tried_as_is) {
+        ret = res_querydomainN(name, NULL, target, res, herrno);
+        if (ret > 0) return ret;
+    }
+
+    /*
+     * if we got here, we didn't satisfy the search.
+     * if we did an initial full query, return that query's h_errno
+     * (note that we wouldn't be here if that query had succeeded).
+     * else if we ever got a nodata, send that back as the reason.
+     * else send back meaningless h_errno, that being the one from
+     * the last DNSRCH we did.
+     */
+    if (saved_herrno != -1)
+        *herrno = saved_herrno;
+    else if (got_nodata)
+        *herrno = NO_DATA;
+    else if (got_servfail)
+        *herrno = TRY_AGAIN;
+    return -1;
+}
+
+/*
+ * Perform a call on res_query on the concatenation of name and domain,
+ * removing a trailing dot from name if domain is NULL.
+ */
+static int res_querydomainN(const char* name, const char* domain, res_target* target, res_state res,
+                            int* herrno) {
+    char nbuf[MAXDNAME];
+    const char* longname = nbuf;
+    size_t n, d;
+
+    assert(name != NULL);
+
+    if (domain == NULL) {
+        // Check for trailing '.'; copy without '.' if present.
+        n = strlen(name);
+        if (n + 1 > sizeof(nbuf)) {
+            *herrno = NO_RECOVERY;
+            return -1;
+        }
+        if (n > 0 && name[--n] == '.') {
+            strncpy(nbuf, name, n);
+            nbuf[n] = '\0';
+        } else
+            longname = name;
+    } else {
+        n = strlen(name);
+        d = strlen(domain);
+        if (n + 1 + d + 1 > sizeof(nbuf)) {
+            *herrno = NO_RECOVERY;
+            return -1;
+        }
+        snprintf(nbuf, sizeof(nbuf), "%s.%s", name, domain);
+    }
+    return res_queryN(longname, target, res, herrno);
+}
diff --git a/server/binder/android/net/UidRange.aidl b/resolv/getaddrinfo.h
similarity index 60%
copy from server/binder/android/net/UidRange.aidl
copy to resolv/getaddrinfo.h
index 55747d0..cf54cab 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/resolv/getaddrinfo.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package android.net;
+#pragma once
 
-/**
- * An inclusive range of UIDs.
- *
- * {@hide}
- */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+#include "netd_resolv/resolv.h"  // struct android_net_context
+
+struct addrinfo;
+
+// This is the DNS proxy entry point for getaddrinfo().
+int android_getaddrinfofornetcontext(const char*, const char*, const addrinfo*,
+                                     const android_net_context*, addrinfo**);
diff --git a/resolv/gethnamaddr.cpp b/resolv/gethnamaddr.cpp
new file mode 100644
index 0000000..1cf0694
--- /dev/null
+++ b/resolv/gethnamaddr.cpp
@@ -0,0 +1,944 @@
+/*	$NetBSD: gethnamaddr.c,v 1.91 2014/06/19 15:08:18 christos Exp $	*/
+
+/*
+ * ++Copyright++ 1985, 1988, 1993
+ * -
+ * Copyright (c) 1985, 1988, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * -
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ * -
+ * --Copyright--
+ */
+
+#include "gethnamaddr.h"
+
+#include <android-base/logging.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <functional>
+#include <vector>
+
+#include "hostent.h"
+#include "netd_resolv/resolv.h"
+#include "resolv_cache.h"
+#include "resolv_private.h"
+
+// NetBSD uses _DIAGASSERT to null-check arguments and the like,
+// but it's clear from the number of mistakes in their assertions
+// that they don't actually test or ship with this.
+#define _DIAGASSERT(e) /* nothing */
+
+// TODO: unify macro ALIGNBYTES and ALIGN for all possible data type alignment of hostent
+// buffer.
+#define ALIGNBYTES (sizeof(uintptr_t) - 1)
+#define ALIGN(p) (((uintptr_t)(p) + ALIGNBYTES) & ~ALIGNBYTES)
+
+#define maybe_ok(res, nm, ok) (((res)->options & RES_NOCHECKNAME) != 0U || (ok)(nm) != 0)
+#define maybe_hnok(res, hn) maybe_ok((res), (hn), res_hnok)
+#define maybe_dnok(res, dn) maybe_ok((res), (dn), res_dnok)
+
+#define MAXPACKET (8 * 1024)
+
+typedef union {
+    HEADER hdr;
+    u_char buf[MAXPACKET];
+} querybuf;
+
+typedef union {
+    int32_t al;
+    char ac;
+} align;
+
+static struct hostent* getanswer(const querybuf*, int, const char*, int, res_state, struct hostent*,
+                                 char*, size_t, int*);
+static void convert_v4v6_hostent(struct hostent* hp, char** bpp, char* ep,
+                                 std::function<void(struct hostent* hp)> mapping_param,
+                                 std::function<void(char* src, char* dst)> mapping_addr);
+static void map_v4v6_address(const char*, char*);
+static void map_v4v6_hostent(struct hostent*, char**, char*);
+static void pad_v4v6_hostent(struct hostent* hp, char** bpp, char* ep);
+static void addrsort(char**, int, res_state);
+
+static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
+                           const android_net_context* netcontext, getnamaddr* info);
+static int dns_gethtbyname(const char* name, int af, getnamaddr* info);
+
+static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
+                                  size_t hbuflen, const android_net_context* netcontext);
+static int gethostbyname_internal_real(const char* name, int af, res_state res, hostent* hp,
+                                       char* buf, size_t buflen);
+static int android_gethostbyaddrfornetcontext_proxy_internal(const void*, socklen_t, int,
+                                                             struct hostent*, char*, size_t,
+                                                             const struct android_net_context*);
+static int android_gethostbyaddrfornetcontext_proxy(const void* addr, socklen_t len, int af,
+                                                    const struct android_net_context* netcontext,
+                                                    hostent** hp);
+
+#define BOUNDED_INCR(x)      \
+    do {                     \
+        BOUNDS_CHECK(cp, x); \
+        cp += (x);           \
+    } while (0)
+
+#define BOUNDS_CHECK(ptr, count)                     \
+    do {                                             \
+        if (eom - (ptr) < (count)) goto no_recovery; \
+    } while (0)
+
+static struct hostent* getanswer(const querybuf* answer, int anslen, const char* qname, int qtype,
+                                 res_state res, struct hostent* hent, char* buf, size_t buflen,
+                                 int* he) {
+    const HEADER* hp;
+    const u_char* cp;
+    int n;
+    size_t qlen;
+    const u_char *eom, *erdata;
+    char *bp, **hap, *ep;
+    int ancount, qdcount;
+    int haveanswer, had_error;
+    int toobig = 0;
+    char tbuf[MAXDNAME];
+    char* addr_ptrs[MAXADDRS];
+    const char* tname;
+    int (*name_ok)(const char*);
+    std::vector<char*> aliases;
+
+    _DIAGASSERT(answer != NULL);
+    _DIAGASSERT(qname != NULL);
+
+    tname = qname;
+    hent->h_name = NULL;
+    eom = answer->buf + anslen;
+    switch (qtype) {
+        case T_A:
+        case T_AAAA:
+            name_ok = res_hnok;
+            break;
+        case T_PTR:
+            name_ok = res_dnok;
+            break;
+        default:
+            *he = NO_RECOVERY;
+            return NULL; /* XXX should be abort(); */
+    }
+
+    /*
+     * find first satisfactory answer
+     */
+    hp = &answer->hdr;
+    ancount = ntohs(hp->ancount);
+    qdcount = ntohs(hp->qdcount);
+    bp = buf;
+    ep = buf + buflen;
+    cp = answer->buf;
+    BOUNDED_INCR(HFIXEDSZ);
+    if (qdcount != 1) goto no_recovery;
+
+    n = dn_expand(answer->buf, eom, cp, bp, (int) (ep - bp));
+    if ((n < 0) || !maybe_ok(res, bp, name_ok)) goto no_recovery;
+
+    BOUNDED_INCR(n + QFIXEDSZ);
+    if (qtype == T_A || qtype == T_AAAA) {
+        /* res_send() has already verified that the query name is the
+         * same as the one we sent; this just gets the expanded name
+         * (i.e., with the succeeding search-domain tacked on).
+         */
+        n = (int) strlen(bp) + 1; /* for the \0 */
+        if (n >= MAXHOSTNAMELEN) goto no_recovery;
+        hent->h_name = bp;
+        bp += n;
+        /* The qname can be abbreviated, but h_name is now absolute. */
+        qname = hent->h_name;
+    }
+    hent->h_addr_list = hap = addr_ptrs;
+    *hap = NULL;
+    haveanswer = 0;
+    had_error = 0;
+    while (ancount-- > 0 && cp < eom && !had_error) {
+        n = dn_expand(answer->buf, eom, cp, bp, (int) (ep - bp));
+        if ((n < 0) || !maybe_ok(res, bp, name_ok)) {
+            had_error++;
+            continue;
+        }
+        cp += n; /* name */
+        BOUNDS_CHECK(cp, 3 * INT16SZ + INT32SZ);
+        int type = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ; /* type */
+        int cl = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ + INT32SZ; /* class, TTL */
+        n = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ; /* len */
+        BOUNDS_CHECK(cp, n);
+        erdata = cp + n;
+        if (cl != C_IN) {
+            /* XXX - debug? syslog? */
+            cp += n;
+            continue; /* XXX - had_error++ ? */
+        }
+        if ((qtype == T_A || qtype == T_AAAA) && type == T_CNAME) {
+            n = dn_expand(answer->buf, eom, cp, tbuf, (int) sizeof tbuf);
+            if ((n < 0) || !maybe_ok(res, tbuf, name_ok)) {
+                had_error++;
+                continue;
+            }
+            cp += n;
+            if (cp != erdata) goto no_recovery;
+            /* Store alias. */
+            aliases.push_back(bp);
+            n = (int) strlen(bp) + 1; /* for the \0 */
+            if (n >= MAXHOSTNAMELEN) {
+                had_error++;
+                continue;
+            }
+            bp += n;
+            /* Get canonical name. */
+            n = (int) strlen(tbuf) + 1; /* for the \0 */
+            if (n > ep - bp || n >= MAXHOSTNAMELEN) {
+                had_error++;
+                continue;
+            }
+            strlcpy(bp, tbuf, (size_t)(ep - bp));
+            hent->h_name = bp;
+            bp += n;
+            continue;
+        }
+        if (qtype == T_PTR && type == T_CNAME) {
+            n = dn_expand(answer->buf, eom, cp, tbuf, (int) sizeof tbuf);
+            if (n < 0 || !maybe_dnok(res, tbuf)) {
+                had_error++;
+                continue;
+            }
+            cp += n;
+            if (cp != erdata) goto no_recovery;
+            /* Get canonical name. */
+            n = (int) strlen(tbuf) + 1; /* for the \0 */
+            if (n > ep - bp || n >= MAXHOSTNAMELEN) {
+                had_error++;
+                continue;
+            }
+            strlcpy(bp, tbuf, (size_t)(ep - bp));
+            tname = bp;
+            bp += n;
+            continue;
+        }
+        if (type != qtype) {
+            if (type != T_KEY && type != T_SIG)
+                LOG(DEBUG) << __func__ << ": asked for \"" << qname << " " << p_class(C_IN) << " "
+                           << p_type(qtype) << "\", got type \"" << p_type(type) << "\"";
+            cp += n;
+            continue; /* XXX - had_error++ ? */
+        }
+        switch (type) {
+            case T_PTR:
+                if (strcasecmp(tname, bp) != 0) {
+                    LOG(DEBUG) << __func__ << ": asked for \"" << qname << "\", got \"" << bp
+                               << "\"";
+                    cp += n;
+                    continue; /* XXX - had_error++ ? */
+                }
+                n = dn_expand(answer->buf, eom, cp, bp, (int) (ep - bp));
+                if ((n < 0) || !maybe_hnok(res, bp)) {
+                    had_error++;
+                    break;
+                }
+                cp += n;
+                if (cp != erdata) goto no_recovery;
+                if (!haveanswer)
+                    hent->h_name = bp;
+                else
+                    aliases.push_back(bp);
+                if (n != -1) {
+                    n = (int) strlen(bp) + 1; /* for the \0 */
+                    if (n >= MAXHOSTNAMELEN) {
+                        had_error++;
+                        break;
+                    }
+                    bp += n;
+                }
+                break;
+            case T_A:
+            case T_AAAA:
+                if (strcasecmp(hent->h_name, bp) != 0) {
+                    LOG(DEBUG) << __func__ << ": asked for \"" << hent->h_name << "\", got \"" << bp
+                               << "\"";
+                    cp += n;
+                    continue; /* XXX - had_error++ ? */
+                }
+                if (n != hent->h_length) {
+                    cp += n;
+                    continue;
+                }
+                if (type == T_AAAA) {
+                    struct in6_addr in6;
+                    memcpy(&in6, cp, NS_IN6ADDRSZ);
+                    if (IN6_IS_ADDR_V4MAPPED(&in6)) {
+                        cp += n;
+                        continue;
+                    }
+                }
+                if (!haveanswer) {
+                    int nn;
+
+                    hent->h_name = bp;
+                    nn = (int) strlen(bp) + 1; /* for the \0 */
+                    bp += nn;
+                }
+
+                bp += sizeof(align) - (size_t)((u_long) bp % sizeof(align));
+
+                if (bp + n >= ep) {
+                    LOG(DEBUG) << __func__ << ": size (" << n << ") too big";
+                    had_error++;
+                    continue;
+                }
+                if (hap >= &addr_ptrs[MAXADDRS - 1]) {
+                    if (!toobig++) {
+                        LOG(DEBUG) << __func__ << ": Too many addresses (" << MAXADDRS << ")";
+                    }
+                    cp += n;
+                    continue;
+                }
+                (void) memcpy(*hap++ = bp, cp, (size_t) n);
+                bp += n;
+                cp += n;
+                if (cp != erdata) goto no_recovery;
+                break;
+            default:
+                abort();
+        }
+        if (!had_error) haveanswer++;
+    }
+    if (haveanswer) {
+        *hap = NULL;
+        /*
+         * Note: we sort even if host can take only one address
+         * in its return structures - should give it the "best"
+         * address in that case, not some random one
+         */
+        if (res->nsort && haveanswer > 1 && qtype == T_A) addrsort(addr_ptrs, haveanswer, res);
+        if (!hent->h_name) {
+            n = (int) strlen(qname) + 1; /* for the \0 */
+            if (n > ep - bp || n >= MAXHOSTNAMELEN) goto no_recovery;
+            strlcpy(bp, qname, (size_t)(ep - bp));
+            hent->h_name = bp;
+            bp += n;
+        }
+        if (res->options & RES_USE_INET6) map_v4v6_hostent(hent, &bp, ep);
+        if (hent->h_addrtype == AF_INET) pad_v4v6_hostent(hent, &bp, ep);
+        goto success;
+    }
+no_recovery:
+    *he = NO_RECOVERY;
+    return NULL;
+success:
+    bp = (char*) ALIGN(bp);
+    aliases.push_back(nullptr);
+    qlen = aliases.size() * sizeof(*hent->h_aliases);
+    if ((size_t)(ep - bp) < qlen) goto nospc;
+    hent->h_aliases = (char**) bp;
+    memcpy(bp, aliases.data(), qlen);
+
+    bp += qlen;
+    n = (int) (hap - addr_ptrs);
+    qlen = (n + 1) * sizeof(*hent->h_addr_list);
+    if ((size_t)(ep - bp) < qlen) goto nospc;
+    hent->h_addr_list = (char**) bp;
+    memcpy(bp, addr_ptrs, qlen);
+    *he = NETDB_SUCCESS;
+    return hent;
+nospc:
+    errno = ENOSPC;
+    *he = NETDB_INTERNAL;
+    return NULL;
+}
+
+static int gethostbyname_internal_real(const char* name, int af, res_state res, hostent* hp,
+                                       char* buf, size_t buflen) {
+    getnamaddr info;
+    size_t size;
+
+    _DIAGASSERT(name != NULL);
+
+    switch (af) {
+        case AF_INET:
+            size = NS_INADDRSZ;
+            break;
+        case AF_INET6:
+            size = NS_IN6ADDRSZ;
+            break;
+        default:
+            return EAI_FAMILY;
+    }
+    if (buflen < size) goto nospc;
+
+    hp->h_addrtype = af;
+    hp->h_length = (int) size;
+
+    /*
+     * disallow names consisting only of digits/dots, unless
+     * they end in a dot.
+     */
+    if (isdigit((u_char) name[0])) {
+        for (const char* cp = name;; ++cp) {
+            if (!*cp) {
+                if (*--cp == '.') break;
+                /*
+                 * All-numeric, no dot at the end.
+                 * Fake up a hostent as if we'd actually
+                 * done a lookup.
+                 */
+                goto fake;
+            }
+            if (!isdigit((u_char) *cp) && *cp != '.') break;
+        }
+    }
+    if ((isxdigit((u_char) name[0]) && strchr(name, ':') != NULL) || name[0] == ':') {
+        for (const char* cp = name;; ++cp) {
+            if (!*cp) {
+                if (*--cp == '.') break;
+                /*
+                 * All-IPv6-legal, no dot at the end.
+                 * Fake up a hostent as if we'd actually
+                 * done a lookup.
+                 */
+                goto fake;
+            }
+            if (!isxdigit((u_char) *cp) && *cp != ':' && *cp != '.') break;
+        }
+    }
+
+    info.hp = hp;
+    info.buf = buf;
+    info.buflen = buflen;
+    if (_hf_gethtbyname2(name, af, &info)) {
+        int error = dns_gethtbyname(name, af, &info);
+        if (error != 0) return error;
+    }
+    return 0;
+nospc:
+    return EAI_MEMORY;
+fake:
+    HENT_ARRAY(hp->h_addr_list, 1, buf, buflen);
+    HENT_ARRAY(hp->h_aliases, 0, buf, buflen);
+
+    hp->h_aliases[0] = NULL;
+    if (size > buflen) goto nospc;
+
+    if (inet_pton(af, name, buf) <= 0) {
+        return EAI_NODATA;
+    }
+    hp->h_addr_list[0] = buf;
+    hp->h_addr_list[1] = NULL;
+    buf += size;
+    buflen -= size;
+    HENT_SCOPY(hp->h_name, name, buf, buflen);
+    if (res->options & RES_USE_INET6) map_v4v6_hostent(hp, &buf, buf + buflen);
+    return 0;
+}
+
+// very similar in proxy-ness to android_getaddrinfo_proxy
+static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
+                                  size_t hbuflen, const android_net_context* netcontext) {
+    res_setnetcontext(res, netcontext);
+    return gethostbyname_internal_real(name, af, res, hp, hbuf, hbuflen);
+}
+
+static int android_gethostbyaddrfornetcontext_real(const void* addr, socklen_t len, int af,
+                                                   struct hostent* hp, char* buf, size_t buflen,
+                                                   const struct android_net_context* netcontext) {
+    const u_char* uaddr = (const u_char*) addr;
+    socklen_t size;
+    struct getnamaddr info;
+
+    _DIAGASSERT(addr != NULL);
+
+    if (af == AF_INET6 && len == NS_IN6ADDRSZ &&
+        (IN6_IS_ADDR_LINKLOCAL((const struct in6_addr*) addr) ||
+         IN6_IS_ADDR_SITELOCAL((const struct in6_addr*) addr))) {
+        return EAI_NODATA;
+    }
+    if (af == AF_INET6 && len == NS_IN6ADDRSZ &&
+        (IN6_IS_ADDR_V4MAPPED((const struct in6_addr*) addr) ||
+         IN6_IS_ADDR_V4COMPAT((const struct in6_addr*) addr))) {
+        /* Unmap. */
+        uaddr += NS_IN6ADDRSZ - NS_INADDRSZ;
+        addr = uaddr;
+        af = AF_INET;
+        len = NS_INADDRSZ;
+    }
+    switch (af) {
+        case AF_INET:
+            size = NS_INADDRSZ;
+            break;
+        case AF_INET6:
+            size = NS_IN6ADDRSZ;
+            break;
+        default:
+            return EAI_FAMILY;
+    }
+    if (size != len) {
+        // TODO: Consider converting to a private extended EAI_* error code.
+        // Currently, the EAI_* value has no corresponding error code for invalid argument socket
+        // length. In order to not rely on errno, convert the original error code pair, EAI_SYSTEM
+        // and EINVAL, to EAI_FAIL.
+        return EAI_FAIL;
+    }
+    info.hp = hp;
+    info.buf = buf;
+    info.buflen = buflen;
+    if (_hf_gethtbyaddr(uaddr, len, af, &info)) {
+        int error = dns_gethtbyaddr(uaddr, len, af, netcontext, &info);
+        if (error != 0) return error;
+    }
+    return 0;
+}
+
+static int android_gethostbyaddrfornetcontext_proxy_internal(
+        const void* addr, socklen_t len, int af, struct hostent* hp, char* hbuf, size_t hbuflen,
+        const struct android_net_context* netcontext) {
+    return android_gethostbyaddrfornetcontext_real(addr, len, af, hp, hbuf, hbuflen, netcontext);
+}
+
+// TODO: Consider leaving function without returning error code as _gethtent() does because
+// the error code of the caller does not currently return to netd.
+struct hostent* netbsd_gethostent_r(FILE* hf, struct hostent* hent, char* buf, size_t buflen,
+                                    int* he) {
+    const size_t line_buf_size = sizeof(res_get_static()->hostbuf);
+    char *name;
+    char* cp;
+    int af, len;
+    size_t anum;
+    struct in6_addr host_addr;
+    std::vector<char*> aliases;
+
+    if (hf == NULL) {
+        *he = NETDB_INTERNAL;
+        errno = EINVAL;
+        return NULL;
+    }
+    char* p = NULL;
+
+    /* Allocate a new space to read file lines like upstream does.
+     * To keep reentrancy we cannot use res_get_static()->hostbuf here,
+     * as the buffer may be used to store content for a previous hostent
+     * returned by non-reentrant functions like gethostbyname().
+     */
+    if ((p = (char*) malloc(line_buf_size)) == NULL) {
+        goto nospc;
+    }
+    for (;;) {
+        if (!fgets(p, line_buf_size, hf)) {
+            free(p);
+            *he = HOST_NOT_FOUND;
+            return NULL;
+        }
+        if (*p == '#') {
+            continue;
+        }
+        if (!(cp = strpbrk(p, "#\n"))) {
+            continue;
+        }
+        *cp = '\0';
+        if (!(cp = strpbrk(p, " \t"))) continue;
+        *cp++ = '\0';
+        if (inet_pton(AF_INET6, p, &host_addr) > 0) {
+            af = AF_INET6;
+            len = NS_IN6ADDRSZ;
+        } else {
+            if (inet_pton(AF_INET, p, &host_addr) <= 0) continue;
+
+            res_state res = res_get_state();
+            if (res == NULL) goto nospc;
+            if (res->options & RES_USE_INET6) {
+                map_v4v6_address(buf, buf);
+                af = AF_INET6;
+                len = NS_IN6ADDRSZ;
+            } else {
+                af = AF_INET;
+                len = NS_INADDRSZ;
+            }
+        }
+
+        /* if this is not something we're looking for, skip it. */
+        if (hent->h_addrtype != 0 && hent->h_addrtype != af) continue;
+        if (hent->h_length != 0 && hent->h_length != len) continue;
+
+        while (*cp == ' ' || *cp == '\t') cp++;
+        if ((cp = strpbrk(name = cp, " \t")) != NULL) *cp++ = '\0';
+        while (cp && *cp) {
+            if (*cp == ' ' || *cp == '\t') {
+                cp++;
+                continue;
+            }
+            aliases.push_back(cp);
+            if ((cp = strpbrk(cp, " \t")) != NULL) *cp++ = '\0';
+        }
+        break;
+    }
+    hent->h_length = len;
+    hent->h_addrtype = af;
+    HENT_ARRAY(hent->h_addr_list, 1, buf, buflen);
+    anum = aliases.size();
+    HENT_ARRAY(hent->h_aliases, anum, buf, buflen);
+    HENT_COPY(hent->h_addr_list[0], &host_addr, hent->h_length, buf, buflen);
+    hent->h_addr_list[1] = NULL;
+
+    /* Reserve space for mapping IPv4 address to IPv6 address in place */
+    if (hent->h_addrtype == AF_INET) {
+        HENT_COPY(buf, NAT64_PAD, sizeof(NAT64_PAD), buf, buflen);
+    }
+
+    HENT_SCOPY(hent->h_name, name, buf, buflen);
+    for (size_t i = 0; i < anum; i++) HENT_SCOPY(hent->h_aliases[i], aliases[i], buf, buflen);
+    hent->h_aliases[anum] = NULL;
+    *he = NETDB_SUCCESS;
+    free(p);
+    return hent;
+nospc:
+    free(p);
+    errno = ENOSPC;
+    *he = NETDB_INTERNAL;
+    return NULL;
+}
+
+static void map_v4v6_address(const char* src, char* dst) {
+    u_char* p = (u_char*) dst;
+    char tmp[NS_INADDRSZ];
+    int i;
+
+    _DIAGASSERT(src != NULL);
+    _DIAGASSERT(dst != NULL);
+
+    /* Stash a temporary copy so our caller can update in place. */
+    memcpy(tmp, src, NS_INADDRSZ);
+    /* Mark this ipv6 addr as a mapped ipv4. */
+    for (i = 0; i < 10; i++) *p++ = 0x00;
+    *p++ = 0xff;
+    *p++ = 0xff;
+    /* Retrieve the saved copy and we're done. */
+    memcpy(p, tmp, NS_INADDRSZ);
+}
+
+static void convert_v4v6_hostent(struct hostent* hp, char** bpp, char* ep,
+                                 std::function<void(struct hostent* hp)> map_param,
+                                 std::function<void(char* src, char* dst)> map_addr) {
+    _DIAGASSERT(hp != NULL);
+    _DIAGASSERT(bpp != NULL);
+    _DIAGASSERT(ep != NULL);
+
+    if (hp->h_addrtype != AF_INET || hp->h_length != NS_INADDRSZ) return;
+    map_param(hp);
+    for (char** ap = hp->h_addr_list; *ap; ap++) {
+        int i = (int) (sizeof(align) - (size_t)((u_long) *bpp % sizeof(align)));
+
+        if (ep - *bpp < (i + NS_IN6ADDRSZ)) {
+            /* Out of memory.  Truncate address list here.  XXX */
+            *ap = NULL;
+            return;
+        }
+        *bpp += i;
+        map_addr(*ap, *bpp);
+        *ap = *bpp;
+        *bpp += NS_IN6ADDRSZ;
+    }
+}
+
+static void map_v4v6_hostent(struct hostent* hp, char** bpp, char* ep) {
+    convert_v4v6_hostent(hp, bpp, ep,
+                         [](struct hostent* hp) {
+                             hp->h_addrtype = AF_INET6;
+                             hp->h_length = NS_IN6ADDRSZ;
+                         },
+                         [](char* src, char* dst) { map_v4v6_address(src, dst); });
+}
+
+/* Reserve space for mapping IPv4 address to IPv6 address in place */
+static void pad_v4v6_hostent(struct hostent* hp, char** bpp, char* ep) {
+    convert_v4v6_hostent(hp, bpp, ep,
+                         [](struct hostent* hp) {
+                             (void) hp; /* unused */
+                         },
+                         [](char* src, char* dst) {
+                             memcpy(dst, src, NS_INADDRSZ);
+                             memcpy(dst + NS_INADDRSZ, NAT64_PAD, sizeof(NAT64_PAD));
+                         });
+}
+
+static void addrsort(char** ap, int num, res_state res) {
+    int i, j;
+    char** p;
+    short aval[MAXADDRS];
+    int needsort = 0;
+
+    _DIAGASSERT(ap != NULL);
+
+    p = ap;
+    for (i = 0; i < num; i++, p++) {
+        for (j = 0; (unsigned) j < res->nsort; j++)
+            if (res->sort_list[j].addr.s_addr ==
+                (((struct in_addr*) (void*) (*p))->s_addr & res->sort_list[j].mask))
+                break;
+        aval[i] = j;
+        if (needsort == 0 && i > 0 && j < aval[i - 1]) needsort = i;
+    }
+    if (!needsort) return;
+
+    while (needsort < num) {
+        for (j = needsort - 1; j >= 0; j--) {
+            if (aval[j] > aval[j + 1]) {
+                char* hp;
+
+                i = aval[j];
+                aval[j] = aval[j + 1];
+                aval[j + 1] = i;
+
+                hp = ap[j];
+                ap[j] = ap[j + 1];
+                ap[j + 1] = hp;
+            } else
+                break;
+        }
+        needsort++;
+    }
+}
+
+static int dns_gethtbyname(const char* name, int addr_type, getnamaddr* info) {
+    int n, type;
+    info->hp->h_addrtype = addr_type;
+
+    switch (info->hp->h_addrtype) {
+        case AF_INET:
+            info->hp->h_length = NS_INADDRSZ;
+            type = T_A;
+            break;
+        case AF_INET6:
+            info->hp->h_length = NS_IN6ADDRSZ;
+            type = T_AAAA;
+            break;
+        default:
+            return EAI_FAMILY;
+    }
+    auto buf = std::make_unique<querybuf>();
+
+    res_state res = res_get_state();
+    if (!res) return EAI_MEMORY;
+
+    int he;
+    n = res_nsearch(res, name, C_IN, type, buf->buf, (int)sizeof(buf->buf), &he);
+    if (n < 0) {
+        LOG(DEBUG) << __func__ << ": res_nsearch failed (" << n << ")";
+        // Return h_errno (he) to catch more detailed errors rather than EAI_NODATA.
+        // Note that res_nsearch() doesn't set the pair NETDB_INTERNAL and errno.
+        // See also herrnoToAiErrno().
+        return herrnoToAiErrno(he);
+    }
+    hostent* hp = getanswer(buf.get(), n, name, type, res, info->hp, info->buf, info->buflen, &he);
+    if (hp == NULL) return herrnoToAiErrno(he);
+
+    return 0;
+}
+
+static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
+                           const android_net_context* netcontext, getnamaddr* info) {
+    char qbuf[MAXDNAME + 1], *qp, *ep;
+    int n;
+    int advance;
+
+    info->hp->h_length = len;
+    info->hp->h_addrtype = af;
+
+    switch (info->hp->h_addrtype) {
+        case AF_INET:
+            (void) snprintf(qbuf, sizeof(qbuf), "%u.%u.%u.%u.in-addr.arpa", (uaddr[3] & 0xff),
+                            (uaddr[2] & 0xff), (uaddr[1] & 0xff), (uaddr[0] & 0xff));
+            break;
+
+        case AF_INET6:
+            qp = qbuf;
+            ep = qbuf + sizeof(qbuf) - 1;
+            for (n = NS_IN6ADDRSZ - 1; n >= 0; n--) {
+                advance = snprintf(qp, (size_t)(ep - qp), "%x.%x.", uaddr[n] & 0xf,
+                                   ((unsigned int) uaddr[n] >> 4) & 0xf);
+                if (advance > 0 && qp + advance < ep)
+                    qp += advance;
+                else {
+                    // TODO: Consider converting to a private extended EAI_* error code.
+                    // Currently, the EAI_* value has no corresponding error code for an internal
+                    // out of buffer space. In order to not rely on errno, convert the original
+                    // error code EAI_SYSTEM to EAI_MEMORY.
+                    return EAI_MEMORY;
+                }
+            }
+            if (strlcat(qbuf, "ip6.arpa", sizeof(qbuf)) >= sizeof(qbuf)) {
+                // TODO: Consider converting to a private extended EAI_* error code.
+                // Currently, the EAI_* value has no corresponding error code for an internal
+                // out of buffer space. In order to not rely on errno, convert the original
+                // error code EAI_SYSTEM to EAI_MEMORY.
+                return EAI_MEMORY;
+            }
+            break;
+        default:
+            return EAI_FAMILY;
+    }
+
+    auto buf = std::make_unique<querybuf>();
+
+    res_state res = res_get_state();
+    if (!res) return EAI_MEMORY;
+
+    res_setnetcontext(res, netcontext);
+    int he;
+    n = res_nquery(res, qbuf, C_IN, T_PTR, buf->buf, (int)sizeof(buf->buf), &he);
+    if (n < 0) {
+        LOG(DEBUG) << __func__ << ": res_nquery failed (" << n << ")";
+        // Note that res_nquery() doesn't set the pair NETDB_INTERNAL and errno.
+        // Return h_errno (he) to catch more detailed errors rather than EAI_NODATA.
+        // See also herrnoToAiErrno().
+        return herrnoToAiErrno(he);
+    }
+    hostent* hp = getanswer(buf.get(), n, qbuf, T_PTR, res, info->hp, info->buf, info->buflen, &he);
+    if (hp == NULL) return herrnoToAiErrno(he);
+
+    char* bf = (char*) (hp->h_addr_list + 2);
+    size_t blen = (size_t)(bf - info->buf);
+    if (blen + info->hp->h_length > info->buflen) goto nospc;
+    hp->h_addr_list[0] = bf;
+    hp->h_addr_list[1] = NULL;
+    memcpy(bf, uaddr, (size_t) info->hp->h_length);
+    if (info->hp->h_addrtype == AF_INET && (res->options & RES_USE_INET6)) {
+        if (blen + NS_IN6ADDRSZ > info->buflen) goto nospc;
+        map_v4v6_address(bf, bf);
+        hp->h_addrtype = AF_INET6;
+        hp->h_length = NS_IN6ADDRSZ;
+    }
+
+    /* Reserve enough space for mapping IPv4 address to IPv6 address in place */
+    if (info->hp->h_addrtype == AF_INET) {
+        if (blen + NS_IN6ADDRSZ > info->buflen) goto nospc;
+        // Pad zero to the unused address space
+        memcpy(bf + NS_INADDRSZ, NAT64_PAD, sizeof(NAT64_PAD));
+    }
+
+    return 0;
+
+nospc:
+    return EAI_MEMORY;
+}
+
+/*
+ * Non-reentrant versions.
+ */
+
+int android_gethostbynamefornetcontext(const char* name, int af,
+                                       const struct android_net_context* netcontext, hostent** hp) {
+    int error;
+    res_state res = res_get_state();
+    if (res == NULL) return EAI_MEMORY;
+    res_static* rs = res_get_static();  // For thread-safety.
+    error = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
+                                   netcontext);
+    if (error == 0) {
+        *hp = &rs->host;
+    }
+    return error;
+}
+
+int android_gethostbyaddrfornetcontext(const void* addr, socklen_t len, int af,
+                                       const struct android_net_context* netcontext, hostent** hp) {
+    return android_gethostbyaddrfornetcontext_proxy(addr, len, af, netcontext, hp);
+}
+
+static int android_gethostbyaddrfornetcontext_proxy(const void* addr, socklen_t len, int af,
+                                                    const struct android_net_context* netcontext,
+                                                    hostent** hp) {
+    struct res_static* rs = res_get_static();  // For thread-safety.
+    int error = android_gethostbyaddrfornetcontext_proxy_internal(
+            addr, len, af, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), netcontext);
+    if (error == 0) *hp = &rs->host;
+    return error;
+}
+
+int herrnoToAiErrno(int he) {
+    switch (he) {
+        // extended h_errno
+        case NETD_RESOLV_H_ERRNO_EXT_TIMEOUT:
+            return NETD_RESOLV_TIMEOUT;
+        // legacy h_errno
+        case NETDB_SUCCESS:
+            return 0;
+        case HOST_NOT_FOUND:  // TODO: Perhaps convert HOST_NOT_FOUND to EAI_NONAME instead
+        case NO_DATA:         // NO_ADDRESS
+            return EAI_NODATA;
+        case TRY_AGAIN:
+            return EAI_AGAIN;
+        case NETDB_INTERNAL:
+            // TODO: Remove ENOSPC and call abort() immediately whenever any allocation fails.
+            if (errno == ENOSPC) return EAI_MEMORY;
+            // Theoretically, this should not happen. Leave this here just in case.
+            // Currently, getanswer() of {gethnamaddr, getaddrinfo}.cpp, res_nsearch() and
+            // res_searchN() use this function to convert error code. Only getanswer()
+            // of gethnamaddr.cpp may return the error code pair, herrno NETDB_INTERNAL and
+            // errno ENOSPC, which has already converted to EAI_MEMORY. The remaining functions
+            // don't set the pair herrno and errno.
+            return EAI_SYSTEM;  // see errno for detail
+        case NO_RECOVERY:
+        default:
+            return EAI_FAIL;  // TODO: Perhaps convert default to EAI_MAX (unknown error) instead
+    }
+}
diff --git a/resolv/gethnamaddr.h b/resolv/gethnamaddr.h
new file mode 100644
index 0000000..27cc1c2
--- /dev/null
+++ b/resolv/gethnamaddr.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.
+ */
+
+#pragma once
+
+#include <netdb.h>               // struct hostent
+#include "netd_resolv/resolv.h"  // struct android_net_context
+
+// This is the entry point for the gethostbyname() family of legacy calls.
+int android_gethostbynamefornetcontext(const char*, int, const android_net_context*, hostent**);
+
+// This is the entry point for the gethostbyaddr() family of legacy calls.
+int android_gethostbyaddrfornetcontext(const void*, socklen_t, int, const android_net_context*,
+                                       hostent**);
diff --git a/resolv/hostent.h b/resolv/hostent.h
new file mode 100644
index 0000000..4f6a33b
--- /dev/null
+++ b/resolv/hostent.h
@@ -0,0 +1,72 @@
+/*	$NetBSD: hostent.h,v 1.2 2013/08/27 09:56:12 christos Exp $	*/
+
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef NETD_RESOLV_HOSTENT_H
+#define NETD_RESOLV_HOSTENT_H
+
+#include <netdb.h>
+#include <stdio.h>
+
+struct getnamaddr {
+    struct hostent* hp;
+    char* buf;
+    size_t buflen;
+};
+
+// /etc/hosts lookup
+int _hf_gethtbyaddr(const unsigned char* uaddr, int len, int af, getnamaddr* info);
+int _hf_gethtbyname2(const char* name, int af, getnamaddr* info);
+hostent* netbsd_gethostent_r(FILE*, struct hostent*, char*, size_t, int*);
+
+// Reserved padding for remapping IPv4 address to NAT64 synthesis IPv6 address
+static const char NAT64_PAD[NS_IN6ADDRSZ - NS_INADDRSZ] = {};
+
+#define HENT_ARRAY(dst, anum, ptr, len) do {     \
+        size_t _len = (anum + 1) * sizeof(*dst); \
+        if (_len > len) goto nospc;              \
+        dst = (char**) ptr;                      \
+        ptr += _len;                             \
+        len -= _len;                             \
+    } while (0)
+
+#define HENT_COPY(dst, src, slen, ptr, len) do { \
+        if ((size_t) slen > len) goto nospc;     \
+        memcpy(ptr, src, (size_t) slen);         \
+        dst = ptr;                               \
+        ptr += slen;                             \
+        len -= slen;                             \
+    } while (0)
+
+#define HENT_SCOPY(dst, src, ptr, len) do {  \
+        size_t _len = strlen(src) + 1;       \
+        HENT_COPY(dst, src, _len, ptr, len); \
+    } while (0)
+
+#endif  // NETD_RESOLV_HOSTENT_H
diff --git a/resolv/include/netd_resolv/params.h b/resolv/include/netd_resolv/params.h
new file mode 100644
index 0000000..93e6287
--- /dev/null
+++ b/resolv/include/netd_resolv/params.h
@@ -0,0 +1,40 @@
+/*
+ * 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 NETD_RESOLV_PARAMS_H
+#define NETD_RESOLV_PARAMS_H
+
+#include <stdint.h>
+
+#define MAXNS 4            // max # name servers we'll track
+#define MAXDNSRCH 6        // max # domains in search path
+#define MAXDNSRCHPATH 256  // max length of domain search paths
+#define MAXNSSAMPLES 64    // max # samples to store per server
+
+// Per-netid configuration parameters passed from netd to the resolver
+struct res_params {
+    uint16_t sample_validity;  // sample lifetime in s
+    // threshold of success / total samples below which a server is considered broken
+    uint8_t success_threshold;  // 0: disable, value / 100 otherwise
+    uint8_t min_samples;        // min # samples needed for statistics to be considered meaningful
+    uint8_t max_samples;        // max # samples taken into account for statistics
+    int base_timeout_msec;      // base query retry timeout (if 0, use RES_TIMEOUT)
+    int retry_count;            // number of retries
+};
+
+#define LIBNETD_RESOLV_PUBLIC extern "C" [[gnu::visibility("default")]]
+
+#endif  // NETD_RESOLV_PARAMS_H
diff --git a/resolv/include/netd_resolv/resolv.h b/resolv/include/netd_resolv/resolv.h
new file mode 100644
index 0000000..3a71110
--- /dev/null
+++ b/resolv/include/netd_resolv/resolv.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "params.h"
+
+#include <netinet/in.h>
+
+typedef union sockaddr_union {
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+    struct sockaddr_in6 sin6;
+} sockaddr_union;
+
+/*
+ * Passing NETID_UNSET as the netId causes system/netd/resolv/DnsProxyListener.cpp to
+ * fill in the appropriate default netId for the query.
+ */
+#define NETID_UNSET 0u
+
+/*
+ * MARK_UNSET represents the default (i.e. unset) value for a socket mark.
+ */
+#define MARK_UNSET 0u
+
+/*
+ * Error code extending EAI_* codes defined in bionic/libc/include/netdb.h.
+ * This error code, including EAI_*, returned from android_getaddrinfofornetcontext()
+ * and android_gethostbynamefornetcontext() are used for DNS metrics.
+ */
+#define NETD_RESOLV_TIMEOUT 255  // consistent with RCODE_TIMEOUT
+
+/*
+ * A struct to capture context relevant to network operations.
+ *
+ * Application and DNS netids/marks can differ from one another under certain
+ * circumstances, notably when a VPN applies to the given uid's traffic but the
+ * VPN network does not have its own DNS servers explicitly provisioned.
+ *
+ * The introduction of per-UID routing means the uid is also an essential part
+ * of the evaluation context. Its proper uninitialized value is
+ * NET_CONTEXT_INVALID_UID.
+ */
+struct android_net_context {
+    unsigned app_netid;
+    unsigned app_mark;
+    unsigned dns_netid;
+    unsigned dns_mark;
+    uid_t uid;
+    unsigned flags;
+};
+
+#define NET_CONTEXT_INVALID_UID ((uid_t) -1)
+#define NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS 0x00000001
+#define NET_CONTEXT_FLAG_USE_EDNS 0x00000002
+
+// TODO: investigate having the resolver check permissions itself, either by adding support to
+// libbinder_ndk or by converting IPermissionController into a stable AIDL interface.
+typedef bool (*check_calling_permission_callback)(const char* permission);
+typedef void (*get_network_context_callback)(unsigned netid, uid_t uid,
+                                             android_net_context* netcontext);
+typedef void (*log_callback)(const char* msg);
+
+/*
+ * Some functions needed by the resolver (e.g. checkCallingPermission()) live in
+ * libraries with no ABI stability guarantees, such as libbinder.so.
+ * As a temporary workaround, we keep these functions in netd and call them via
+ * function pointers.
+ */
+struct ResolverNetdCallbacks {
+    check_calling_permission_callback check_calling_permission;
+    get_network_context_callback get_network_context;
+    log_callback log;
+};
+
+LIBNETD_RESOLV_PUBLIC bool resolv_has_nameservers(unsigned netid);
+
+// Set callbacks and bring DnsResolver up.
+LIBNETD_RESOLV_PUBLIC bool resolv_init(const ResolverNetdCallbacks* callbacks);
diff --git a/resolv/include/netd_resolv/resolv_stub.h b/resolv/include/netd_resolv/resolv_stub.h
new file mode 100644
index 0000000..41f56fa
--- /dev/null
+++ b/resolv/include/netd_resolv/resolv_stub.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef NETD_RESOLV_RESOLV_STUB_H
+#define NETD_RESOLV_RESOLV_STUB_H
+
+#include "resolv.h"
+#include "stats.h"
+
+// Struct containing function pointers for every function exported by libnetd_resolv.
+extern struct ResolvStub {
+    bool (*resolv_has_nameservers)(unsigned netid);
+
+    bool (*resolv_init)(const ResolverNetdCallbacks& callbacks);
+} RESOLV_STUB;
+
+int resolv_stub_init();
+
+#endif  // NETD_RESOLV_RESOLV_STUB_H
diff --git a/resolv/include/netd_resolv/stats.h b/resolv/include/netd_resolv/stats.h
new file mode 100644
index 0000000..7bfe845
--- /dev/null
+++ b/resolv/include/netd_resolv/stats.h
@@ -0,0 +1,62 @@
+/*
+ * 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 NETD_RES_STATS_H
+#define NETD_RES_STATS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <time.h>
+
+#include "params.h"
+
+#define RCODE_INTERNAL_ERROR 254
+#define RCODE_TIMEOUT 255
+
+struct res_sample {
+    time_t at;      // time in s at which the sample was recorded
+    uint16_t rtt;   // round-trip time in ms
+    uint8_t rcode;  // the DNS rcode or RCODE_XXX defined above
+};
+
+// Resolver reachability statistics and run-time parameters.
+struct res_stats {
+    // Stats of the last <sample_count> queries.
+    res_sample samples[MAXNSSAMPLES];
+    // The number of samples stored.
+    uint8_t sample_count;
+    // The next sample to modify.
+    uint8_t sample_next;
+};
+
+// Aggregates the reachability statistics for the given server based on on the stored samples.
+LIBNETD_RESOLV_PUBLIC void android_net_res_stats_aggregate(res_stats* stats, int* successes,
+                                                           int* errors, int* timeouts,
+                                                           int* internal_errors, int* rtt_avg,
+                                                           time_t* last_sample_time);
+
+LIBNETD_RESOLV_PUBLIC int android_net_res_stats_get_info_for_net(
+        unsigned netid, int* nscount, sockaddr_storage servers[MAXNS], int* dcount,
+        char domains[MAXDNSRCH][MAXDNSRCHPATH], res_params* params, res_stats stats[MAXNS],
+        int* wait_for_pending_req_timeout_count);
+
+// Returns an array of bools indicating which servers are considered good
+LIBNETD_RESOLV_PUBLIC int android_net_res_stats_get_usable_servers(const res_params* params,
+                                                                   res_stats stats[], int nscount,
+                                                                   bool valid_servers[]);
+
+#endif  // NETD_RES_STATS_H
diff --git a/resolv/libnetd_resolv.map.txt b/resolv/libnetd_resolv.map.txt
new file mode 100644
index 0000000..be193db
--- /dev/null
+++ b/resolv/libnetd_resolv.map.txt
@@ -0,0 +1,27 @@
+#
+# 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 lists the entry points visible to applications that use the
+# libnetd_resolv library. Other entry points present in the library won't be
+# usable.
+
+LIBNETD_RESOLV {
+  global:
+    resolv_has_nameservers;
+    resolv_init;
+  local:
+    *;
+};
diff --git a/resolv/libnetd_resolv_test.cpp b/resolv/libnetd_resolv_test.cpp
new file mode 100644
index 0000000..7fc5669
--- /dev/null
+++ b/resolv/libnetd_resolv_test.cpp
@@ -0,0 +1,591 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "libnetd_resolv_test"
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "dns_responder.h"
+#include "getaddrinfo.h"
+#include "gethnamaddr.h"
+#include "resolv_cache.h"
+
+// TODO: make this dynamic and stop depending on implementation details.
+constexpr unsigned int TEST_NETID = 30;
+
+// Specifying 0 in ai_socktype or ai_protocol of struct addrinfo indicates that any type or
+// protocol can be returned by getaddrinfo().
+constexpr unsigned int ANY = 0;
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace net {
+
+// Minimize class ResolverTest to be class TestBase because class TestBase doesn't need all member
+// functions of class ResolverTest and class DnsResponderClient.
+class TestBase : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        // Create cache for test
+        resolv_create_cache_for_net(TEST_NETID);
+    }
+    void TearDown() override {
+        // Delete cache for test
+        resolv_delete_cache_for_net(TEST_NETID);
+    }
+
+    static std::string ToString(const hostent* he) {
+        if (he == nullptr) return "<null>";
+        char buffer[INET6_ADDRSTRLEN];
+        if (!inet_ntop(he->h_addrtype, he->h_addr_list[0], buffer, sizeof(buffer))) {
+            return "<invalid>";
+        }
+        return buffer;
+    }
+
+    static std::string ToString(const addrinfo* ai) {
+        if (!ai) return "<null>";
+        for (const auto* aip = ai; aip != nullptr; aip = aip->ai_next) {
+            char host[NI_MAXHOST];
+            int rv = getnameinfo(aip->ai_addr, aip->ai_addrlen, host, sizeof(host), nullptr, 0,
+                                 NI_NUMERICHOST);
+            if (rv != 0) return gai_strerror(rv);
+            return host;
+        }
+        return "<invalid>";
+    }
+
+    size_t GetNumQueries(const test::DNSResponder& dns, const char* name) const {
+        auto queries = dns.queries();
+        size_t found = 0;
+        for (const auto& p : queries) {
+            if (p.first == name) {
+                ++found;
+            }
+        }
+        return found;
+    }
+
+    const char* mDefaultSearchDomains = "example.com";
+    const res_params mDefaultParams_Binder = {
+            .sample_validity = 300,
+            .success_threshold = 25,
+            .min_samples = 8,
+            .max_samples = 8,
+            .base_timeout_msec = 1000,
+            .retry_count = 2,
+    };
+    const android_net_context mNetcontext = {
+            .app_netid = TEST_NETID,
+            .app_mark = MARK_UNSET,
+            .dns_netid = TEST_NETID,
+            .dns_mark = MARK_UNSET,
+            .uid = NET_CONTEXT_INVALID_UID,
+    };
+};
+
+class GetAddrInfoForNetContextTest : public TestBase {};
+class GetHostByNameForNetContextTest : public TestBase {};
+
+TEST_F(GetAddrInfoForNetContextTest, InvalidParameters) {
+    // Both null "netcontext" and null "res" of android_getaddrinfofornetcontext() are not tested
+    // here because they are checked by assert() without returning any error number.
+
+    // Invalid hostname and servname.
+    // Both hostname and servname are null pointers. Expect error number EAI_NONAME.
+    struct addrinfo* result = nullptr;
+    int rv = android_getaddrinfofornetcontext(nullptr /*hostname*/, nullptr /*servname*/,
+                                              nullptr /*hints*/, &mNetcontext, &result);
+    EXPECT_EQ(EAI_NONAME, rv);
+    if (result) {
+        freeaddrinfo(result);
+        result = nullptr;
+    }
+
+    // Invalid hints.
+    // These place holders are used to test function call with unrequired parameters.
+    // The content is not important because function call returns error directly if
+    // there have any unrequired parameter.
+    char placeholder_cname[] = "invalid_cname";
+    sockaddr placeholder_addr = {};
+    addrinfo placeholder_next = {};
+    static const struct TestConfig {
+        int ai_flags;
+        socklen_t ai_addrlen;
+        char* ai_canonname;
+        struct sockaddr* ai_addr;
+        struct addrinfo* ai_next;
+        int expected_errorno;  // expected result
+
+        std::string asParameters() const {
+            return StringPrintf("0x%x/%u/%s/%p/%p", ai_flags, ai_addrlen,
+                                ai_canonname ? ai_canonname : "(null)", (void*) ai_addr,
+                                (void*) ai_next);
+        }
+    } testConfigs[]{
+            {0, sizeof(struct in_addr) /*bad*/, nullptr, nullptr, nullptr, EAI_BADHINTS},
+            {0, 0, placeholder_cname /*bad*/, nullptr, nullptr, EAI_BADHINTS},
+            {0, 0, nullptr, &placeholder_addr /*bad*/, nullptr, EAI_BADHINTS},
+            {0, 0, nullptr, nullptr, &placeholder_next /*bad*/, EAI_BADHINTS},
+            {AI_ALL /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
+            {AI_V4MAPPED_CFG /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
+            {AI_V4MAPPED /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
+            {AI_DEFAULT /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(config.asParameters());
+
+        // In current test configuration set, ai_family, ai_protocol and ai_socktype are not
+        // checked because other fields cause hints error check failed first.
+        const struct addrinfo hints = {
+                .ai_flags = config.ai_flags,
+                .ai_family = AF_UNSPEC,
+                .ai_socktype = ANY,
+                .ai_protocol = ANY,
+                .ai_addrlen = config.ai_addrlen,
+                .ai_canonname = config.ai_canonname,
+                .ai_addr = config.ai_addr,
+                .ai_next = config.ai_next,
+        };
+
+        rv = android_getaddrinfofornetcontext("localhost", nullptr /*servname*/, &hints,
+                                              &mNetcontext, &result);
+        EXPECT_EQ(config.expected_errorno, rv);
+
+        if (result) {
+            freeaddrinfo(result);
+            result = nullptr;
+        }
+    }
+}
+
+TEST_F(GetAddrInfoForNetContextTest, InvalidParameters_Family) {
+    for (int family = 0; family < AF_MAX; ++family) {
+        if (family == AF_UNSPEC || family == AF_INET || family == AF_INET6) {
+            continue;  // skip supported family
+        }
+        SCOPED_TRACE(StringPrintf("family: %d", family));
+
+        struct addrinfo* result = nullptr;
+        const struct addrinfo hints = {
+                .ai_family = family,  // unsupported family
+        };
+
+        int rv = android_getaddrinfofornetcontext("localhost", nullptr /*servname*/, &hints,
+                                                  &mNetcontext, &result);
+        EXPECT_EQ(EAI_FAMILY, rv);
+
+        if (result) freeaddrinfo(result);
+    }
+}
+
+TEST_F(GetAddrInfoForNetContextTest, InvalidParameters_MeaningfulSocktypeAndProtocolCombination) {
+    static const int families[] = {PF_INET, PF_INET6, PF_UNSPEC};
+    // Skip to test socket type SOCK_RAW in meaningful combination (explore_options[]) of
+    // system\netd\resolv\getaddrinfo.cpp. In explore_options[], the socket type SOCK_RAW always
+    // comes with protocol ANY which causes skipping meaningful socktype/protocol combination
+    // check. So it nerver returns error number EAI_BADHINTS which we want to test in this test
+    // case.
+    static const int socktypes[] = {SOCK_STREAM, SOCK_DGRAM};
+
+    // If both socktype/protocol are specified, check non-meaningful combination returns
+    // expected error number EAI_BADHINTS. See meaningful combination in explore_options[] of
+    // system\netd\resolv\getaddrinfo.cpp.
+    for (const auto& family : families) {
+        for (const auto& socktype : socktypes) {
+            for (int protocol = 0; protocol < IPPROTO_MAX; ++protocol) {
+                SCOPED_TRACE(StringPrintf("family: %d, socktype: %d, protocol: %d", family,
+                                          socktype, protocol));
+
+                // Both socktype/protocol need to be specified.
+                if (!socktype || !protocol) continue;
+
+                // Skip meaningful combination in explore_options[] of
+                // system\netd\resolv\getaddrinfo.cpp.
+                if ((family == AF_INET6 && socktype == SOCK_DGRAM && protocol == IPPROTO_UDP) ||
+                    (family == AF_INET6 && socktype == SOCK_STREAM && protocol == IPPROTO_TCP) ||
+                    (family == AF_INET && socktype == SOCK_DGRAM && protocol == IPPROTO_UDP) ||
+                    (family == AF_INET && socktype == SOCK_STREAM && protocol == IPPROTO_TCP) ||
+                    (family == AF_UNSPEC && socktype == SOCK_DGRAM && protocol == IPPROTO_UDP) ||
+                    (family == AF_UNSPEC && socktype == SOCK_STREAM && protocol == IPPROTO_TCP)) {
+                    continue;
+                }
+
+                struct addrinfo* result = nullptr;
+                const struct addrinfo hints = {
+                        .ai_family = family,
+                        .ai_protocol = protocol,
+                        .ai_socktype = socktype,
+                };
+
+                int rv = android_getaddrinfofornetcontext("localhost", nullptr /*servname*/, &hints,
+                                                          &mNetcontext, &result);
+                EXPECT_EQ(EAI_BADHINTS, rv);
+
+                if (result) freeaddrinfo(result);
+            }
+        }
+    }
+}
+
+TEST_F(GetAddrInfoForNetContextTest, InvalidParameters_PortNameAndNumber) {
+    constexpr char http_portno[] = "80";
+    constexpr char invalid_portno[] = "65536";  // out of valid port range from 0 to 65535
+    constexpr char http_portname[] = "http";
+    constexpr char invalid_portname[] = "invalid_portname";
+
+    static const struct TestConfig {
+        int ai_flags;
+        int ai_family;
+        int ai_socktype;
+        const char* servname;
+        int expected_errorno;  // expected result
+
+        std::string asParameters() const {
+            return StringPrintf("0x%x/%d/%d/%s", ai_flags, ai_family, ai_socktype,
+                                servname ? servname : "(null)");
+        }
+    } testConfigs[]{
+            {0, AF_INET, SOCK_RAW /*bad*/, http_portno, EAI_SERVICE},
+            {0, AF_INET6, SOCK_RAW /*bad*/, http_portno, EAI_SERVICE},
+            {0, AF_UNSPEC, SOCK_RAW /*bad*/, http_portno, EAI_SERVICE},
+            {0, AF_INET, SOCK_RDM /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET6, SOCK_RDM /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_UNSPEC, SOCK_RDM /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET, SOCK_SEQPACKET /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET6, SOCK_SEQPACKET /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_UNSPEC, SOCK_SEQPACKET /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET, SOCK_DCCP /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET6, SOCK_DCCP /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_UNSPEC, SOCK_DCCP /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET, SOCK_PACKET /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET6, SOCK_PACKET /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_UNSPEC, SOCK_PACKET /*bad*/, http_portno, EAI_SOCKTYPE},
+            {0, AF_INET, ANY, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_INET, SOCK_STREAM, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_INET, SOCK_DGRAM, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_INET6, ANY, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_INET6, SOCK_STREAM, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_INET6, SOCK_DGRAM, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_UNSPEC, ANY, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_UNSPEC, SOCK_STREAM, invalid_portno /*bad*/, EAI_SERVICE},
+            {0, AF_UNSPEC, SOCK_DGRAM, invalid_portno /*bad*/, EAI_SERVICE},
+            {AI_NUMERICSERV, AF_INET, ANY, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_INET, SOCK_STREAM, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_INET, SOCK_DGRAM, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_INET6, ANY, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_INET6, SOCK_STREAM, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_INET6, SOCK_DGRAM, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_UNSPEC, ANY, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_UNSPEC, SOCK_STREAM, http_portname /*bad*/, EAI_NONAME},
+            {AI_NUMERICSERV, AF_UNSPEC, SOCK_DGRAM, http_portname /*bad*/, EAI_NONAME},
+            {0, AF_INET, ANY, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_INET, SOCK_STREAM, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_INET, SOCK_DGRAM, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_INET6, ANY, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_INET6, SOCK_STREAM, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_INET6, SOCK_DGRAM, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_UNSPEC, ANY, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_UNSPEC, SOCK_STREAM, invalid_portname /*bad*/, EAI_SERVICE},
+            {0, AF_UNSPEC, SOCK_DGRAM, invalid_portname /*bad*/, EAI_SERVICE},
+    };
+
+    for (const auto& config : testConfigs) {
+        const std::string testParameters = config.asParameters();
+        SCOPED_TRACE(testParameters);
+
+        const struct addrinfo hints = {
+                .ai_flags = config.ai_flags,
+                .ai_family = config.ai_family,
+                .ai_socktype = config.ai_socktype,
+        };
+
+        struct addrinfo* result = nullptr;
+        int rv = android_getaddrinfofornetcontext("localhost", config.servname, &hints,
+                                                  &mNetcontext, &result);
+        EXPECT_EQ(config.expected_errorno, rv);
+
+        if (result) freeaddrinfo(result);
+    }
+}
+
+TEST_F(GetAddrInfoForNetContextTest, AlphabeticalHostname_NoData) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char v4_host_name[] = "v4only.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns.addMapping(v4_host_name, ns_type::ns_t_a, "1.2.3.3");
+    ASSERT_TRUE(dns.startServer());
+    const char* servers[] = {listen_addr};
+    ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                sizeof(servers) / sizeof(servers[0]),
+                                                mDefaultSearchDomains, &mDefaultParams_Binder));
+    dns.clearQueries();
+
+    // Want AAAA answer but DNS server has A answer only.
+    struct addrinfo* result = nullptr;
+    const addrinfo hints = {.ai_family = AF_INET6};
+    int rv = android_getaddrinfofornetcontext("v4only", nullptr, &hints, &mNetcontext, &result);
+    EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
+    EXPECT_TRUE(result == nullptr);
+    EXPECT_EQ(EAI_NODATA, rv);
+
+    if (result) freeaddrinfo(result);
+}
+
+TEST_F(GetAddrInfoForNetContextTest, AlphabeticalHostname) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name[] = "sawadee.example.com.";
+    constexpr char v4addr[] = "1.2.3.4";
+    constexpr char v6addr[] = "::1.2.3.4";
+
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns.addMapping(host_name, ns_type::ns_t_a, v4addr);
+    dns.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
+    ASSERT_TRUE(dns.startServer());
+    const char* servers[] = {listen_addr};
+    ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                sizeof(servers) / sizeof(servers[0]),
+                                                mDefaultSearchDomains, &mDefaultParams_Binder));
+
+    static const struct TestConfig {
+        int ai_family;
+        const std::string expected_addr;
+    } testConfigs[]{
+            {AF_INET, v4addr},
+            {AF_INET6, v6addr},
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(StringPrintf("family: %d", config.ai_family));
+        dns.clearQueries();
+
+        struct addrinfo* result = nullptr;
+        const struct addrinfo hints = {.ai_family = config.ai_family};
+        int rv =
+                android_getaddrinfofornetcontext("sawadee", nullptr, &hints, &mNetcontext, &result);
+        EXPECT_EQ(0, rv);
+        EXPECT_TRUE(result != nullptr);
+        EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+        EXPECT_EQ(config.expected_addr, ToString(result));
+
+        if (result) freeaddrinfo(result);
+    }
+}
+
+TEST_F(GetAddrInfoForNetContextTest, ServerResponseError) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name[] = "hello.example.com.";
+
+    static const struct TestConfig {
+        ns_rcode rcode;
+        int expected_errorno;  // expected result
+
+        // Only test failure RCODE [1..5] in RFC 1035 section 4.1.1 and skip successful RCODE 0
+        // which means no error.
+    } testConfigs[]{
+            // clang-format off
+            {ns_rcode::ns_r_formerr, EAI_FAIL},
+            {ns_rcode::ns_r_servfail, EAI_AGAIN},
+            {ns_rcode::ns_r_nxdomain, EAI_NODATA},
+            {ns_rcode::ns_r_notimpl, EAI_FAIL},
+            {ns_rcode::ns_r_refused, EAI_FAIL},
+            // clang-format on
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(StringPrintf("rcode: %d", config.rcode));
+
+        test::DNSResponder dns(listen_addr, listen_srv, 250,
+                               config.rcode /*response specific rcode*/);
+        dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+        dns.setResponseProbability(0.0);  // always ignore requests and response preset rcode
+        ASSERT_TRUE(dns.startServer());
+        const char* servers[] = {listen_addr};
+        ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                    sizeof(servers) / sizeof(servers[0]),
+                                                    mDefaultSearchDomains, &mDefaultParams_Binder));
+
+        struct addrinfo* result = nullptr;
+        const struct addrinfo hints = {.ai_family = AF_UNSPEC};
+        int rv =
+                android_getaddrinfofornetcontext(host_name, nullptr, &hints, &mNetcontext, &result);
+        EXPECT_EQ(config.expected_errorno, rv);
+
+        if (result) freeaddrinfo(result);
+    }
+}
+
+// TODO: Add private DNS server timeout test.
+TEST_F(GetAddrInfoForNetContextTest, ServerTimeout) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name[] = "hello.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, static_cast<ns_rcode>(-1) /*no response*/);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+    dns.setResponseProbability(0.0);  // always ignore requests and don't response
+    ASSERT_TRUE(dns.startServer());
+    const char* servers[] = {listen_addr};
+    ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                sizeof(servers) / sizeof(servers[0]),
+                                                mDefaultSearchDomains, &mDefaultParams_Binder));
+
+    struct addrinfo* result = nullptr;
+    const struct addrinfo hints = {.ai_family = AF_UNSPEC};
+    int rv = android_getaddrinfofornetcontext("hello", nullptr, &hints, &mNetcontext, &result);
+    EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
+
+    if (result) freeaddrinfo(result);
+}
+
+TEST_F(GetHostByNameForNetContextTest, AlphabeticalHostname) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name[] = "jiababuei.example.com.";
+    constexpr char v4addr[] = "1.2.3.4";
+    constexpr char v6addr[] = "::1.2.3.4";
+
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns.addMapping(host_name, ns_type::ns_t_a, v4addr);
+    dns.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
+    ASSERT_TRUE(dns.startServer());
+    const char* servers[] = {listen_addr};
+    ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                sizeof(servers) / sizeof(servers[0]),
+                                                mDefaultSearchDomains, &mDefaultParams_Binder));
+
+    static const struct TestConfig {
+        int ai_family;
+        const std::string expected_addr;
+    } testConfigs[]{
+            {AF_INET, v4addr},
+            {AF_INET6, v6addr},
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(StringPrintf("family: %d", config.ai_family));
+        dns.clearQueries();
+
+        struct hostent* hp = nullptr;
+        int rv = android_gethostbynamefornetcontext("jiababuei", config.ai_family, &mNetcontext,
+                                                    &hp);
+        EXPECT_EQ(0, rv);
+        EXPECT_TRUE(hp != nullptr);
+        EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+        EXPECT_EQ(config.expected_addr, ToString(hp));
+    }
+}
+
+TEST_F(GetHostByNameForNetContextTest, NoData) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char v4_host_name[] = "v4only.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns.addMapping(v4_host_name, ns_type::ns_t_a, "1.2.3.3");
+    ASSERT_TRUE(dns.startServer());
+    const char* servers[] = {listen_addr};
+    ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                sizeof(servers) / sizeof(servers[0]),
+                                                mDefaultSearchDomains, &mDefaultParams_Binder));
+    dns.clearQueries();
+
+    // Want AAAA answer but DNS server has A answer only.
+    struct hostent* hp = nullptr;
+    int rv = android_gethostbynamefornetcontext("v4only", AF_INET6, &mNetcontext, &hp);
+    EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
+    EXPECT_TRUE(hp == nullptr);
+    EXPECT_EQ(EAI_NODATA, rv);
+}
+
+TEST_F(GetHostByNameForNetContextTest, ServerResponseError) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name[] = "hello.example.com.";
+
+    static const struct TestConfig {
+        ns_rcode rcode;
+        int expected_errorno;  // expected result
+
+        // Only test failure RCODE [1..5] in RFC 1035 section 4.1.1 and skip successful RCODE 0
+        // which means no error. Note that the return error codes aren't mapped by rcode in the
+        // test case SERVFAIL, NOTIMP and REFUSED. See the comment of res_nsend()
+        // in system\netd\resolv\res_query.cpp for more detail.
+    } testConfigs[]{
+            // clang-format off
+            {ns_rcode::ns_r_formerr, EAI_FAIL},
+            {ns_rcode::ns_r_servfail, EAI_AGAIN},  // Not mapped by rcode.
+            {ns_rcode::ns_r_nxdomain, EAI_NODATA},
+            {ns_rcode::ns_r_notimpl, EAI_AGAIN},  // Not mapped by rcode.
+            {ns_rcode::ns_r_refused, EAI_AGAIN},  // Not mapped by rcode.
+            // clang-format on
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(StringPrintf("rcode: %d", config.rcode));
+
+        test::DNSResponder dns(listen_addr, listen_srv, 250,
+                               config.rcode /*response specific rcode*/);
+        dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+        dns.setResponseProbability(0.0);  // always ignore requests and response preset rcode
+        ASSERT_TRUE(dns.startServer());
+        const char* servers[] = {listen_addr};
+        ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                    sizeof(servers) / sizeof(servers[0]),
+                                                    mDefaultSearchDomains, &mDefaultParams_Binder));
+
+        struct hostent* hp = nullptr;
+        int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp);
+        EXPECT_TRUE(hp == nullptr);
+        EXPECT_EQ(config.expected_errorno, rv);
+    }
+}
+
+// TODO: Add private DNS server timeout test.
+TEST_F(GetHostByNameForNetContextTest, ServerTimeout) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name[] = "hello.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, static_cast<ns_rcode>(-1) /*no response*/);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+    dns.setResponseProbability(0.0);  // always ignore requests and don't response
+    ASSERT_TRUE(dns.startServer());
+    const char* servers[] = {listen_addr};
+    ASSERT_EQ(0, resolv_set_nameservers_for_net(TEST_NETID, servers,
+                                                sizeof(servers) / sizeof(servers[0]),
+                                                mDefaultSearchDomains, &mDefaultParams_Binder));
+
+    struct hostent* hp = nullptr;
+    int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp);
+    EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
+}
+
+// Note that local host file function, files_getaddrinfo(), of android_getaddrinfofornetcontext()
+// is not tested because it only returns a boolean (success or failure) without any error number.
+
+// TODO: Add test NULL hostname, or numeric hostname for android_getaddrinfofornetcontext.
+// TODO: Add test invalid parameters for android_gethostbynamefornetcontext.
+// TODO: Add test for android_gethostbyaddrfornetcontext.
+
+}  // end of namespace net
+}  // end of namespace android
diff --git a/resolv/res_cache.cpp b/resolv/res_cache.cpp
new file mode 100644
index 0000000..f0ff564
--- /dev/null
+++ b/resolv/res_cache.cpp
@@ -0,0 +1,2025 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define LOG_TAG "res_cache"
+
+#include "resolv_cache.h"
+
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <mutex>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <linux/if.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/thread_annotations.h>
+#include <android/multinetwork.h>  // ResNsendFlags
+
+#include <server_configurable_flags/get_flags.h>
+
+#include "res_state_ext.h"
+#include "resolv_private.h"
+
+// NOTE: verbose logging MUST NOT be left enabled in production binaries.
+// It floods logs at high rate, and can leak privacy-sensitive information.
+constexpr bool kDumpData = false;
+
+/* This code implements a small and *simple* DNS resolver cache.
+ *
+ * It is only used to cache DNS answers for a time defined by the smallest TTL
+ * among the answer records in order to reduce DNS traffic. It is not supposed
+ * to be a full DNS cache, since we plan to implement that in the future in a
+ * dedicated process running on the system.
+ *
+ * Note that its design is kept simple very intentionally, i.e.:
+ *
+ *  - it takes raw DNS query packet data as input, and returns raw DNS
+ *    answer packet data as output
+ *
+ *    (this means that two similar queries that encode the DNS name
+ *     differently will be treated distinctly).
+ *
+ *    the smallest TTL value among the answer records are used as the time
+ *    to keep an answer in the cache.
+ *
+ *    this is bad, but we absolutely want to avoid parsing the answer packets
+ *    (and should be solved by the later full DNS cache process).
+ *
+ *  - the implementation is just a (query-data) => (answer-data) hash table
+ *    with a trivial least-recently-used expiration policy.
+ *
+ * Doing this keeps the code simple and avoids to deal with a lot of things
+ * that a full DNS cache is expected to do.
+ *
+ * The API is also very simple:
+ *
+ *   - the client calls _resolv_cache_get() to obtain a handle to the cache.
+ *     this will initialize the cache on first usage. the result can be NULL
+ *     if the cache is disabled.
+ *
+ *   - the client calls _resolv_cache_lookup() before performing a query
+ *
+ *     if the function returns RESOLV_CACHE_FOUND, a copy of the answer data
+ *     has been copied into the client-provided answer buffer.
+ *
+ *     if the function returns RESOLV_CACHE_NOTFOUND, the client should perform
+ *     a request normally, *then* call _resolv_cache_add() to add the received
+ *     answer to the cache.
+ *
+ *     if the function returns RESOLV_CACHE_UNSUPPORTED, the client should
+ *     perform a request normally, and *not* call _resolv_cache_add()
+ *
+ *     note that RESOLV_CACHE_UNSUPPORTED is also returned if the answer buffer
+ *     is too short to accomodate the cached result.
+ */
+
+/* default number of entries kept in the cache. This value has been
+ * determined by browsing through various sites and counting the number
+ * of corresponding requests. Keep in mind that our framework is currently
+ * performing two requests per name lookup (one for IPv4, the other for IPv6)
+ *
+ *    www.google.com      4
+ *    www.ysearch.com     6
+ *    www.amazon.com      8
+ *    www.nytimes.com     22
+ *    www.espn.com        28
+ *    www.msn.com         28
+ *    www.lemonde.fr      35
+ *
+ * (determined in 2009-2-17 from Paris, France, results may vary depending
+ *  on location)
+ *
+ * most high-level websites use lots of media/ad servers with different names
+ * but these are generally reused when browsing through the site.
+ *
+ * As such, a value of 64 should be relatively comfortable at the moment.
+ *
+ * ******************************************
+ * * NOTE - this has changed.
+ * * 1) we've added IPv6 support so each dns query results in 2 responses
+ * * 2) we've made this a system-wide cache, so the cost is less (it's not
+ * *    duplicated in each process) and the need is greater (more processes
+ * *    making different requests).
+ * * Upping by 2x for IPv6
+ * * Upping by another 5x for the centralized nature
+ * *****************************************
+ */
+#define CONFIG_MAX_ENTRIES (64 * 2 * 5)
+
+/** BOUNDED BUFFER FORMATTING **/
+
+/* technical note:
+ *
+ *   the following debugging routines are used to append data to a bounded
+ *   buffer they take two parameters that are:
+ *
+ *   - p : a pointer to the current cursor position in the buffer
+ *         this value is initially set to the buffer's address.
+ *
+ *   - end : the address of the buffer's limit, i.e. of the first byte
+ *           after the buffer. this address should never be touched.
+ *
+ *           IMPORTANT: it is assumed that end > buffer_address, i.e.
+ *                      that the buffer is at least one byte.
+ *
+ *   the bprint_x() functions return the new value of 'p' after the data
+ *   has been appended, and also ensure the following:
+ *
+ *   - the returned value will never be strictly greater than 'end'
+ *
+ *   - a return value equal to 'end' means that truncation occurred
+ *     (in which case, end[-1] will be set to 0)
+ *
+ *   - after returning from a bprint_x() function, the content of the buffer
+ *     is always 0-terminated, even in the event of truncation.
+ *
+ *  these conventions allow you to call bprint_x() functions multiple times and
+ *  only check for truncation at the end of the sequence, as in:
+ *
+ *     char  buff[1000], *p = buff, *end = p + sizeof(buff);
+ *
+ *     p = bprint_c(p, end, '"');
+ *     p = bprint_s(p, end, my_string);
+ *     p = bprint_c(p, end, '"');
+ *
+ *     if (p >= end) {
+ *        // buffer was too small
+ *     }
+ *
+ *     printf( "%s", buff );
+ */
+
+/* Defaults used for initializing res_params */
+
+// If successes * 100 / total_samples is less than this value, the server is considered failing
+#define SUCCESS_THRESHOLD 75
+// Sample validity in seconds. Set to -1 to disable skipping failing servers.
+#define NSSAMPLE_VALIDITY 1800
+
+/* add a char to a bounded buffer */
+static char* bprint_c(char* p, char* end, int c) {
+    if (p < end) {
+        if (p + 1 == end)
+            *p++ = 0;
+        else {
+            *p++ = (char) c;
+            *p = 0;
+        }
+    }
+    return p;
+}
+
+/* add a sequence of bytes to a bounded buffer */
+static char* bprint_b(char* p, char* end, const char* buf, int len) {
+    int avail = end - p;
+
+    if (avail <= 0 || len <= 0) return p;
+
+    if (avail > len) avail = len;
+
+    memcpy(p, buf, avail);
+    p += avail;
+
+    if (p < end)
+        p[0] = 0;
+    else
+        end[-1] = 0;
+
+    return p;
+}
+
+/* add a string to a bounded buffer */
+static char* bprint_s(char* p, char* end, const char* str) {
+    return bprint_b(p, end, str, strlen(str));
+}
+
+/* add a formatted string to a bounded buffer */
+static char* bprint(char* p, char* end, const char* format, ...) {
+    int avail, n;
+    va_list args;
+
+    avail = end - p;
+
+    if (avail <= 0) return p;
+
+    va_start(args, format);
+    n = vsnprintf(p, avail, format, args);
+    va_end(args);
+
+    /* certain C libraries return -1 in case of truncation */
+    if (n < 0 || n > avail) n = avail;
+
+    p += n;
+    /* certain C libraries do not zero-terminate in case of truncation */
+    if (p == end) p[-1] = 0;
+
+    return p;
+}
+
+/* add a hex value to a bounded buffer, up to 8 digits */
+static char* bprint_hex(char* p, char* end, unsigned value, int numDigits) {
+    char text[sizeof(unsigned) * 2];
+    int nn = 0;
+
+    while (numDigits-- > 0) {
+        text[nn++] = "0123456789abcdef"[(value >> (numDigits * 4)) & 15];
+    }
+    return bprint_b(p, end, text, nn);
+}
+
+/* add the hexadecimal dump of some memory area to a bounded buffer */
+static char* bprint_hexdump(char* p, char* end, const uint8_t* data, int datalen) {
+    int lineSize = 16;
+
+    while (datalen > 0) {
+        int avail = datalen;
+        int nn;
+
+        if (avail > lineSize) avail = lineSize;
+
+        for (nn = 0; nn < avail; nn++) {
+            if (nn > 0) p = bprint_c(p, end, ' ');
+            p = bprint_hex(p, end, data[nn], 2);
+        }
+        for (; nn < lineSize; nn++) {
+            p = bprint_s(p, end, "   ");
+        }
+        p = bprint_s(p, end, "  ");
+
+        for (nn = 0; nn < avail; nn++) {
+            int c = data[nn];
+
+            if (c < 32 || c > 127) c = '.';
+
+            p = bprint_c(p, end, c);
+        }
+        p = bprint_c(p, end, '\n');
+
+        data += avail;
+        datalen -= avail;
+    }
+    return p;
+}
+
+/* dump the content of a query of packet to the log */
+static void dump_bytes(const uint8_t* base, int len) {
+    if (!kDumpData) return;
+
+    char buff[1024];
+    char *p = buff, *end = p + sizeof(buff);
+
+    p = bprint_hexdump(p, end, base, len);
+    LOG(INFO) << __func__ << ": " << buff;
+}
+
+static time_t _time_now(void) {
+    struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec;
+}
+
+/* reminder: the general format of a DNS packet is the following:
+ *
+ *    HEADER  (12 bytes)
+ *    QUESTION  (variable)
+ *    ANSWER (variable)
+ *    AUTHORITY (variable)
+ *    ADDITIONNAL (variable)
+ *
+ * the HEADER is made of:
+ *
+ *   ID     : 16 : 16-bit unique query identification field
+ *
+ *   QR     :  1 : set to 0 for queries, and 1 for responses
+ *   Opcode :  4 : set to 0 for queries
+ *   AA     :  1 : set to 0 for queries
+ *   TC     :  1 : truncation flag, will be set to 0 in queries
+ *   RD     :  1 : recursion desired
+ *
+ *   RA     :  1 : recursion available (0 in queries)
+ *   Z      :  3 : three reserved zero bits
+ *   RCODE  :  4 : response code (always 0=NOERROR in queries)
+ *
+ *   QDCount: 16 : question count
+ *   ANCount: 16 : Answer count (0 in queries)
+ *   NSCount: 16: Authority Record count (0 in queries)
+ *   ARCount: 16: Additionnal Record count (0 in queries)
+ *
+ * the QUESTION is made of QDCount Question Record (QRs)
+ * the ANSWER is made of ANCount RRs
+ * the AUTHORITY is made of NSCount RRs
+ * the ADDITIONNAL is made of ARCount RRs
+ *
+ * Each Question Record (QR) is made of:
+ *
+ *   QNAME   : variable : Query DNS NAME
+ *   TYPE    : 16       : type of query (A=1, PTR=12, MX=15, AAAA=28, ALL=255)
+ *   CLASS   : 16       : class of query (IN=1)
+ *
+ * Each Resource Record (RR) is made of:
+ *
+ *   NAME    : variable : DNS NAME
+ *   TYPE    : 16       : type of query (A=1, PTR=12, MX=15, AAAA=28, ALL=255)
+ *   CLASS   : 16       : class of query (IN=1)
+ *   TTL     : 32       : seconds to cache this RR (0=none)
+ *   RDLENGTH: 16       : size of RDDATA in bytes
+ *   RDDATA  : variable : RR data (depends on TYPE)
+ *
+ * Each QNAME contains a domain name encoded as a sequence of 'labels'
+ * terminated by a zero. Each label has the following format:
+ *
+ *    LEN  : 8     : lenght of label (MUST be < 64)
+ *    NAME : 8*LEN : label length (must exclude dots)
+ *
+ * A value of 0 in the encoding is interpreted as the 'root' domain and
+ * terminates the encoding. So 'www.android.com' will be encoded as:
+ *
+ *   <3>www<7>android<3>com<0>
+ *
+ * Where <n> represents the byte with value 'n'
+ *
+ * Each NAME reflects the QNAME of the question, but has a slightly more
+ * complex encoding in order to provide message compression. This is achieved
+ * by using a 2-byte pointer, with format:
+ *
+ *    TYPE   : 2  : 0b11 to indicate a pointer, 0b01 and 0b10 are reserved
+ *    OFFSET : 14 : offset to another part of the DNS packet
+ *
+ * The offset is relative to the start of the DNS packet and must point
+ * A pointer terminates the encoding.
+ *
+ * The NAME can be encoded in one of the following formats:
+ *
+ *   - a sequence of simple labels terminated by 0 (like QNAMEs)
+ *   - a single pointer
+ *   - a sequence of simple labels terminated by a pointer
+ *
+ * A pointer shall always point to either a pointer of a sequence of
+ * labels (which can themselves be terminated by either a 0 or a pointer)
+ *
+ * The expanded length of a given domain name should not exceed 255 bytes.
+ *
+ * NOTE: we don't parse the answer packets, so don't need to deal with NAME
+ *       records, only QNAMEs.
+ */
+
+#define DNS_HEADER_SIZE 12
+
+#define DNS_TYPE_A "\00\01"     /* big-endian decimal 1 */
+#define DNS_TYPE_PTR "\00\014"  /* big-endian decimal 12 */
+#define DNS_TYPE_MX "\00\017"   /* big-endian decimal 15 */
+#define DNS_TYPE_AAAA "\00\034" /* big-endian decimal 28 */
+#define DNS_TYPE_ALL "\00\0377" /* big-endian decimal 255 */
+
+#define DNS_CLASS_IN "\00\01" /* big-endian decimal 1 */
+
+typedef struct {
+    const uint8_t* base;
+    const uint8_t* end;
+    const uint8_t* cursor;
+} DnsPacket;
+
+static void _dnsPacket_init(DnsPacket* packet, const uint8_t* buff, int bufflen) {
+    packet->base = buff;
+    packet->end = buff + bufflen;
+    packet->cursor = buff;
+}
+
+static void _dnsPacket_rewind(DnsPacket* packet) {
+    packet->cursor = packet->base;
+}
+
+static void _dnsPacket_skip(DnsPacket* packet, int count) {
+    const uint8_t* p = packet->cursor + count;
+
+    if (p > packet->end) p = packet->end;
+
+    packet->cursor = p;
+}
+
+static int _dnsPacket_readInt16(DnsPacket* packet) {
+    const uint8_t* p = packet->cursor;
+
+    if (p + 2 > packet->end) return -1;
+
+    packet->cursor = p + 2;
+    return (p[0] << 8) | p[1];
+}
+
+/** QUERY CHECKING **/
+
+/* check bytes in a dns packet. returns 1 on success, 0 on failure.
+ * the cursor is only advanced in the case of success
+ */
+static int _dnsPacket_checkBytes(DnsPacket* packet, int numBytes, const void* bytes) {
+    const uint8_t* p = packet->cursor;
+
+    if (p + numBytes > packet->end) return 0;
+
+    if (memcmp(p, bytes, numBytes) != 0) return 0;
+
+    packet->cursor = p + numBytes;
+    return 1;
+}
+
+/* parse and skip a given QNAME stored in a query packet,
+ * from the current cursor position. returns 1 on success,
+ * or 0 for malformed data.
+ */
+static int _dnsPacket_checkQName(DnsPacket* packet) {
+    const uint8_t* p = packet->cursor;
+    const uint8_t* end = packet->end;
+
+    for (;;) {
+        int c;
+
+        if (p >= end) break;
+
+        c = *p++;
+
+        if (c == 0) {
+            packet->cursor = p;
+            return 1;
+        }
+
+        /* we don't expect label compression in QNAMEs */
+        if (c >= 64) break;
+
+        p += c;
+        /* we rely on the bound check at the start
+         * of the loop here */
+    }
+    /* malformed data */
+    LOG(INFO) << __func__ << ": malformed QNAME";
+    return 0;
+}
+
+/* parse and skip a given QR stored in a packet.
+ * returns 1 on success, and 0 on failure
+ */
+static int _dnsPacket_checkQR(DnsPacket* packet) {
+    if (!_dnsPacket_checkQName(packet)) return 0;
+
+    /* TYPE must be one of the things we support */
+    if (!_dnsPacket_checkBytes(packet, 2, DNS_TYPE_A) &&
+        !_dnsPacket_checkBytes(packet, 2, DNS_TYPE_PTR) &&
+        !_dnsPacket_checkBytes(packet, 2, DNS_TYPE_MX) &&
+        !_dnsPacket_checkBytes(packet, 2, DNS_TYPE_AAAA) &&
+        !_dnsPacket_checkBytes(packet, 2, DNS_TYPE_ALL)) {
+        LOG(INFO) << __func__ << ": unsupported TYPE";
+        return 0;
+    }
+    /* CLASS must be IN */
+    if (!_dnsPacket_checkBytes(packet, 2, DNS_CLASS_IN)) {
+        LOG(INFO) << __func__ << ": unsupported CLASS";
+        return 0;
+    }
+
+    return 1;
+}
+
+/* check the header of a DNS Query packet, return 1 if it is one
+ * type of query we can cache, or 0 otherwise
+ */
+static int _dnsPacket_checkQuery(DnsPacket* packet) {
+    const uint8_t* p = packet->base;
+    int qdCount, anCount, dnCount, arCount;
+
+    if (p + DNS_HEADER_SIZE > packet->end) {
+        LOG(INFO) << __func__ << ": query packet too small";
+        return 0;
+    }
+
+    /* QR must be set to 0, opcode must be 0 and AA must be 0 */
+    /* RA, Z, and RCODE must be 0 */
+    if ((p[2] & 0xFC) != 0 || (p[3] & 0xCF) != 0) {
+        LOG(INFO) << __func__ << ": query packet flags unsupported";
+        return 0;
+    }
+
+    /* Note that we ignore the TC, RD, CD, and AD bits here for the
+     * following reasons:
+     *
+     * - there is no point for a query packet sent to a server
+     *   to have the TC bit set, but the implementation might
+     *   set the bit in the query buffer for its own needs
+     *   between a _resolv_cache_lookup and a
+     *   _resolv_cache_add. We should not freak out if this
+     *   is the case.
+     *
+     * - we consider that the result from a query might depend on
+     *   the RD, AD, and CD bits, so these bits
+     *   should be used to differentiate cached result.
+     *
+     *   this implies that these bits are checked when hashing or
+     *   comparing query packets, but not TC
+     */
+
+    /* ANCOUNT, DNCOUNT and ARCOUNT must be 0 */
+    qdCount = (p[4] << 8) | p[5];
+    anCount = (p[6] << 8) | p[7];
+    dnCount = (p[8] << 8) | p[9];
+    arCount = (p[10] << 8) | p[11];
+
+    if (anCount != 0 || dnCount != 0 || arCount > 1) {
+        LOG(INFO) << __func__ << ": query packet contains non-query records";
+        return 0;
+    }
+
+    if (qdCount == 0) {
+        LOG(INFO) << __func__ << ": query packet doesn't contain query record";
+        return 0;
+    }
+
+    /* Check QDCOUNT QRs */
+    packet->cursor = p + DNS_HEADER_SIZE;
+
+    for (; qdCount > 0; qdCount--)
+        if (!_dnsPacket_checkQR(packet)) return 0;
+
+    return 1;
+}
+
+/** QUERY DEBUGGING **/
+static char* dnsPacket_bprintQName(DnsPacket* packet, char* bp, char* bend) {
+    const uint8_t* p = packet->cursor;
+    const uint8_t* end = packet->end;
+    int first = 1;
+
+    for (;;) {
+        int c;
+
+        if (p >= end) break;
+
+        c = *p++;
+
+        if (c == 0) {
+            packet->cursor = p;
+            return bp;
+        }
+
+        /* we don't expect label compression in QNAMEs */
+        if (c >= 64) break;
+
+        if (first)
+            first = 0;
+        else
+            bp = bprint_c(bp, bend, '.');
+
+        bp = bprint_b(bp, bend, (const char*) p, c);
+
+        p += c;
+        /* we rely on the bound check at the start
+         * of the loop here */
+    }
+    /* malformed data */
+    bp = bprint_s(bp, bend, "<MALFORMED>");
+    return bp;
+}
+
+static char* dnsPacket_bprintQR(DnsPacket* packet, char* p, char* end) {
+#define QQ(x) \
+    { DNS_TYPE_##x, #x }
+    static const struct {
+        const char* typeBytes;
+        const char* typeString;
+    } qTypes[] = {QQ(A), QQ(PTR), QQ(MX), QQ(AAAA), QQ(ALL), {NULL, NULL}};
+    int nn;
+    const char* typeString = NULL;
+
+    /* dump QNAME */
+    p = dnsPacket_bprintQName(packet, p, end);
+
+    /* dump TYPE */
+    p = bprint_s(p, end, " (");
+
+    for (nn = 0; qTypes[nn].typeBytes != NULL; nn++) {
+        if (_dnsPacket_checkBytes(packet, 2, qTypes[nn].typeBytes)) {
+            typeString = qTypes[nn].typeString;
+            break;
+        }
+    }
+
+    if (typeString != NULL)
+        p = bprint_s(p, end, typeString);
+    else {
+        int typeCode = _dnsPacket_readInt16(packet);
+        p = bprint(p, end, "UNKNOWN-%d", typeCode);
+    }
+
+    p = bprint_c(p, end, ')');
+
+    /* skip CLASS */
+    _dnsPacket_skip(packet, 2);
+    return p;
+}
+
+/* this function assumes the packet has already been checked */
+static char* dnsPacket_bprintQuery(DnsPacket* packet, char* p, char* end) {
+    int qdCount;
+
+    if (packet->base[2] & 0x1) {
+        p = bprint_s(p, end, "RECURSIVE ");
+    }
+
+    _dnsPacket_skip(packet, 4);
+    qdCount = _dnsPacket_readInt16(packet);
+    _dnsPacket_skip(packet, 6);
+
+    for (; qdCount > 0; qdCount--) {
+        p = dnsPacket_bprintQR(packet, p, end);
+    }
+    return p;
+}
+
+/** QUERY HASHING SUPPORT
+ **
+ ** THE FOLLOWING CODE ASSUMES THAT THE INPUT PACKET HAS ALREADY
+ ** BEEN SUCCESFULLY CHECKED.
+ **/
+
+/* use 32-bit FNV hash function */
+#define FNV_MULT 16777619U
+#define FNV_BASIS 2166136261U
+
+static unsigned _dnsPacket_hashBytes(DnsPacket* packet, int numBytes, unsigned hash) {
+    const uint8_t* p = packet->cursor;
+    const uint8_t* end = packet->end;
+
+    while (numBytes > 0 && p < end) {
+        hash = hash * FNV_MULT ^ *p++;
+    }
+    packet->cursor = p;
+    return hash;
+}
+
+static unsigned _dnsPacket_hashQName(DnsPacket* packet, unsigned hash) {
+    const uint8_t* p = packet->cursor;
+    const uint8_t* end = packet->end;
+
+    for (;;) {
+        int c;
+
+        if (p >= end) { /* should not happen */
+            LOG(INFO) << __func__ << ": INTERNAL_ERROR: read-overflow";
+            break;
+        }
+
+        c = *p++;
+
+        if (c == 0) break;
+
+        if (c >= 64) {
+            LOG(INFO) << __func__ << ": INTERNAL_ERROR: malformed domain";
+            break;
+        }
+        if (p + c >= end) {
+            LOG(INFO) << __func__ << ": INTERNAL_ERROR: simple label read-overflow";
+            break;
+        }
+        while (c > 0) {
+            hash = hash * FNV_MULT ^ *p++;
+            c -= 1;
+        }
+    }
+    packet->cursor = p;
+    return hash;
+}
+
+static unsigned _dnsPacket_hashQR(DnsPacket* packet, unsigned hash) {
+    hash = _dnsPacket_hashQName(packet, hash);
+    hash = _dnsPacket_hashBytes(packet, 4, hash); /* TYPE and CLASS */
+    return hash;
+}
+
+static unsigned _dnsPacket_hashRR(DnsPacket* packet, unsigned hash) {
+    int rdlength;
+    hash = _dnsPacket_hashQR(packet, hash);
+    hash = _dnsPacket_hashBytes(packet, 4, hash); /* TTL */
+    rdlength = _dnsPacket_readInt16(packet);
+    hash = _dnsPacket_hashBytes(packet, rdlength, hash); /* RDATA */
+    return hash;
+}
+
+static unsigned _dnsPacket_hashQuery(DnsPacket* packet) {
+    unsigned hash = FNV_BASIS;
+    int count, arcount;
+    _dnsPacket_rewind(packet);
+
+    /* ignore the ID */
+    _dnsPacket_skip(packet, 2);
+
+    /* we ignore the TC bit for reasons explained in
+     * _dnsPacket_checkQuery().
+     *
+     * however we hash the RD bit to differentiate
+     * between answers for recursive and non-recursive
+     * queries.
+     */
+    hash = hash * FNV_MULT ^ (packet->base[2] & 1);
+
+    /* mark the first header byte as processed */
+    _dnsPacket_skip(packet, 1);
+
+    /* process the second header byte */
+    hash = _dnsPacket_hashBytes(packet, 1, hash);
+
+    /* read QDCOUNT */
+    count = _dnsPacket_readInt16(packet);
+
+    /* assume: ANcount and NScount are 0 */
+    _dnsPacket_skip(packet, 4);
+
+    /* read ARCOUNT */
+    arcount = _dnsPacket_readInt16(packet);
+
+    /* hash QDCOUNT QRs */
+    for (; count > 0; count--) hash = _dnsPacket_hashQR(packet, hash);
+
+    /* hash ARCOUNT RRs */
+    for (; arcount > 0; arcount--) hash = _dnsPacket_hashRR(packet, hash);
+
+    return hash;
+}
+
+/** QUERY COMPARISON
+ **
+ ** THE FOLLOWING CODE ASSUMES THAT THE INPUT PACKETS HAVE ALREADY
+ ** BEEN SUCCESSFULLY CHECKED.
+ **/
+
+static int _dnsPacket_isEqualDomainName(DnsPacket* pack1, DnsPacket* pack2) {
+    const uint8_t* p1 = pack1->cursor;
+    const uint8_t* end1 = pack1->end;
+    const uint8_t* p2 = pack2->cursor;
+    const uint8_t* end2 = pack2->end;
+
+    for (;;) {
+        int c1, c2;
+
+        if (p1 >= end1 || p2 >= end2) {
+            LOG(INFO) << __func__ << ": INTERNAL_ERROR: read-overflow";
+            break;
+        }
+        c1 = *p1++;
+        c2 = *p2++;
+        if (c1 != c2) break;
+
+        if (c1 == 0) {
+            pack1->cursor = p1;
+            pack2->cursor = p2;
+            return 1;
+        }
+        if (c1 >= 64) {
+            LOG(INFO) << __func__ << ": INTERNAL_ERROR: malformed domain";
+            break;
+        }
+        if ((p1 + c1 > end1) || (p2 + c1 > end2)) {
+            LOG(INFO) << __func__ << ": INTERNAL_ERROR: simple label read-overflow";
+            break;
+        }
+        if (memcmp(p1, p2, c1) != 0) break;
+        p1 += c1;
+        p2 += c1;
+        /* we rely on the bound checks at the start of the loop */
+    }
+    /* not the same, or one is malformed */
+    LOG(INFO) << __func__ << ": different DN";
+    return 0;
+}
+
+static int _dnsPacket_isEqualBytes(DnsPacket* pack1, DnsPacket* pack2, int numBytes) {
+    const uint8_t* p1 = pack1->cursor;
+    const uint8_t* p2 = pack2->cursor;
+
+    if (p1 + numBytes > pack1->end || p2 + numBytes > pack2->end) return 0;
+
+    if (memcmp(p1, p2, numBytes) != 0) return 0;
+
+    pack1->cursor += numBytes;
+    pack2->cursor += numBytes;
+    return 1;
+}
+
+static int _dnsPacket_isEqualQR(DnsPacket* pack1, DnsPacket* pack2) {
+    /* compare domain name encoding + TYPE + CLASS */
+    if (!_dnsPacket_isEqualDomainName(pack1, pack2) ||
+        !_dnsPacket_isEqualBytes(pack1, pack2, 2 + 2))
+        return 0;
+
+    return 1;
+}
+
+static int _dnsPacket_isEqualRR(DnsPacket* pack1, DnsPacket* pack2) {
+    int rdlength1, rdlength2;
+    /* compare query + TTL */
+    if (!_dnsPacket_isEqualQR(pack1, pack2) || !_dnsPacket_isEqualBytes(pack1, pack2, 4)) return 0;
+
+    /* compare RDATA */
+    rdlength1 = _dnsPacket_readInt16(pack1);
+    rdlength2 = _dnsPacket_readInt16(pack2);
+    if (rdlength1 != rdlength2 || !_dnsPacket_isEqualBytes(pack1, pack2, rdlength1)) return 0;
+
+    return 1;
+}
+
+static int _dnsPacket_isEqualQuery(DnsPacket* pack1, DnsPacket* pack2) {
+    int count1, count2, arcount1, arcount2;
+
+    /* compare the headers, ignore most fields */
+    _dnsPacket_rewind(pack1);
+    _dnsPacket_rewind(pack2);
+
+    /* compare RD, ignore TC, see comment in _dnsPacket_checkQuery */
+    if ((pack1->base[2] & 1) != (pack2->base[2] & 1)) {
+        LOG(INFO) << __func__ << ": different RD";
+        return 0;
+    }
+
+    if (pack1->base[3] != pack2->base[3]) {
+        LOG(INFO) << __func__ << ": different CD or AD";
+        return 0;
+    }
+
+    /* mark ID and header bytes as compared */
+    _dnsPacket_skip(pack1, 4);
+    _dnsPacket_skip(pack2, 4);
+
+    /* compare QDCOUNT */
+    count1 = _dnsPacket_readInt16(pack1);
+    count2 = _dnsPacket_readInt16(pack2);
+    if (count1 != count2 || count1 < 0) {
+        LOG(INFO) << __func__ << ": different QDCOUNT";
+        return 0;
+    }
+
+    /* assume: ANcount and NScount are 0 */
+    _dnsPacket_skip(pack1, 4);
+    _dnsPacket_skip(pack2, 4);
+
+    /* compare ARCOUNT */
+    arcount1 = _dnsPacket_readInt16(pack1);
+    arcount2 = _dnsPacket_readInt16(pack2);
+    if (arcount1 != arcount2 || arcount1 < 0) {
+        LOG(INFO) << __func__ << ": different ARCOUNT";
+        return 0;
+    }
+
+    /* compare the QDCOUNT QRs */
+    for (; count1 > 0; count1--) {
+        if (!_dnsPacket_isEqualQR(pack1, pack2)) {
+            LOG(INFO) << __func__ << ": different QR";
+            return 0;
+        }
+    }
+
+    /* compare the ARCOUNT RRs */
+    for (; arcount1 > 0; arcount1--) {
+        if (!_dnsPacket_isEqualRR(pack1, pack2)) {
+            LOG(INFO) << __func__ << ": different additional RR";
+            return 0;
+        }
+    }
+    return 1;
+}
+
+/* cache entry. for simplicity, 'hash' and 'hlink' are inlined in this
+ * structure though they are conceptually part of the hash table.
+ *
+ * similarly, mru_next and mru_prev are part of the global MRU list
+ */
+typedef struct Entry {
+    unsigned int hash;   /* hash value */
+    struct Entry* hlink; /* next in collision chain */
+    struct Entry* mru_prev;
+    struct Entry* mru_next;
+
+    const uint8_t* query;
+    int querylen;
+    const uint8_t* answer;
+    int answerlen;
+    time_t expires; /* time_t when the entry isn't valid any more */
+    int id;         /* for debugging purpose */
+} Entry;
+
+/*
+ * Find the TTL for a negative DNS result.  This is defined as the minimum
+ * of the SOA records TTL and the MINIMUM-TTL field (RFC-2308).
+ *
+ * Return 0 if not found.
+ */
+static u_long answer_getNegativeTTL(ns_msg handle) {
+    int n, nscount;
+    u_long result = 0;
+    ns_rr rr;
+
+    nscount = ns_msg_count(handle, ns_s_ns);
+    for (n = 0; n < nscount; n++) {
+        if ((ns_parserr(&handle, ns_s_ns, n, &rr) == 0) && (ns_rr_type(rr) == ns_t_soa)) {
+            const u_char* rdata = ns_rr_rdata(rr);          // find the data
+            const u_char* edata = rdata + ns_rr_rdlen(rr);  // add the len to find the end
+            int len;
+            u_long ttl, rec_result = ns_rr_ttl(rr);
+
+            // find the MINIMUM-TTL field from the blob of binary data for this record
+            // skip the server name
+            len = dn_skipname(rdata, edata);
+            if (len == -1) continue;  // error skipping
+            rdata += len;
+
+            // skip the admin name
+            len = dn_skipname(rdata, edata);
+            if (len == -1) continue;  // error skipping
+            rdata += len;
+
+            if (edata - rdata != 5 * NS_INT32SZ) continue;
+            // skip: serial number + refresh interval + retry interval + expiry
+            rdata += NS_INT32SZ * 4;
+            // finally read the MINIMUM TTL
+            ttl = ntohl(*reinterpret_cast<const uint32_t*>(rdata));
+            if (ttl < rec_result) {
+                rec_result = ttl;
+            }
+            // Now that the record is read successfully, apply the new min TTL
+            if (n == 0 || rec_result < result) {
+                result = rec_result;
+            }
+        }
+    }
+    return result;
+}
+
+/*
+ * Parse the answer records and find the appropriate
+ * smallest TTL among the records.  This might be from
+ * the answer records if found or from the SOA record
+ * if it's a negative result.
+ *
+ * The returned TTL is the number of seconds to
+ * keep the answer in the cache.
+ *
+ * In case of parse error zero (0) is returned which
+ * indicates that the answer shall not be cached.
+ */
+static u_long answer_getTTL(const void* answer, int answerlen) {
+    ns_msg handle;
+    int ancount, n;
+    u_long result, ttl;
+    ns_rr rr;
+
+    result = 0;
+    if (ns_initparse((const uint8_t*) answer, answerlen, &handle) >= 0) {
+        // get number of answer records
+        ancount = ns_msg_count(handle, ns_s_an);
+
+        if (ancount == 0) {
+            // a response with no answers?  Cache this negative result.
+            result = answer_getNegativeTTL(handle);
+        } else {
+            for (n = 0; n < ancount; n++) {
+                if (ns_parserr(&handle, ns_s_an, n, &rr) == 0) {
+                    ttl = ns_rr_ttl(rr);
+                    if (n == 0 || ttl < result) {
+                        result = ttl;
+                    }
+                } else {
+                    PLOG(INFO) << __func__ << ": ns_parserr failed ancount no = " << n;
+                }
+            }
+        }
+    } else {
+        PLOG(INFO) << __func__ << ": ns_initparse failed";
+    }
+
+    LOG(INFO) << __func__ << ": TTL = " << result;
+    return result;
+}
+
+static void entry_free(Entry* e) {
+    /* everything is allocated in a single memory block */
+    if (e) {
+        free(e);
+    }
+}
+
+static void entry_mru_remove(Entry* e) {
+    e->mru_prev->mru_next = e->mru_next;
+    e->mru_next->mru_prev = e->mru_prev;
+}
+
+static void entry_mru_add(Entry* e, Entry* list) {
+    Entry* first = list->mru_next;
+
+    e->mru_next = first;
+    e->mru_prev = list;
+
+    list->mru_next = e;
+    first->mru_prev = e;
+}
+
+/* compute the hash of a given entry, this is a hash of most
+ * data in the query (key) */
+static unsigned entry_hash(const Entry* e) {
+    DnsPacket pack[1];
+
+    _dnsPacket_init(pack, e->query, e->querylen);
+    return _dnsPacket_hashQuery(pack);
+}
+
+/* initialize an Entry as a search key, this also checks the input query packet
+ * returns 1 on success, or 0 in case of unsupported/malformed data */
+static int entry_init_key(Entry* e, const void* query, int querylen) {
+    DnsPacket pack[1];
+
+    memset(e, 0, sizeof(*e));
+
+    e->query = (const uint8_t*) query;
+    e->querylen = querylen;
+    e->hash = entry_hash(e);
+
+    _dnsPacket_init(pack, e->query, e->querylen);
+
+    return _dnsPacket_checkQuery(pack);
+}
+
+/* allocate a new entry as a cache node */
+static Entry* entry_alloc(const Entry* init, const void* answer, int answerlen) {
+    Entry* e;
+    int size;
+
+    size = sizeof(*e) + init->querylen + answerlen;
+    e = (Entry*) calloc(size, 1);
+    if (e == NULL) return e;
+
+    e->hash = init->hash;
+    e->query = (const uint8_t*) (e + 1);
+    e->querylen = init->querylen;
+
+    memcpy((char*) e->query, init->query, e->querylen);
+
+    e->answer = e->query + e->querylen;
+    e->answerlen = answerlen;
+
+    memcpy((char*) e->answer, answer, e->answerlen);
+
+    return e;
+}
+
+static int entry_equals(const Entry* e1, const Entry* e2) {
+    DnsPacket pack1[1], pack2[1];
+
+    if (e1->querylen != e2->querylen) {
+        return 0;
+    }
+    _dnsPacket_init(pack1, e1->query, e1->querylen);
+    _dnsPacket_init(pack2, e2->query, e2->querylen);
+
+    return _dnsPacket_isEqualQuery(pack1, pack2);
+}
+
+/* We use a simple hash table with external collision lists
+ * for simplicity, the hash-table fields 'hash' and 'hlink' are
+ * inlined in the Entry structure.
+ */
+
+/* Maximum time for a thread to wait for an pending request */
+constexpr int PENDING_REQUEST_TIMEOUT = 20;
+
+typedef struct resolv_cache {
+    int max_entries;
+    int num_entries;
+    Entry mru_list;
+    int last_id;
+    Entry* entries;
+    struct pending_req_info {
+        unsigned int hash;
+        struct pending_req_info* next;
+    } pending_requests;
+} Cache;
+
+struct resolv_cache_info {
+    unsigned netid;
+    Cache* cache;
+    struct resolv_cache_info* next;
+    int nscount;
+    char* nameservers[MAXNS];
+    struct addrinfo* nsaddrinfo[MAXNS];
+    int revision_id;  // # times the nameservers have been replaced
+    res_params params;
+    struct res_stats nsstats[MAXNS];
+    char defdname[MAXDNSRCHPATH];
+    int dnsrch_offset[MAXDNSRCH + 1];  // offsets into defdname
+    int wait_for_pending_req_timeout_count;
+};
+
+// A helper class for the Clang Thread Safety Analysis to deal with
+// std::unique_lock.
+class SCOPED_CAPABILITY ScopedAssumeLocked {
+  public:
+    ScopedAssumeLocked(std::mutex& mutex) ACQUIRE(mutex) {}
+    ~ScopedAssumeLocked() RELEASE() {}
+};
+
+// lock protecting everything in the resolve_cache_info structs (next ptr, etc)
+static std::mutex cache_mutex;
+static std::condition_variable cv;
+
+/* gets cache associated with a network, or NULL if none exists */
+static resolv_cache* find_named_cache_locked(unsigned netid) REQUIRES(cache_mutex);
+static int resolv_create_cache_for_net_locked(unsigned netid) REQUIRES(cache_mutex);
+
+static void cache_flush_pending_requests_locked(struct resolv_cache* cache) {
+    resolv_cache::pending_req_info *ri, *tmp;
+    if (!cache) return;
+
+    ri = cache->pending_requests.next;
+
+    while (ri) {
+        tmp = ri;
+        ri = ri->next;
+        free(tmp);
+    }
+
+    cache->pending_requests.next = NULL;
+    cv.notify_all();
+}
+
+// Return true - if there is a pending request in |cache| matching |key|.
+// Return false - if no pending request is found matching the key. Optionally
+//                link a new one if parameter append_if_not_found is true.
+static bool cache_has_pending_request_locked(resolv_cache* cache, const Entry* key,
+                                             bool append_if_not_found) {
+    if (!cache || !key) return false;
+
+    resolv_cache::pending_req_info* ri = cache->pending_requests.next;
+    resolv_cache::pending_req_info* prev = &cache->pending_requests;
+    while (ri) {
+        if (ri->hash == key->hash) {
+            return true;
+        }
+        prev = ri;
+        ri = ri->next;
+    }
+
+    if (append_if_not_found) {
+        ri = (resolv_cache::pending_req_info*)calloc(1, sizeof(resolv_cache::pending_req_info));
+        if (ri) {
+            ri->hash = key->hash;
+            prev->next = ri;
+        }
+    }
+    return false;
+}
+
+// Notify all threads that the cache entry |key| has become available
+static void _cache_notify_waiting_tid_locked(struct resolv_cache* cache, const Entry* key) {
+    if (!cache || !key) return;
+
+    resolv_cache::pending_req_info* ri = cache->pending_requests.next;
+    resolv_cache::pending_req_info* prev = &cache->pending_requests;
+    while (ri) {
+        if (ri->hash == key->hash) {
+            // remove item from list and destroy
+            prev->next = ri->next;
+            free(ri);
+            cv.notify_all();
+            return;
+        }
+        prev = ri;
+        ri = ri->next;
+    }
+}
+
+void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen, uint32_t flags) {
+    // We should not notify with these flags.
+    if (flags & (ANDROID_RESOLV_NO_CACHE_STORE | ANDROID_RESOLV_NO_CACHE_LOOKUP)) {
+        return;
+    }
+    Entry key[1];
+    Cache* cache;
+
+    if (!entry_init_key(key, query, querylen)) return;
+
+    std::lock_guard guard(cache_mutex);
+
+    cache = find_named_cache_locked(netid);
+
+    if (cache) {
+        _cache_notify_waiting_tid_locked(cache, key);
+    }
+}
+
+static void cache_flush_locked(Cache* cache) {
+    int nn;
+
+    for (nn = 0; nn < cache->max_entries; nn++) {
+        Entry** pnode = (Entry**) &cache->entries[nn];
+
+        while (*pnode != NULL) {
+            Entry* node = *pnode;
+            *pnode = node->hlink;
+            entry_free(node);
+        }
+    }
+
+    // flush pending request
+    cache_flush_pending_requests_locked(cache);
+
+    cache->mru_list.mru_next = cache->mru_list.mru_prev = &cache->mru_list;
+    cache->num_entries = 0;
+    cache->last_id = 0;
+
+    LOG(INFO) << __func__ << ": *** DNS CACHE FLUSHED ***";
+}
+
+static resolv_cache* resolv_cache_create() {
+    struct resolv_cache* cache;
+
+    cache = (struct resolv_cache*) calloc(sizeof(*cache), 1);
+    if (cache) {
+        cache->max_entries = CONFIG_MAX_ENTRIES;
+        cache->entries = (Entry*) calloc(sizeof(*cache->entries), cache->max_entries);
+        if (cache->entries) {
+            cache->mru_list.mru_prev = cache->mru_list.mru_next = &cache->mru_list;
+            LOG(INFO) << __func__ << ": cache created";
+        } else {
+            free(cache);
+            cache = NULL;
+        }
+    }
+    return cache;
+}
+
+static void dump_query(const uint8_t* query, int querylen) {
+    if (!WOULD_LOG(VERBOSE)) return;
+
+    char temp[256], *p = temp, *end = p + sizeof(temp);
+    DnsPacket pack[1];
+
+    _dnsPacket_init(pack, query, querylen);
+    p = dnsPacket_bprintQuery(pack, p, end);
+    LOG(VERBOSE) << __func__ << ": " << temp;
+}
+
+static void cache_dump_mru(Cache* cache) {
+    char temp[512], *p = temp, *end = p + sizeof(temp);
+    Entry* e;
+
+    p = bprint(temp, end, "MRU LIST (%2d): ", cache->num_entries);
+    for (e = cache->mru_list.mru_next; e != &cache->mru_list; e = e->mru_next)
+        p = bprint(p, end, " %d", e->id);
+
+    LOG(INFO) << __func__ << ": " << temp;
+}
+
+/* This function tries to find a key within the hash table
+ * In case of success, it will return a *pointer* to the hashed key.
+ * In case of failure, it will return a *pointer* to NULL
+ *
+ * So, the caller must check '*result' to check for success/failure.
+ *
+ * The main idea is that the result can later be used directly in
+ * calls to _resolv_cache_add or _resolv_cache_remove as the 'lookup'
+ * parameter. This makes the code simpler and avoids re-searching
+ * for the key position in the htable.
+ *
+ * The result of a lookup_p is only valid until you alter the hash
+ * table.
+ */
+static Entry** _cache_lookup_p(Cache* cache, Entry* key) {
+    int index = key->hash % cache->max_entries;
+    Entry** pnode = (Entry**) &cache->entries[index];
+
+    while (*pnode != NULL) {
+        Entry* node = *pnode;
+
+        if (node == NULL) break;
+
+        if (node->hash == key->hash && entry_equals(node, key)) break;
+
+        pnode = &node->hlink;
+    }
+    return pnode;
+}
+
+/* Add a new entry to the hash table. 'lookup' must be the
+ * result of an immediate previous failed _lookup_p() call
+ * (i.e. with *lookup == NULL), and 'e' is the pointer to the
+ * newly created entry
+ */
+static void _cache_add_p(Cache* cache, Entry** lookup, Entry* e) {
+    *lookup = e;
+    e->id = ++cache->last_id;
+    entry_mru_add(e, &cache->mru_list);
+    cache->num_entries += 1;
+
+    LOG(INFO) << __func__ << ": entry " << e->id << " added (count=" << cache->num_entries << ")";
+}
+
+/* Remove an existing entry from the hash table,
+ * 'lookup' must be the result of an immediate previous
+ * and succesful _lookup_p() call.
+ */
+static void _cache_remove_p(Cache* cache, Entry** lookup) {
+    Entry* e = *lookup;
+
+    LOG(INFO) << __func__ << ": entry " << e->id << " removed (count=" << cache->num_entries - 1
+              << ")";
+
+    entry_mru_remove(e);
+    *lookup = e->hlink;
+    entry_free(e);
+    cache->num_entries -= 1;
+}
+
+/* Remove the oldest entry from the hash table.
+ */
+static void _cache_remove_oldest(Cache* cache) {
+    Entry* oldest = cache->mru_list.mru_prev;
+    Entry** lookup = _cache_lookup_p(cache, oldest);
+
+    if (*lookup == NULL) { /* should not happen */
+        LOG(INFO) << __func__ << ": OLDEST NOT IN HTABLE ?";
+        return;
+    }
+    LOG(INFO) << __func__ << ": Cache full - removing oldest";
+    dump_query(oldest->query, oldest->querylen);
+    _cache_remove_p(cache, lookup);
+}
+
+/* Remove all expired entries from the hash table.
+ */
+static void _cache_remove_expired(Cache* cache) {
+    Entry* e;
+    time_t now = _time_now();
+
+    for (e = cache->mru_list.mru_next; e != &cache->mru_list;) {
+        // Entry is old, remove
+        if (now >= e->expires) {
+            Entry** lookup = _cache_lookup_p(cache, e);
+            if (*lookup == NULL) { /* should not happen */
+                LOG(INFO) << __func__ << ": ENTRY NOT IN HTABLE ?";
+                return;
+            }
+            e = e->mru_next;
+            _cache_remove_p(cache, lookup);
+        } else {
+            e = e->mru_next;
+        }
+    }
+}
+
+// gets a resolv_cache_info associated with a network, or NULL if not found
+static resolv_cache_info* find_cache_info_locked(unsigned netid) REQUIRES(cache_mutex);
+
+ResolvCacheStatus _resolv_cache_lookup(unsigned netid, const void* query, int querylen,
+                                       void* answer, int answersize, int* answerlen,
+                                       uint32_t flags) {
+    // Skip cache lookup, return RESOLV_CACHE_NOTFOUND directly so that it is
+    // possible to cache the answer of this query.
+    // If ANDROID_RESOLV_NO_CACHE_STORE is set, return RESOLV_CACHE_SKIP to skip possible cache
+    // storing.
+    if (flags & ANDROID_RESOLV_NO_CACHE_LOOKUP) {
+        return flags & ANDROID_RESOLV_NO_CACHE_STORE ? RESOLV_CACHE_SKIP : RESOLV_CACHE_NOTFOUND;
+    }
+    Entry key;
+    Entry** lookup;
+    Entry* e;
+    time_t now;
+    Cache* cache;
+
+    LOG(INFO) << __func__ << ": lookup";
+    dump_query((u_char*) query, querylen);
+
+    /* we don't cache malformed queries */
+    if (!entry_init_key(&key, query, querylen)) {
+        LOG(INFO) << __func__ << ": unsupported query";
+        return RESOLV_CACHE_UNSUPPORTED;
+    }
+    /* lookup cache */
+    std::unique_lock lock(cache_mutex);
+    ScopedAssumeLocked assume_lock(cache_mutex);
+    cache = find_named_cache_locked(netid);
+    if (cache == NULL) {
+        return RESOLV_CACHE_UNSUPPORTED;
+    }
+
+    /* see the description of _lookup_p to understand this.
+     * the function always return a non-NULL pointer.
+     */
+    lookup = _cache_lookup_p(cache, &key);
+    e = *lookup;
+
+    if (e == NULL) {
+        LOG(INFO) << __func__ << ": NOT IN CACHE";
+        // If it is no-cache-store mode, we won't wait for possible query.
+        if (flags & ANDROID_RESOLV_NO_CACHE_STORE) {
+            return RESOLV_CACHE_SKIP;
+        }
+
+        if (!cache_has_pending_request_locked(cache, &key, true)) {
+            return RESOLV_CACHE_NOTFOUND;
+
+        } else {
+            LOG(INFO) << __func__ << ": Waiting for previous request";
+            // wait until (1) timeout OR
+            //            (2) cv is notified AND no pending request matching the |key|
+            // (cv notifier should delete pending request before sending notification.)
+            bool ret = cv.wait_for(lock, std::chrono::seconds(PENDING_REQUEST_TIMEOUT),
+                                   [netid, &cache, &key]() REQUIRES(cache_mutex) {
+                                       // Must update cache as it could have been deleted
+                                       cache = find_named_cache_locked(netid);
+                                       return !cache_has_pending_request_locked(cache, &key, false);
+                                   });
+            if (!cache) {
+                return RESOLV_CACHE_NOTFOUND;
+            }
+            if (ret == false) {
+                resolv_cache_info* info = find_cache_info_locked(netid);
+                if (info != NULL) {
+                    info->wait_for_pending_req_timeout_count++;
+                }
+            }
+            lookup = _cache_lookup_p(cache, &key);
+            e = *lookup;
+            if (e == NULL) {
+                return RESOLV_CACHE_NOTFOUND;
+            }
+        }
+    }
+
+    now = _time_now();
+
+    /* remove stale entries here */
+    if (now >= e->expires) {
+        LOG(INFO) << __func__ << ": NOT IN CACHE (STALE ENTRY " << *lookup << "DISCARDED)";
+        dump_query(e->query, e->querylen);
+        _cache_remove_p(cache, lookup);
+        return RESOLV_CACHE_NOTFOUND;
+    }
+
+    *answerlen = e->answerlen;
+    if (e->answerlen > answersize) {
+        /* NOTE: we return UNSUPPORTED if the answer buffer is too short */
+        LOG(INFO) << __func__ << ": ANSWER TOO LONG";
+        return RESOLV_CACHE_UNSUPPORTED;
+    }
+
+    memcpy(answer, e->answer, e->answerlen);
+
+    /* bump up this entry to the top of the MRU list */
+    if (e != cache->mru_list.mru_next) {
+        entry_mru_remove(e);
+        entry_mru_add(e, &cache->mru_list);
+    }
+
+    LOG(INFO) << __func__ << ": FOUND IN CACHE entry=" << e;
+    return RESOLV_CACHE_FOUND;
+}
+
+void _resolv_cache_add(unsigned netid, const void* query, int querylen, const void* answer,
+                       int answerlen) {
+    Entry key[1];
+    Entry* e;
+    Entry** lookup;
+    u_long ttl;
+    Cache* cache = NULL;
+
+    /* don't assume that the query has already been cached
+     */
+    if (!entry_init_key(key, query, querylen)) {
+        LOG(INFO) << __func__ << ": passed invalid query?";
+        return;
+    }
+
+    std::lock_guard guard(cache_mutex);
+
+    cache = find_named_cache_locked(netid);
+    if (cache == NULL) {
+        return;
+    }
+
+    LOG(INFO) << __func__ << ": query:";
+    dump_query((u_char*)query, querylen);
+    res_pquery((u_char*)answer, answerlen);
+    if (kDumpData) {
+        LOG(INFO) << __func__ << ": answer:";
+        dump_bytes((u_char*)answer, answerlen);
+    }
+
+    lookup = _cache_lookup_p(cache, key);
+    e = *lookup;
+
+    // Should only happen on ANDROID_RESOLV_NO_CACHE_LOOKUP
+    if (e != NULL) {
+        LOG(INFO) << __func__ << ": ALREADY IN CACHE (" << e << ") ? IGNORING ADD";
+        _cache_notify_waiting_tid_locked(cache, key);
+        return;
+    }
+
+    if (cache->num_entries >= cache->max_entries) {
+        _cache_remove_expired(cache);
+        if (cache->num_entries >= cache->max_entries) {
+            _cache_remove_oldest(cache);
+        }
+        // TODO: It looks useless, remove below code after having test to prove it.
+        lookup = _cache_lookup_p(cache, key);
+        e = *lookup;
+        if (e != NULL) {
+            LOG(INFO) << __func__ << ": ALREADY IN CACHE (" << e << ") ? IGNORING ADD";
+            _cache_notify_waiting_tid_locked(cache, key);
+            return;
+        }
+    }
+
+    ttl = answer_getTTL(answer, answerlen);
+    if (ttl > 0) {
+        e = entry_alloc(key, answer, answerlen);
+        if (e != NULL) {
+            e->expires = ttl + _time_now();
+            _cache_add_p(cache, lookup, e);
+        }
+    }
+
+    cache_dump_mru(cache);
+    _cache_notify_waiting_tid_locked(cache, key);
+}
+
+// Head of the list of caches.
+static struct resolv_cache_info res_cache_list GUARDED_BY(cache_mutex);
+
+// insert resolv_cache_info into the list of resolv_cache_infos
+static void insert_cache_info_locked(resolv_cache_info* cache_info);
+// creates a resolv_cache_info
+static resolv_cache_info* create_cache_info();
+// empty the nameservers set for the named cache
+static void free_nameservers_locked(resolv_cache_info* cache_info);
+// return 1 if the provided list of name servers differs from the list of name servers
+// currently attached to the provided cache_info
+static int resolv_is_nameservers_equal_locked(resolv_cache_info* cache_info, const char** servers,
+                                              int numservers);
+// clears the stats samples contained withing the given cache_info
+static void res_cache_clear_stats_locked(resolv_cache_info* cache_info);
+
+// public API for netd to query if name server is set on specific netid
+bool resolv_has_nameservers(unsigned netid) {
+    std::lock_guard guard(cache_mutex);
+    resolv_cache_info* info = find_cache_info_locked(netid);
+    return (info != nullptr) && (info->nscount > 0);
+}
+
+static int resolv_create_cache_for_net_locked(unsigned netid) {
+    resolv_cache* cache = find_named_cache_locked(netid);
+    // Should not happen
+    if (cache) {
+        LOG(ERROR) << __func__ << ": Cache is already created, netId: " << netid;
+        return -EEXIST;
+    }
+
+    resolv_cache_info* cache_info = create_cache_info();
+    if (!cache_info) return -ENOMEM;
+    cache = resolv_cache_create();
+    if (!cache) {
+        free(cache_info);
+        return -ENOMEM;
+    }
+    cache_info->cache = cache;
+    cache_info->netid = netid;
+    insert_cache_info_locked(cache_info);
+
+    return 0;
+}
+
+int resolv_create_cache_for_net(unsigned netid) {
+    std::lock_guard guard(cache_mutex);
+    return resolv_create_cache_for_net_locked(netid);
+}
+
+void resolv_delete_cache_for_net(unsigned netid) {
+    std::lock_guard guard(cache_mutex);
+
+    struct resolv_cache_info* prev_cache_info = &res_cache_list;
+
+    while (prev_cache_info->next) {
+        struct resolv_cache_info* cache_info = prev_cache_info->next;
+
+        if (cache_info->netid == netid) {
+            prev_cache_info->next = cache_info->next;
+            cache_flush_locked(cache_info->cache);
+            free(cache_info->cache->entries);
+            free(cache_info->cache);
+            free_nameservers_locked(cache_info);
+            free(cache_info);
+            break;
+        }
+
+        prev_cache_info = prev_cache_info->next;
+    }
+}
+
+std::vector<unsigned> resolv_list_caches() {
+    std::lock_guard guard(cache_mutex);
+    struct resolv_cache_info* cache_info = res_cache_list.next;
+    std::vector<unsigned> result;
+    while (cache_info) {
+        result.push_back(cache_info->netid);
+        cache_info = cache_info->next;
+    }
+    return result;
+}
+
+static resolv_cache_info* create_cache_info() {
+    return (struct resolv_cache_info*) calloc(sizeof(struct resolv_cache_info), 1);
+}
+
+// TODO: convert this to a simple and efficient C++ container.
+static void insert_cache_info_locked(struct resolv_cache_info* cache_info) {
+    struct resolv_cache_info* last;
+    for (last = &res_cache_list; last->next; last = last->next) {}
+    last->next = cache_info;
+}
+
+static resolv_cache* find_named_cache_locked(unsigned netid) {
+    resolv_cache_info* info = find_cache_info_locked(netid);
+    if (info != NULL) return info->cache;
+    return NULL;
+}
+
+static resolv_cache_info* find_cache_info_locked(unsigned netid) {
+    struct resolv_cache_info* cache_info = res_cache_list.next;
+
+    while (cache_info) {
+        if (cache_info->netid == netid) {
+            break;
+        }
+
+        cache_info = cache_info->next;
+    }
+    return cache_info;
+}
+
+static void resolv_set_default_params(res_params* params) {
+    params->sample_validity = NSSAMPLE_VALIDITY;
+    params->success_threshold = SUCCESS_THRESHOLD;
+    params->min_samples = 0;
+    params->max_samples = 0;
+    params->base_timeout_msec = 0;  // 0 = legacy algorithm
+    params->retry_count = 0;
+}
+
+static void resolv_set_experiment_params(res_params* params) {
+    using android::base::ParseInt;
+    using server_configurable_flags::GetServerConfigurableFlag;
+
+    if (params->retry_count == 0) {
+        params->retry_count = RES_DFLRETRY;
+        ParseInt(GetServerConfigurableFlag("netd_native", "retry_count", ""), &params->retry_count);
+    }
+
+    if (params->base_timeout_msec == 0) {
+        params->base_timeout_msec = RES_TIMEOUT;
+        ParseInt(GetServerConfigurableFlag("netd_native", "retransmission_time_interval", ""),
+                 &params->base_timeout_msec);
+    }
+}
+
+int resolv_set_nameservers_for_net(unsigned netid, const char** servers, const int numservers,
+                                   const char* domains, const res_params* params) {
+    char* cp;
+    int* offset;
+    struct addrinfo* nsaddrinfo[MAXNS];
+
+    if (numservers > MAXNS) {
+        LOG(ERROR) << __func__ << ": numservers=" << numservers << ", MAXNS=" << MAXNS;
+        return E2BIG;
+    }
+
+    // Parse the addresses before actually locking or changing any state, in case there is an error.
+    // As a side effect this also reduces the time the lock is kept.
+    char sbuf[NI_MAXSERV];
+    snprintf(sbuf, sizeof(sbuf), "%u", NAMESERVER_PORT);
+    for (int i = 0; i < numservers; i++) {
+        // The addrinfo structures allocated here are freed in free_nameservers_locked().
+        const addrinfo hints = {
+                .ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_flags = AI_NUMERICHOST};
+        int rt = getaddrinfo_numeric(servers[i], sbuf, hints, &nsaddrinfo[i]);
+        if (rt != 0) {
+            for (int j = 0; j < i; j++) {
+                freeaddrinfo(nsaddrinfo[j]);
+            }
+            LOG(INFO) << __func__ << ": getaddrinfo_numeric(" << servers[i]
+                      << ") = " << gai_strerror(rt);
+            return EINVAL;
+        }
+    }
+
+    std::lock_guard guard(cache_mutex);
+
+    resolv_cache_info* cache_info = find_cache_info_locked(netid);
+
+    if (cache_info == NULL) return ENONET;
+
+    uint8_t old_max_samples = cache_info->params.max_samples;
+    if (params != NULL) {
+        cache_info->params = *params;
+    } else {
+        resolv_set_default_params(&cache_info->params);
+    }
+    resolv_set_experiment_params(&cache_info->params);
+    if (!resolv_is_nameservers_equal_locked(cache_info, servers, numservers)) {
+        // free current before adding new
+        free_nameservers_locked(cache_info);
+        for (int i = 0; i < numservers; i++) {
+            cache_info->nsaddrinfo[i] = nsaddrinfo[i];
+            cache_info->nameservers[i] = strdup(servers[i]);
+            LOG(INFO) << __func__ << ": netid = " << netid << ", addr = " << servers[i];
+        }
+        cache_info->nscount = numservers;
+
+        // Clear the NS statistics because the mapping to nameservers might have changed.
+        res_cache_clear_stats_locked(cache_info);
+
+        // increment the revision id to ensure that sample state is not written back if the
+        // servers change; in theory it would suffice to do so only if the servers or
+        // max_samples actually change, in practice the overhead of checking is higher than the
+        // cost, and overflows are unlikely
+        ++cache_info->revision_id;
+    } else {
+        if (cache_info->params.max_samples != old_max_samples) {
+            // If the maximum number of samples changes, the overhead of keeping the most recent
+            // samples around is not considered worth the effort, so they are cleared instead.
+            // All other parameters do not affect shared state: Changing these parameters does
+            // not invalidate the samples, as they only affect aggregation and the conditions
+            // under which servers are considered usable.
+            res_cache_clear_stats_locked(cache_info);
+            ++cache_info->revision_id;
+        }
+        for (int j = 0; j < numservers; j++) {
+            freeaddrinfo(nsaddrinfo[j]);
+        }
+    }
+
+    // Always update the search paths, since determining whether they actually changed is
+    // complex due to the zero-padding, and probably not worth the effort. Cache-flushing
+    // however is not necessary, since the stored cache entries do contain the domain, not
+    // just the host name.
+    strlcpy(cache_info->defdname, domains, sizeof(cache_info->defdname));
+    if ((cp = strchr(cache_info->defdname, '\n')) != NULL) *cp = '\0';
+    LOG(INFO) << __func__ << ": domains=\"" << cache_info->defdname << "\"";
+
+    cp = cache_info->defdname;
+    offset = cache_info->dnsrch_offset;
+    while (offset < cache_info->dnsrch_offset + MAXDNSRCH) {
+        while (*cp == ' ' || *cp == '\t') /* skip leading white space */
+            cp++;
+        if (*cp == '\0') /* stop if nothing more to do */
+            break;
+        *offset++ = cp - cache_info->defdname; /* record this search domain */
+        while (*cp) {                          /* zero-terminate it */
+            if (*cp == ' ' || *cp == '\t') {
+                *cp++ = '\0';
+                break;
+            }
+            cp++;
+        }
+    }
+    *offset = -1; /* cache_info->dnsrch_offset has MAXDNSRCH+1 items */
+
+    return 0;
+}
+
+static int resolv_is_nameservers_equal_locked(resolv_cache_info* cache_info, const char** servers,
+                                              int numservers) {
+    if (cache_info->nscount != numservers) {
+        return 0;
+    }
+
+    // Compare each name server against current name servers.
+    // TODO: this is incorrect if the list of current or previous nameservers
+    // contains duplicates. This does not really matter because the framework
+    // filters out duplicates, but we should probably fix it. It's also
+    // insensitive to the order of the nameservers; we should probably fix that
+    // too.
+    for (int i = 0; i < numservers; i++) {
+        for (int j = 0;; j++) {
+            if (j >= numservers) {
+                return 0;
+            }
+            if (strcmp(cache_info->nameservers[i], servers[j]) == 0) {
+                break;
+            }
+        }
+    }
+
+    return 1;
+}
+
+static void free_nameservers_locked(resolv_cache_info* cache_info) {
+    int i;
+    for (i = 0; i < cache_info->nscount; i++) {
+        free(cache_info->nameservers[i]);
+        cache_info->nameservers[i] = NULL;
+        if (cache_info->nsaddrinfo[i] != NULL) {
+            freeaddrinfo(cache_info->nsaddrinfo[i]);
+            cache_info->nsaddrinfo[i] = NULL;
+        }
+        cache_info->nsstats[i].sample_count = cache_info->nsstats[i].sample_next = 0;
+    }
+    cache_info->nscount = 0;
+    res_cache_clear_stats_locked(cache_info);
+    ++cache_info->revision_id;
+}
+
+void _resolv_populate_res_for_net(res_state statp) {
+    if (statp == NULL) {
+        return;
+    }
+    LOG(INFO) << __func__ << ": netid=" << statp->netid;
+
+    std::lock_guard guard(cache_mutex);
+    resolv_cache_info* info = find_cache_info_locked(statp->netid);
+    if (info != NULL) {
+        int nserv;
+        struct addrinfo* ai;
+        for (nserv = 0; nserv < MAXNS; nserv++) {
+            ai = info->nsaddrinfo[nserv];
+            if (ai == NULL) {
+                break;
+            }
+
+            if ((size_t) ai->ai_addrlen <= sizeof(statp->_u._ext.ext->nsaddrs[0])) {
+                if (statp->_u._ext.ext != NULL) {
+                    memcpy(&statp->_u._ext.ext->nsaddrs[nserv], ai->ai_addr, ai->ai_addrlen);
+                    statp->nsaddr_list[nserv].sin_family = AF_UNSPEC;
+                } else {
+                    if ((size_t) ai->ai_addrlen <= sizeof(statp->nsaddr_list[0])) {
+                        memcpy(&statp->nsaddr_list[nserv], ai->ai_addr, ai->ai_addrlen);
+                    } else {
+                        statp->nsaddr_list[nserv].sin_family = AF_UNSPEC;
+                    }
+                }
+            } else {
+                LOG(INFO) << __func__ << ": found too long addrlen";
+            }
+        }
+        statp->nscount = nserv;
+        // now do search domains.  Note that we cache the offsets as this code runs alot
+        // but the setting/offset-computer only runs when set/changed
+        // WARNING: Don't use str*cpy() here, this string contains zeroes.
+        memcpy(statp->defdname, info->defdname, sizeof(statp->defdname));
+        char** pp = statp->dnsrch;
+        int* p = info->dnsrch_offset;
+        while (pp < statp->dnsrch + MAXDNSRCH && *p != -1) {
+            *pp++ = &statp->defdname[0] + *p++;
+        }
+    }
+}
+
+/* Resolver reachability statistics. */
+
+static void _res_cache_add_stats_sample_locked(res_stats* stats, const res_sample* sample,
+                                               int max_samples) {
+    // Note: This function expects max_samples > 0, otherwise a (harmless) modification of the
+    // allocated but supposedly unused memory for samples[0] will happen
+    LOG(INFO) << __func__ << ": adding sample to stats, next = " << unsigned(stats->sample_next)
+              << ", count = " << unsigned(stats->sample_count);
+    stats->samples[stats->sample_next] = *sample;
+    if (stats->sample_count < max_samples) {
+        ++stats->sample_count;
+    }
+    if (++stats->sample_next >= max_samples) {
+        stats->sample_next = 0;
+    }
+}
+
+static void res_cache_clear_stats_locked(resolv_cache_info* cache_info) {
+    if (cache_info) {
+        for (int i = 0; i < MAXNS; ++i) {
+            cache_info->nsstats->sample_count = cache_info->nsstats->sample_next = 0;
+        }
+    }
+}
+
+int android_net_res_stats_get_info_for_net(unsigned netid, int* nscount,
+                                           struct sockaddr_storage servers[MAXNS], int* dcount,
+                                           char domains[MAXDNSRCH][MAXDNSRCHPATH],
+                                           res_params* params, struct res_stats stats[MAXNS],
+                                           int* wait_for_pending_req_timeout_count) {
+    int revision_id = -1;
+    std::lock_guard guard(cache_mutex);
+
+    resolv_cache_info* info = find_cache_info_locked(netid);
+    if (info) {
+        if (info->nscount > MAXNS) {
+            LOG(INFO) << __func__ << ": nscount " << info->nscount << " > MAXNS " << MAXNS;
+            errno = EFAULT;
+            return -1;
+        }
+        int i;
+        for (i = 0; i < info->nscount; i++) {
+            // Verify that the following assumptions are held, failure indicates corruption:
+            //  - getaddrinfo() may never return a sockaddr > sockaddr_storage
+            //  - all addresses are valid
+            //  - there is only one address per addrinfo thanks to numeric resolution
+            int addrlen = info->nsaddrinfo[i]->ai_addrlen;
+            if (addrlen < (int) sizeof(struct sockaddr) || addrlen > (int) sizeof(servers[0])) {
+                LOG(INFO) << __func__ << ": nsaddrinfo[" << i << "].ai_addrlen == " << addrlen;
+                errno = EMSGSIZE;
+                return -1;
+            }
+            if (info->nsaddrinfo[i]->ai_addr == NULL) {
+                LOG(INFO) << __func__ << ": nsaddrinfo[" << i << "].ai_addr == NULL";
+                errno = ENOENT;
+                return -1;
+            }
+            if (info->nsaddrinfo[i]->ai_next != NULL) {
+                LOG(INFO) << __func__ << ": nsaddrinfo[" << i << "].ai_next != NULL";
+                errno = ENOTUNIQ;
+                return -1;
+            }
+        }
+        *nscount = info->nscount;
+        for (i = 0; i < info->nscount; i++) {
+            memcpy(&servers[i], info->nsaddrinfo[i]->ai_addr, info->nsaddrinfo[i]->ai_addrlen);
+            stats[i] = info->nsstats[i];
+        }
+        for (i = 0; i < MAXDNSRCH; i++) {
+            const char* cur_domain = info->defdname + info->dnsrch_offset[i];
+            // dnsrch_offset[i] can either be -1 or point to an empty string to indicate the end
+            // of the search offsets. Checking for < 0 is not strictly necessary, but safer.
+            // TODO: Pass in a search domain array instead of a string to
+            // resolv_set_nameservers_for_net() and make this double check unnecessary.
+            if (info->dnsrch_offset[i] < 0 ||
+                ((size_t) info->dnsrch_offset[i]) >= sizeof(info->defdname) || !cur_domain[0]) {
+                break;
+            }
+            strlcpy(domains[i], cur_domain, MAXDNSRCHPATH);
+        }
+        *dcount = i;
+        *params = info->params;
+        revision_id = info->revision_id;
+        *wait_for_pending_req_timeout_count = info->wait_for_pending_req_timeout_count;
+    }
+
+    return revision_id;
+}
+
+int resolv_cache_get_resolver_stats(unsigned netid, res_params* params, res_stats stats[MAXNS]) {
+    std::lock_guard guard(cache_mutex);
+    resolv_cache_info* info = find_cache_info_locked(netid);
+    if (info) {
+        memcpy(stats, info->nsstats, sizeof(info->nsstats));
+        *params = info->params;
+        return info->revision_id;
+    }
+
+    return -1;
+}
+
+void _resolv_cache_add_resolver_stats_sample(unsigned netid, int revision_id, int ns,
+                                             const res_sample* sample, int max_samples) {
+    if (max_samples <= 0) return;
+
+    std::lock_guard guard(cache_mutex);
+    resolv_cache_info* info = find_cache_info_locked(netid);
+
+    if (info && info->revision_id == revision_id) {
+        _res_cache_add_stats_sample_locked(&info->nsstats[ns], sample, max_samples);
+    }
+}
diff --git a/resolv/res_comp.cpp b/resolv/res_comp.cpp
new file mode 100644
index 0000000..98461b3
--- /dev/null
+++ b/resolv/res_comp.cpp
@@ -0,0 +1,212 @@
+/*	$NetBSD: res_comp.c,v 1.6 2004/05/22 23:47:09 christos Exp $	*/
+
+/*
+ * Copyright (c) 1985, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <arpa/nameser.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "resolv_private.h"
+
+/*
+ * Expand compressed domain name 'src' to full domain name.
+ * 'msg' is a pointer to the begining of the message,
+ * 'eom' points to the first location after the message,
+ * 'dst' is a pointer to a buffer of size 'dstsiz' for the result.
+ * Return size of compressed name or -1 if there was an error.
+ */
+int dn_expand(const u_char* msg, const u_char* eom, const u_char* src, char* dst, int dstsiz) {
+    int n = ns_name_uncompress(msg, eom, src, dst, (size_t) dstsiz);
+
+    if (n > 0 && dst[0] == '.') dst[0] = '\0';
+    return (n);
+}
+
+/*
+ * Pack domain name 'exp_dn' in presentation form into 'comp_dn'.
+ * Return the size of the compressed name or -1.
+ * 'length' is the size of the array pointed to by 'comp_dn'.
+ */
+int dn_comp(const char* src, u_char* dst, int dstsiz, u_char** dnptrs, u_char** lastdnptr) {
+    return (ns_name_compress(src, dst, (size_t) dstsiz, (const u_char**) dnptrs,
+                             (const u_char**) lastdnptr));
+}
+
+/*
+ * Skip over a compressed domain name. Return the size or -1.
+ */
+int dn_skipname(const u_char* ptr, const u_char* eom) {
+    const u_char* saveptr = ptr;
+
+    if (ns_name_skip(&ptr, eom) == -1) return (-1);
+    return (ptr - saveptr);
+}
+
+/*
+ * Verify that a domain name uses an acceptable character set.
+ */
+
+/*
+ * Note the conspicuous absence of ctype macros in these definitions.  On
+ * non-ASCII hosts, we can't depend on string literals or ctype macros to
+ * tell us anything about network-format data.  The rest of the BIND system
+ * is not careful about this, but for some reason, we're doing it right here.
+ */
+
+/* BIONIC: We also accept underscores in the middle of labels.
+ *         This extension is needed to make resolution on some VPN networks
+ *         work properly.
+ */
+
+#define PERIOD 0x2e
+#define hyphenchar(c) ((c) == 0x2d)
+#define bslashchar(c) ((c) == 0x5c)
+#define periodchar(c) ((c) == PERIOD)
+#define asterchar(c) ((c) == 0x2a)
+#define alphachar(c) (((c) >= 0x41 && (c) <= 0x5a) || ((c) >= 0x61 && (c) <= 0x7a))
+#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39)
+#define underscorechar(c) ((c) == 0x5f)
+
+#define borderchar(c) (alphachar(c) || digitchar(c))
+#define middlechar(c) (borderchar(c) || hyphenchar(c) || underscorechar(c))
+#define domainchar(c) ((c) > 0x20 && (c) < 0x7f)
+
+int res_hnok(const char* dn) {
+    int pch = PERIOD, ch = *dn++;
+
+    while (ch != '\0') {
+        int nch = *dn++;
+
+        if (periodchar(ch)) {
+            ;
+        } else if (periodchar(pch)) {
+            if (!borderchar(ch)) return (0);
+        } else if (periodchar(nch) || nch == '\0') {
+            if (!borderchar(ch)) return (0);
+        } else {
+            if (!middlechar(ch)) return (0);
+        }
+        pch = ch, ch = nch;
+    }
+    return (1);
+}
+
+/*
+ * hostname-like (A, MX, WKS) owners can have "*" as their first label
+ * but must otherwise be as a host name.
+ */
+int res_ownok(const char* dn) {
+    if (asterchar(dn[0])) {
+        if (periodchar(dn[1])) return (res_hnok(dn + 2));
+        if (dn[1] == '\0') return (1);
+    }
+    return (res_hnok(dn));
+}
+
+/*
+ * SOA RNAMEs and RP RNAMEs can have any printable character in their first
+ * label, but the rest of the name has to look like a host name.
+ */
+int res_mailok(const char* dn) {
+    int ch, escaped = 0;
+
+    /* "." is a valid missing representation */
+    if (*dn == '\0') return (1);
+
+    /* otherwise <label>.<hostname> */
+    while ((ch = *dn++) != '\0') {
+        if (!domainchar(ch)) return (0);
+        if (!escaped && periodchar(ch)) break;
+        if (escaped)
+            escaped = 0;
+        else if (bslashchar(ch))
+            escaped = 1;
+    }
+    if (periodchar(ch)) return (res_hnok(dn));
+    return (0);
+}
+
+/*
+ * This function is quite liberal, since RFC 1034's character sets are only
+ * recommendations.
+ */
+int res_dnok(const char* dn) {
+    int ch;
+
+    while ((ch = *dn++) != '\0')
+        if (!domainchar(ch)) return (0);
+    return (1);
+}
diff --git a/resolv/res_debug.cpp b/resolv/res_debug.cpp
new file mode 100644
index 0000000..5b20a58
--- /dev/null
+++ b/resolv/res_debug.cpp
@@ -0,0 +1,540 @@
+/*	$NetBSD: res_debug.c,v 1.13 2012/06/25 22:32:45 abs Exp $	*/
+
+/*
+ * Portions Copyright (C) 2004, 2005, 2008, 2009  Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (C) 1996-2003  Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 1985
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Portions Copyright (c) 1995 by International Business Machines, Inc.
+ *
+ * International Business Machines, Inc. (hereinafter called IBM) grants
+ * permission under its copyrights to use, copy, modify, and distribute this
+ * Software with or without fee, provided that the above copyright notice and
+ * all paragraphs of this notice appear in all copies, and that the name of IBM
+ * not be used in connection with the marketing of any product incorporating
+ * the Software or modifications thereof, without specific, written prior
+ * permission.
+ *
+ * To the extent it has a right to do so, IBM grants an immunity from suit
+ * under its patents, if any, for the use, sale or manufacture of products to
+ * the extent that such products are used for performing Domain Name System
+ * dynamic updates in TCP/IP networks by means of the Software.  No immunity is
+ * granted for any product per se or for any other function of any product.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.  IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
+ * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
+ * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#define LOG_TAG "res_debug"
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+
+#include <aidl/android/net/IDnsResolver.h>
+#include <android-base/logging.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+
+#include "resolv_private.h"
+
+// Default to disabling verbose logging unless overridden by Android.bp
+// for debuggable builds.
+//
+// NOTE: Verbose resolver logs could contain PII -- do NOT enable in production builds
+#ifndef RESOLV_ALLOW_VERBOSE_LOGGING
+#define RESOLV_ALLOW_VERBOSE_LOGGING 0
+#endif
+
+struct res_sym {
+    int number;            /* Identifying number, like T_MX */
+    const char* name;      /* Its symbolic name, like "MX" */
+    const char* humanname; /* Its fun name, like "mail exchanger" */
+};
+
+// add a formatted string to a bounded buffer
+// TODO: convert to std::string
+static char* dbprint(char* p, char* end, const char* format, ...) {
+    int avail, n;
+    va_list args;
+
+    avail = end - p;
+
+    if (avail <= 0) return p;
+
+    va_start(args, format);
+    n = vsnprintf(p, avail, format, args);
+    va_end(args);
+
+    /* certain C libraries return -1 in case of truncation */
+    if (n < 0 || n > avail) n = avail;
+
+    p += n;
+    /* certain C libraries do not zero-terminate in case of truncation */
+    if (p == end) p[-1] = 0;
+
+    return p;
+}
+
+static void do_section(ns_msg* handle, ns_sect section) {
+    int n, rrnum;
+    int buflen = 2048;
+    ns_opcode opcode;
+    ns_rr rr;
+    char temp[2048], *p = temp, *end = p + sizeof(temp);
+
+    /*
+     * Print answer records.
+     */
+
+    char* buf = (char*) malloc((size_t) buflen);
+    if (buf == NULL) {
+        dbprint(p, end, ";; memory allocation failure\n");
+        LOG(VERBOSE) << __func__ << ": " << temp;
+        return;
+    }
+
+    opcode = (ns_opcode) ns_msg_getflag(*handle, ns_f_opcode);
+    rrnum = 0;
+    for (;;) {
+        if (ns_parserr(handle, section, rrnum, &rr)) {
+            if (errno != ENODEV)
+                dbprint(p, end, ";; ns_parserr: %s", strerror(errno));
+            goto cleanup;
+        }
+        if (section == ns_s_qd)
+            dbprint(p, end, ";;\t%s, type = %s, class = %s\n", ns_rr_name(rr),
+                    p_type(ns_rr_type(rr)), p_class(ns_rr_class(rr)));
+        else if (section == ns_s_ar && ns_rr_type(rr) == ns_t_opt) {
+            size_t rdatalen, ttl;
+            uint16_t optcode, optlen;
+
+            rdatalen = ns_rr_rdlen(rr);
+            ttl = ns_rr_ttl(rr);
+            dbprint(p, end, "; EDNS: version: %zu, udp=%u, flags=%04zx\n", (ttl >> 16) & 0xff,
+                    ns_rr_class(rr), ttl & 0xffff);
+            const u_char* cp = ns_rr_rdata(rr);
+            while (rdatalen <= ns_rr_rdlen(rr) && rdatalen >= 4) {
+                int i;
+
+                GETSHORT(optcode, cp);
+                GETSHORT(optlen, cp);
+
+                if (optcode == NS_OPT_NSID) {
+                    p = dbprint(p, end, "; NSID: ");
+                    if (optlen == 0) {
+                        p = dbprint(p, end, "; NSID\n");
+                    } else {
+                        p = dbprint(p, end, "; NSID: ");
+                        for (i = 0; i < optlen; i++) {
+                            p = dbprint(p, end, "%02x ", cp[i]);
+                        }
+                        p = dbprint(p, end, " (");
+                        for (i = 0; i < optlen; i++) {
+                            p = dbprint(p, end, "%c", isprint(cp[i]) ? cp[i] : '.');
+                        }
+                        p = dbprint(p, end, ")\n");
+                    }
+                } else {
+                    if (optlen == 0) {
+                        p = dbprint(p, end, "; OPT=%u\n", optcode);
+                    } else {
+                        p = dbprint(p, end, "; OPT=%u: ", optcode);
+                        for (i = 0; i < optlen; i++) {
+                            p = dbprint(p, end, "%02x ", cp[i]);
+                        }
+                        p = dbprint(p, end, " (");
+                        for (i = 0; i < optlen; i++) {
+                            p = dbprint(p, end, "%c", isprint(cp[i]) ? cp[i] : '.');
+                        }
+                        p = dbprint(p, end, ")\n");
+                    }
+                }
+                rdatalen -= 4 + optlen;
+                cp += optlen;
+            }
+        } else {
+            n = ns_sprintrr(handle, &rr, NULL, NULL, buf, (u_int) buflen);
+            if (n < 0) {
+                if (errno == ENOSPC) {
+                    free(buf);
+                    buf = NULL;
+                    if (buflen < 131072) {
+                        buf = (char*) malloc((size_t)(buflen += 1024));
+                    }
+                    if (buf == NULL) {
+                        p = dbprint(p, end, ";; memory allocation failure\n");
+                        LOG(VERBOSE) << __func__ << ": " << temp;
+                        return;
+                    }
+                    continue;
+                }
+                p = dbprint(p, end, ";; ns_sprintrr: %s\n", strerror(errno));
+                goto cleanup;
+            }
+            p = dbprint(p, end, ";; %s\n", buf);
+        }
+        rrnum++;
+    }
+cleanup:
+    free(buf);
+    LOG(VERBOSE) << temp;
+}
+
+/*
+ * Print the contents of a query.
+ * This is intended to be primarily a debugging routine.
+ */
+void res_pquery(const u_char* msg, int len) {
+    if (!WOULD_LOG(VERBOSE)) return;
+
+    ns_msg handle;
+    int qdcount, ancount, nscount, arcount;
+    u_int opcode, rcode, id;
+    char temp[2048], *p = temp, *end = p + sizeof(temp);
+
+    if (ns_initparse(msg, len, &handle) < 0) {
+        dbprint(p, end, ";; ns_initparse: %s\n", strerror(errno));
+        return;
+    }
+    opcode = ns_msg_getflag(handle, ns_f_opcode);
+    rcode = ns_msg_getflag(handle, ns_f_rcode);
+    id = ns_msg_id(handle);
+    qdcount = ns_msg_count(handle, ns_s_qd);
+    ancount = ns_msg_count(handle, ns_s_an);
+    nscount = ns_msg_count(handle, ns_s_ns);
+    arcount = ns_msg_count(handle, ns_s_ar);
+
+    /*
+     * Print header fields.
+     */
+    dbprint(p, end, ";; ->>HEADER<<- opcode: %s, status: %s, id: %d\n", _res_opcodes[opcode],
+            p_rcode((int)rcode), id);
+    p = dbprint(p, end, ";");
+    p = dbprint(p, end, "; flags:");
+    if (ns_msg_getflag(handle, ns_f_qr)) p = dbprint(p, end, " qr");
+    if (ns_msg_getflag(handle, ns_f_aa)) p = dbprint(p, end, " aa");
+    if (ns_msg_getflag(handle, ns_f_tc)) p = dbprint(p, end, " tc");
+    if (ns_msg_getflag(handle, ns_f_rd)) p = dbprint(p, end, " rd");
+    if (ns_msg_getflag(handle, ns_f_ra)) p = dbprint(p, end, " ra");
+    if (ns_msg_getflag(handle, ns_f_z)) p = dbprint(p, end, " ??");
+    if (ns_msg_getflag(handle, ns_f_ad)) p = dbprint(p, end, " ad");
+    if (ns_msg_getflag(handle, ns_f_cd)) p = dbprint(p, end, " cd");
+    p = dbprint(p, end, "; %s: %d", p_section(ns_s_qd, (int)opcode), qdcount);
+    p = dbprint(p, end, ", %s: %d", p_section(ns_s_an, (int)opcode), ancount);
+    p = dbprint(p, end, ", %s: %d", p_section(ns_s_ns, (int)opcode), nscount);
+    p = dbprint(p, end, ", %s: %d", p_section(ns_s_ar, (int)opcode), arcount);
+
+    LOG(VERBOSE) << temp;
+
+    /*
+     * Print the various sections.
+     */
+    do_section(&handle, ns_s_qd);
+    do_section(&handle, ns_s_an);
+    do_section(&handle, ns_s_ns);
+    do_section(&handle, ns_s_ar);
+    if (qdcount == 0 && ancount == 0 && nscount == 0 && arcount == 0) LOG(VERBOSE) << ";;";
+}
+
+/*
+ * Names of RR classes and qclasses.  Classes and qclasses are the same, except
+ * that C_ANY is a qclass but not a class.  (You can ask for records of class
+ * C_ANY, but you can't have any records of that class in the database.)
+ */
+static const struct res_sym p_class_syms[] = {
+        {C_IN, "IN", (char*) 0},     {C_CHAOS, "CH", (char*) 0},  {C_CHAOS, "CHAOS", (char*) 0},
+        {C_HS, "HS", (char*) 0},     {C_HS, "HESIOD", (char*) 0}, {C_ANY, "ANY", (char*) 0},
+        {C_NONE, "NONE", (char*) 0}, {C_IN, (char*) 0, (char*) 0}};
+
+/*
+ * Names of message sections.
+ */
+static const struct res_sym p_default_section_syms[] = {{ns_s_qd, "QUERY", (char*) 0},
+                                                        {ns_s_an, "ANSWER", (char*) 0},
+                                                        {ns_s_ns, "AUTHORITY", (char*) 0},
+                                                        {ns_s_ar, "ADDITIONAL", (char*) 0},
+                                                        {0, (char*) 0, (char*) 0}};
+
+static const struct res_sym p_update_section_syms[] = {{S_ZONE, "ZONE", (char*) 0},
+                                                       {S_PREREQ, "PREREQUISITE", (char*) 0},
+                                                       {S_UPDATE, "UPDATE", (char*) 0},
+                                                       {S_ADDT, "ADDITIONAL", (char*) 0},
+                                                       {0, (char*) 0, (char*) 0}};
+
+/*
+ * Names of RR types and qtypes.  Types and qtypes are the same, except
+ * that T_ANY is a qtype but not a type.  (You can ask for records of type
+ * T_ANY, but you can't have any records of that type in the database.)
+ */
+const struct res_sym p_type_syms[] = {
+        {ns_t_a, "A", "address"},
+        {ns_t_ns, "NS", "name server"},
+        {ns_t_md, "MD", "mail destination (deprecated)"},
+        {ns_t_mf, "MF", "mail forwarder (deprecated)"},
+        {ns_t_cname, "CNAME", "canonical name"},
+        {ns_t_soa, "SOA", "start of authority"},
+        {ns_t_mb, "MB", "mailbox"},
+        {ns_t_mg, "MG", "mail group member"},
+        {ns_t_mr, "MR", "mail rename"},
+        {ns_t_null, "NULL", "null"},
+        {ns_t_wks, "WKS", "well-known service (deprecated)"},
+        {ns_t_ptr, "PTR", "domain name pointer"},
+        {ns_t_hinfo, "HINFO", "host information"},
+        {ns_t_minfo, "MINFO", "mailbox information"},
+        {ns_t_mx, "MX", "mail exchanger"},
+        {ns_t_txt, "TXT", "text"},
+        {ns_t_rp, "RP", "responsible person"},
+        {ns_t_afsdb, "AFSDB", "DCE or AFS server"},
+        {ns_t_x25, "X25", "X25 address"},
+        {ns_t_isdn, "ISDN", "ISDN address"},
+        {ns_t_rt, "RT", "router"},
+        {ns_t_nsap, "NSAP", "nsap address"},
+        {ns_t_nsap_ptr, "NSAP_PTR", "domain name pointer"},
+        {ns_t_sig, "SIG", "signature"},
+        {ns_t_key, "KEY", "key"},
+        {ns_t_px, "PX", "mapping information"},
+        {ns_t_gpos, "GPOS", "geographical position (withdrawn)"},
+        {ns_t_aaaa, "AAAA", "IPv6 address"},
+        {ns_t_loc, "LOC", "location"},
+        {ns_t_nxt, "NXT", "next valid name (unimplemented)"},
+        {ns_t_eid, "EID", "endpoint identifier (unimplemented)"},
+        {ns_t_nimloc, "NIMLOC", "NIMROD locator (unimplemented)"},
+        {ns_t_srv, "SRV", "server selection"},
+        {ns_t_atma, "ATMA", "ATM address (unimplemented)"},
+        {ns_t_naptr, "NAPTR", "naptr"},
+        {ns_t_kx, "KX", "key exchange"},
+        {ns_t_cert, "CERT", "certificate"},
+        {ns_t_a6, "A", "IPv6 address (experminental)"},
+        {ns_t_dname, "DNAME", "non-terminal redirection"},
+        {ns_t_opt, "OPT", "opt"},
+        {ns_t_apl, "apl", "apl"},
+        {ns_t_ds, "DS", "delegation signer"},
+        {ns_t_sshfp, "SSFP", "SSH fingerprint"},
+        {ns_t_ipseckey, "IPSECKEY", "IPSEC key"},
+        {ns_t_rrsig, "RRSIG", "rrsig"},
+        {ns_t_nsec, "NSEC", "nsec"},
+        {ns_t_dnskey, "DNSKEY", "DNS key"},
+        {ns_t_dhcid, "DHCID", "dynamic host configuration identifier"},
+        {ns_t_nsec3, "NSEC3", "nsec3"},
+        {ns_t_nsec3param, "NSEC3PARAM", "NSEC3 parameters"},
+        {ns_t_hip, "HIP", "host identity protocol"},
+        {ns_t_spf, "SPF", "sender policy framework"},
+        {ns_t_tkey, "TKEY", "tkey"},
+        {ns_t_tsig, "TSIG", "transaction signature"},
+        {ns_t_ixfr, "IXFR", "incremental zone transfer"},
+        {ns_t_axfr, "AXFR", "zone transfer"},
+        {ns_t_zxfr, "ZXFR", "compressed zone transfer"},
+        {ns_t_mailb, "MAILB", "mailbox-related data (deprecated)"},
+        {ns_t_maila, "MAILA", "mail agent (deprecated)"},
+        {ns_t_naptr, "NAPTR", "URN Naming Authority"},
+        {ns_t_kx, "KX", "Key Exchange"},
+        {ns_t_cert, "CERT", "Certificate"},
+        {ns_t_a6, "A6", "IPv6 Address"},
+        {ns_t_dname, "DNAME", "dname"},
+        {ns_t_sink, "SINK", "Kitchen Sink (experimental)"},
+        {ns_t_opt, "OPT", "EDNS Options"},
+        {ns_t_any, "ANY", "\"any\""},
+        {ns_t_dlv, "DLV", "DNSSEC look-aside validation"},
+        {0, NULL, NULL}};
+
+/*
+ * Names of DNS rcodes.
+ */
+static const struct res_sym p_rcode_syms[] = {{ns_r_noerror, "NOERROR", "no error"},
+                                              {ns_r_formerr, "FORMERR", "format error"},
+                                              {ns_r_servfail, "SERVFAIL", "server failed"},
+                                              {ns_r_nxdomain, "NXDOMAIN", "no such domain name"},
+                                              {ns_r_notimpl, "NOTIMP", "not implemented"},
+                                              {ns_r_refused, "REFUSED", "refused"},
+                                              {ns_r_yxdomain, "YXDOMAIN", "domain name exists"},
+                                              {ns_r_yxrrset, "YXRRSET", "rrset exists"},
+                                              {ns_r_nxrrset, "NXRRSET", "rrset doesn't exist"},
+                                              {ns_r_notauth, "NOTAUTH", "not authoritative"},
+                                              {ns_r_notzone, "NOTZONE", "Not in zone"},
+                                              {ns_r_max, "", ""},
+                                              {ns_r_badsig, "BADSIG", "bad signature"},
+                                              {ns_r_badkey, "BADKEY", "bad key"},
+                                              {ns_r_badtime, "BADTIME", "bad time"},
+                                              {0, NULL, NULL}};
+
+static const char* sym_ntos(const struct res_sym* syms, int number, int* success) {
+    static char unname[20];
+
+    for (; syms->name != 0; syms++) {
+        if (number == syms->number) {
+            if (success) *success = 1;
+            return (syms->name);
+        }
+    }
+
+    snprintf(unname, sizeof(unname), "%d", number); /* XXX nonreentrant */
+    if (success) *success = 0;
+    return (unname);
+}
+
+/*
+ * Return a string for the type.
+ */
+const char* p_type(int type) {
+    int success;
+    const char* result;
+    static char typebuf[20];
+
+    result = sym_ntos(p_type_syms, type, &success);
+    if (success) return (result);
+    if (type < 0 || type > 0xffff) return ("BADTYPE");
+    snprintf(typebuf, sizeof(typebuf), "TYPE%d", type);
+    return (typebuf);
+}
+
+/*
+ * Return a string for the type.
+ */
+const char* p_section(int section, int opcode) {
+    const struct res_sym* symbols;
+
+    switch (opcode) {
+        case ns_o_update:
+            symbols = p_update_section_syms;
+            break;
+        default:
+            symbols = p_default_section_syms;
+            break;
+    }
+    return (sym_ntos(symbols, section, (int*) 0));
+}
+
+/*
+ * Return a mnemonic for class.
+ */
+const char* p_class(int cl) {
+    int success;
+    const char* result;
+    static char classbuf[20];
+
+    result = sym_ntos(p_class_syms, cl, &success);
+    if (success) return (result);
+    if (cl < 0 || cl > 0xffff) return ("BADCLASS");
+    snprintf(classbuf, sizeof(classbuf), "CLASS%d", cl);
+    return (classbuf);
+}
+
+/*
+ * Return a string for the rcode.
+ */
+const char* p_rcode(int rcode) {
+    return (sym_ntos(p_rcode_syms, rcode, (int*) 0));
+}
+
+int resolv_set_log_severity(uint32_t logSeverity) {
+    switch (logSeverity) {
+        case aidl::android::net::IDnsResolver::DNS_RESOLVER_LOG_VERBOSE:
+            logSeverity = android::base::VERBOSE;
+            // *** enable verbose logging only when DBG is set. It prints sensitive data ***
+            if (RESOLV_ALLOW_VERBOSE_LOGGING == false) {
+                logSeverity = android::base::DEBUG;
+                LOG(ERROR) << "Refusing to set VERBOSE logging in non-debuggable build";
+                // TODO: Return EACCES then callers could know if the log
+                // severity is acceptable
+            }
+            break;
+        case aidl::android::net::IDnsResolver::DNS_RESOLVER_LOG_DEBUG:
+            logSeverity = android::base::DEBUG;
+            break;
+        case aidl::android::net::IDnsResolver::DNS_RESOLVER_LOG_INFO:
+            logSeverity = android::base::INFO;
+            break;
+        case aidl::android::net::IDnsResolver::DNS_RESOLVER_LOG_WARNING:
+            logSeverity = android::base::WARNING;
+            break;
+        case aidl::android::net::IDnsResolver::DNS_RESOLVER_LOG_ERROR:
+            logSeverity = android::base::ERROR;
+            break;
+        default:
+            LOG(ERROR) << __func__ << ": invalid log severity: " << logSeverity;
+            return -EINVAL;
+    }
+    android::base::SetMinimumLogSeverity(static_cast<android::base::LogSeverity>(logSeverity));
+    return 0;
+}
diff --git a/server/binder/android/net/UidRange.aidl b/resolv/res_debug.h
similarity index 73%
copy from server/binder/android/net/UidRange.aidl
copy to resolv/res_debug.h
index 55747d0..2ba31d4 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/resolv/res_debug.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-package android.net;
+#pragma once
 
-/**
- * An inclusive range of UIDs.
- *
- * {@hide}
- */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+#include <cinttypes>
+
+int resolv_set_log_severity(uint32_t logSeverity);
diff --git a/resolv/res_init.cpp b/resolv/res_init.cpp
new file mode 100644
index 0000000..ceb3023
--- /dev/null
+++ b/resolv/res_init.cpp
@@ -0,0 +1,397 @@
+/*	$NetBSD: res_init.c,v 1.8 2006/03/19 03:10:08 christos Exp $	*/
+
+/*
+ * Copyright (c) 1985, 1989, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define LOG_TAG "res_init"
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+
+#include <android-base/logging.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "netd_resolv/resolv.h"
+#include "res_state_ext.h"
+#include "resolv_private.h"
+
+
+static void res_setoptions(res_state, const char*, const char*);
+
+/*
+ * Resolver state default settings.
+ */
+
+/*
+ * Set up default settings.  If the configuration file exist, the values
+ * there will have precedence.  Otherwise, the server address is set to
+ * INADDR_ANY and the default domain name comes from the gethostname().
+ *
+ * An interrim version of this code (BIND 4.9, pre-4.4BSD) used 127.0.0.1
+ * rather than INADDR_ANY ("0.0.0.0") as the default name server address
+ * since it was noted that INADDR_ANY actually meant ``the first interface
+ * you "ifconfig"'d at boot time'' and if this was a SLIP or PPP interface,
+ * it had to be "up" in order for you to reach your own name server.  It
+ * was later decided that since the recommended practice is to always
+ * install local static routes through 127.0.0.1 for all your network
+ * interfaces, that we could solve this problem without a code change.
+ *
+ * The configuration file should always be used, since it is the only way
+ * to specify a default domain.  If you are running a server on your local
+ * machine, you should say "nameserver 0.0.0.0" or "nameserver 127.0.0.1"
+ * in the configuration file.
+ *
+ * Return 0 if completes successfully, -1 on error
+ */
+int res_ninit(res_state statp) {
+    return res_vinit(statp, 0);
+}
+
+/* This function has to be reachable by res_data.c but not publicly. */
+int res_vinit(res_state statp, int preinit) {
+    char *cp, **pp;
+    char buf[BUFSIZ];
+    int nserv = 0; /* number of nameserver records read from file */
+    int havesearch = 0;
+    int dots;
+    sockaddr_union u[2];
+
+    if ((statp->options & RES_INIT) != 0U) res_ndestroy(statp);
+
+    if (!preinit) {
+        statp->netid = NETID_UNSET;
+        statp->options = RES_DEFAULT;
+        statp->id = arc4random_uniform(65536);
+        statp->_mark = MARK_UNSET;
+    }
+
+    memset(u, 0, sizeof(u));
+    u[nserv].sin.sin_addr.s_addr = INADDR_ANY;
+    u[nserv].sin.sin_family = AF_INET;
+    u[nserv].sin.sin_port = htons(NAMESERVER_PORT);
+    nserv++;
+    statp->nscount = 0;
+    statp->ndots = 1;
+    statp->_vcsock = -1;
+    statp->_flags = 0;
+    statp->_u._ext.nscount = 0;
+    statp->_u._ext.ext = (res_state_ext*) malloc(sizeof(*statp->_u._ext.ext));
+    statp->use_local_nameserver = false;
+    if (statp->_u._ext.ext != NULL) {
+        memset(statp->_u._ext.ext, 0, sizeof(*statp->_u._ext.ext));
+        statp->_u._ext.ext->nsaddrs[0].sin = statp->nsaddr;
+        strcpy(statp->_u._ext.ext->nsuffix, "ip6.arpa");
+        strcpy(statp->_u._ext.ext->nsuffix2, "ip6.int");
+    }
+    statp->nsort = 0;
+    res_setservers(statp, u, nserv);
+
+    if (statp->defdname[0] == 0 && gethostname(buf, sizeof(statp->defdname) - 1) == 0 &&
+        (cp = strchr(buf, '.')) != NULL)
+        strcpy(statp->defdname, cp + 1);
+
+    /* find components of local domain that might be searched */
+    if (havesearch == 0) {
+        pp = statp->dnsrch;
+        *pp++ = statp->defdname;
+        *pp = NULL;
+
+        dots = 0;
+        for (cp = statp->defdname; *cp; cp++) dots += (*cp == '.');
+
+        cp = statp->defdname;
+        while (pp < statp->dnsrch + MAXDFLSRCH) {
+            if (dots < LOCALDOMAINPARTS) break;
+            cp = strchr(cp, '.') + 1; /* we know there is one */
+            *pp++ = cp;
+            dots--;
+        }
+        *pp = NULL;
+        LOG(DEBUG) << __func__ << ": dnsrch list:";
+        for (pp = statp->dnsrch; *pp; pp++) LOG(DEBUG) << "\t" << *pp;
+    }
+
+    if ((cp = getenv("RES_OPTIONS")) != NULL) res_setoptions(statp, cp, "env");
+    if (nserv > 0) {
+        statp->nscount = nserv;
+        statp->options |= RES_INIT;
+    }
+    return (0);
+}
+
+static void res_setoptions(res_state statp, const char* options, const char* source) {
+    const char* cp = options;
+    int i;
+    res_state_ext* ext = statp->_u._ext.ext;
+
+    LOG(DEBUG) << "res_setoptions(\"" << options << "\", \"" << source << "\")...";
+
+    while (*cp) {
+        /* skip leading and inner runs of spaces */
+        while (*cp == ' ' || *cp == '\t') cp++;
+        /* search for and process individual options */
+        if (!strncmp(cp, "ndots:", sizeof("ndots:") - 1)) {
+            i = atoi(cp + sizeof("ndots:") - 1);
+            if (i <= RES_MAXNDOTS)
+                statp->ndots = i;
+            else
+                statp->ndots = RES_MAXNDOTS;
+            LOG(DEBUG) << "\tndots=" << statp->ndots;
+
+        } else if (!strncmp(cp, "debug", sizeof("debug") - 1)) {
+            if (!(statp->options & RES_DEBUG)) {
+                LOG(DEBUG) << "res_setoptions(\"" << options << "\", \"" << source << "\")..";
+                statp->options |= RES_DEBUG;
+            }
+            LOG(DEBUG) << "\tdebug";
+
+        } else if (!strncmp(cp, "no_tld_query", sizeof("no_tld_query") - 1) ||
+                   !strncmp(cp, "no-tld-query", sizeof("no-tld-query") - 1)) {
+            statp->options |= RES_NOTLDQUERY;
+        } else if (!strncmp(cp, "inet6", sizeof("inet6") - 1)) {
+            statp->options |= RES_USE_INET6;
+        } else if (!strncmp(cp, "rotate", sizeof("rotate") - 1)) {
+            statp->options |= RES_ROTATE;
+        } else if (!strncmp(cp, "no-check-names", sizeof("no-check-names") - 1)) {
+            statp->options |= RES_NOCHECKNAME;
+        }
+        else if (!strncmp(cp, "edns0", sizeof("edns0") - 1)) {
+            statp->options |= RES_USE_EDNS0;
+        }
+        else if (!strncmp(cp, "dname", sizeof("dname") - 1)) {
+            statp->options |= RES_USE_DNAME;
+        } else if (!strncmp(cp, "nibble:", sizeof("nibble:") - 1)) {
+            if (ext == NULL) goto skip;
+            cp += sizeof("nibble:") - 1;
+            i = MIN(strcspn(cp, " \t"), sizeof(ext->nsuffix) - 1);
+            strncpy(ext->nsuffix, cp, (size_t) i);
+            ext->nsuffix[i] = '\0';
+        } else if (!strncmp(cp, "nibble2:", sizeof("nibble2:") - 1)) {
+            if (ext == NULL) goto skip;
+            cp += sizeof("nibble2:") - 1;
+            i = MIN(strcspn(cp, " \t"), sizeof(ext->nsuffix2) - 1);
+            strncpy(ext->nsuffix2, cp, (size_t) i);
+            ext->nsuffix2[i] = '\0';
+        } else if (!strncmp(cp, "v6revmode:", sizeof("v6revmode:") - 1)) {
+            cp += sizeof("v6revmode:") - 1;
+            /* "nibble" and "bitstring" used to be valid */
+            if (!strncmp(cp, "single", sizeof("single") - 1)) {
+                statp->options |= RES_NO_NIBBLE2;
+            } else if (!strncmp(cp, "both", sizeof("both") - 1)) {
+                statp->options &= ~RES_NO_NIBBLE2;
+            }
+        } else {
+            /* XXX - print a warning here? */
+        }
+    skip:
+        /* skip to next run of spaces */
+        while (*cp && *cp != ' ' && *cp != '\t') cp++;
+    }
+}
+
+/*
+ * This routine is for closing the socket if a virtual circuit is used and
+ * the program wants to close it.  This provides support for endhostent()
+ * which expects to close the socket.
+ *
+ * This routine is not expected to be user visible.
+ */
+void res_nclose(res_state statp) {
+    int ns;
+
+    if (statp->_vcsock >= 0) {
+        (void) close(statp->_vcsock);
+        statp->_vcsock = -1;
+        statp->_flags &= ~RES_F_VC;
+    }
+    for (ns = 0; ns < statp->_u._ext.nscount; ns++) {
+        if (statp->_u._ext.nssocks[ns] != -1) {
+            (void) close(statp->_u._ext.nssocks[ns]);
+            statp->_u._ext.nssocks[ns] = -1;
+        }
+    }
+}
+
+void res_ndestroy(res_state statp) {
+    res_nclose(statp);
+    if (statp->_u._ext.ext != NULL) free(statp->_u._ext.ext);
+    statp->options &= ~RES_INIT;
+    statp->_u._ext.ext = NULL;
+}
+
+void res_setservers(res_state statp, const sockaddr_union* set, int cnt) {
+    int i, nserv;
+    size_t size;
+
+    /* close open servers */
+    res_nclose(statp);
+
+    /* cause rtt times to be forgotten */
+    statp->_u._ext.nscount = 0;
+
+    nserv = 0;
+    for (i = 0; i < cnt && nserv < MAXNS; i++) {
+        switch (set->sin.sin_family) {
+            case AF_INET:
+                size = sizeof(set->sin);
+                if (statp->_u._ext.ext)
+                    memcpy(&statp->_u._ext.ext->nsaddrs[nserv], &set->sin, size);
+                if (size <= sizeof(statp->nsaddr_list[nserv]))
+                    memcpy(&statp->nsaddr_list[nserv], &set->sin, size);
+                else
+                    statp->nsaddr_list[nserv].sin_family = 0;
+                nserv++;
+                break;
+
+#ifdef HAS_INET6_STRUCTS
+            case AF_INET6:
+                size = sizeof(set->sin6);
+                if (statp->_u._ext.ext)
+                    memcpy(&statp->_u._ext.ext->nsaddrs[nserv], &set->sin6, size);
+                if (size <= sizeof(statp->nsaddr_list[nserv]))
+                    memcpy(&statp->nsaddr_list[nserv], &set->sin6, size);
+                else
+                    statp->nsaddr_list[nserv].sin_family = 0;
+                nserv++;
+                break;
+#endif
+
+            default:
+                break;
+        }
+        set++;
+    }
+    statp->nscount = nserv;
+}
+
+int res_getservers(res_state statp, sockaddr_union* set, int cnt) {
+    int i;
+    size_t size;
+    uint16_t family;
+
+    for (i = 0; i < statp->nscount && i < cnt; i++) {
+        if (statp->_u._ext.ext)
+            family = statp->_u._ext.ext->nsaddrs[i].sin.sin_family;
+        else
+            family = statp->nsaddr_list[i].sin_family;
+
+        switch (family) {
+            case AF_INET:
+                size = sizeof(set->sin);
+                if (statp->_u._ext.ext)
+                    memcpy(&set->sin, &statp->_u._ext.ext->nsaddrs[i], size);
+                else
+                    memcpy(&set->sin, &statp->nsaddr_list[i], size);
+                break;
+
+#ifdef HAS_INET6_STRUCTS
+            case AF_INET6:
+                size = sizeof(set->sin6);
+                if (statp->_u._ext.ext)
+                    memcpy(&set->sin6, &statp->_u._ext.ext->nsaddrs[i], size);
+                else
+                    memcpy(&set->sin6, &statp->nsaddr_list[i], size);
+                break;
+#endif
+
+            default:
+                set->sin.sin_family = 0;
+                break;
+        }
+        set++;
+    }
+    return (statp->nscount);
+}
+
+void res_setnetcontext(res_state statp, const struct android_net_context* netcontext) {
+    if (statp != NULL) {
+        statp->netid = netcontext->dns_netid;
+        statp->_mark = netcontext->dns_mark;
+        if (netcontext->flags & NET_CONTEXT_FLAG_USE_EDNS) {
+            statp->options |= RES_USE_EDNS0 | RES_USE_DNSSEC;
+        }
+        if (netcontext->flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
+            statp->use_local_nameserver = true;
+        }
+    }
+}
diff --git a/resolv/res_mkquery.cpp b/resolv/res_mkquery.cpp
new file mode 100644
index 0000000..2371c7e
--- /dev/null
+++ b/resolv/res_mkquery.cpp
@@ -0,0 +1,250 @@
+/*	$NetBSD: res_mkquery.c,v 1.6 2006/01/24 17:40:32 christos Exp $	*/
+
+/*
+ * Copyright (c) 1985, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define LOG_TAG "res_mkquery"
+
+#include <algorithm>  // std::min()
+
+#include <arpa/nameser.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <android-base/logging.h>
+
+#include "resolv_private.h"
+
+// Queries will be padded to a multiple of this length when EDNS0 is active.
+constexpr uint16_t kEdns0Padding = 128;
+
+extern const char* const _res_opcodes[] = {
+        "QUERY",  "IQUERY", "CQUERYM", "CQUERYU", /* experimental */
+        "NOTIFY",                                 /* experimental */
+        "UPDATE", "6",      "7",       "8",        "9",       "10",
+        "11",     "12",     "13",      "ZONEINIT", "ZONEREF",
+};
+
+/*
+ * Form all types of queries.
+ * Returns the size of the result or -1.
+ */
+int res_nmkquery(res_state statp, int op,    /* opcode of query */
+                 const char* dname,          /* domain name */
+                 int cl, int type,           /* class and type of query */
+                 const u_char* data,         /* resource record data */
+                 int datalen,                /* length of data */
+                 const u_char* /*newrr_in*/, /* new rr for modify or append */
+                 u_char* buf,                /* buffer to put query */
+                 int buflen)                 /* size of buffer */
+{
+    HEADER* hp;
+    u_char *cp, *ep;
+    int n;
+    u_char *dnptrs[20], **dpp, **lastdnptr;
+
+    LOG(DEBUG) << __func__ << ": (" << _res_opcodes[op] << ", " << p_class(cl) << ", "
+               << p_type(type) << ")";
+
+    /*
+     * Initialize header fields.
+     */
+    if ((buf == NULL) || (buflen < HFIXEDSZ)) return (-1);
+    memset(buf, 0, HFIXEDSZ);
+    hp = (HEADER*) (void*) buf;
+    hp->id = htons(arc4random_uniform(65536));
+    hp->opcode = op;
+    hp->rd = (statp->options & RES_RECURSE) != 0U;
+    hp->ad = (statp->options & RES_USE_DNSSEC) != 0U;
+    hp->rcode = NOERROR;
+    cp = buf + HFIXEDSZ;
+    ep = buf + buflen;
+    dpp = dnptrs;
+    *dpp++ = buf;
+    *dpp++ = NULL;
+    lastdnptr = dnptrs + sizeof dnptrs / sizeof dnptrs[0];
+    /*
+     * perform opcode specific processing
+     */
+    switch (op) {
+        case QUERY:
+            [[fallthrough]];
+        case NS_NOTIFY_OP:
+            if (ep - cp < QFIXEDSZ) return (-1);
+            if ((n = dn_comp(dname, cp, ep - cp - QFIXEDSZ, dnptrs, lastdnptr)) < 0) return (-1);
+            cp += n;
+            *reinterpret_cast<uint16_t*>(cp) = htons(type);
+            cp += INT16SZ;
+            *reinterpret_cast<uint16_t*>(cp) = htons(cl);
+            cp += INT16SZ;
+            hp->qdcount = htons(1);
+            if (op == QUERY || data == NULL) break;
+            /*
+             * Make an additional record for completion domain.
+             */
+            if ((ep - cp) < RRFIXEDSZ) return (-1);
+            n = dn_comp((const char*) data, cp, ep - cp - RRFIXEDSZ, dnptrs, lastdnptr);
+            if (n < 0) return (-1);
+            cp += n;
+            *reinterpret_cast<uint16_t*>(cp) = htons(ns_t_null);
+            cp += INT16SZ;
+            *reinterpret_cast<uint16_t*>(cp) = htons(cl);
+            cp += INT16SZ;
+            *reinterpret_cast<uint32_t*>(cp) = htonl(0);
+            cp += INT32SZ;
+            *reinterpret_cast<uint16_t*>(cp) = htons(0);
+            cp += INT16SZ;
+            hp->arcount = htons(1);
+            break;
+
+        case IQUERY:
+            /*
+             * Initialize answer section
+             */
+            if (ep - cp < 1 + RRFIXEDSZ + datalen) return (-1);
+            *cp++ = '\0'; /* no domain name */
+            *reinterpret_cast<uint16_t*>(cp) = htons(type);
+            cp += INT16SZ;
+            *reinterpret_cast<uint16_t*>(cp) = htons(cl);
+            cp += INT16SZ;
+            *reinterpret_cast<uint32_t*>(cp) = htonl(0);
+            cp += INT32SZ;
+            *reinterpret_cast<uint16_t*>(cp) = htons(datalen);
+            cp += INT16SZ;
+            if (datalen) {
+                memcpy(cp, data, (size_t) datalen);
+                cp += datalen;
+            }
+            hp->ancount = htons(1);
+            break;
+
+        default:
+            return (-1);
+    }
+    return (cp - buf);
+}
+
+int res_nopt(res_state statp, int n0, /* current offset in buffer */
+             u_char* buf,             /* buffer to put query */
+             int buflen,              /* size of buffer */
+             int anslen)              /* UDP answer buffer size */
+{
+    HEADER* hp;
+    u_char *cp, *ep;
+    u_int16_t flags = 0;
+
+    LOG(DEBUG) << __func__;
+
+    hp = (HEADER*) (void*) buf;
+    cp = buf + n0;
+    ep = buf + buflen;
+
+    if ((ep - cp) < 1 + RRFIXEDSZ) return (-1);
+
+    *cp++ = 0; /* "." */
+
+    // Attach OPT pseudo-RR, as documented in RFC2671 (EDNS0).
+    *reinterpret_cast<uint16_t*>(cp) = htons(ns_t_opt); /* TYPE */
+    cp += INT16SZ;
+    if (anslen > 0xffff) anslen = 0xffff;
+    *reinterpret_cast<uint16_t*>(cp) = htons(anslen); /* CLASS = UDP payload size */
+    cp += INT16SZ;
+    *cp++ = NOERROR; /* extended RCODE */
+    *cp++ = 0;       /* EDNS version */
+    if (statp->options & RES_USE_DNSSEC) {
+        LOG(DEBUG) << __func__ << ": ENDS0 DNSSEC";
+        flags |= NS_OPT_DNSSEC_OK;
+    }
+    *reinterpret_cast<uint16_t*>(cp) = htons(flags);
+    cp += INT16SZ;
+
+    // EDNS0 padding
+    const uint16_t minlen = static_cast<uint16_t>(cp - buf) + 3 * INT16SZ;
+    const uint16_t extra = minlen % kEdns0Padding;
+    uint16_t padlen = (kEdns0Padding - extra) % kEdns0Padding;
+    if (minlen > buflen) {
+        return -1;
+    }
+    padlen = std::min(padlen, static_cast<uint16_t>(buflen - minlen));
+    *reinterpret_cast<uint16_t*>(cp) = htons(padlen + 2 * INT16SZ); /* RDLEN */
+    cp += INT16SZ;
+    *reinterpret_cast<uint16_t*>(cp) = htons(NS_OPT_PADDING); /* OPTION-CODE */
+    cp += INT16SZ;
+    *reinterpret_cast<uint16_t*>(cp) = htons(padlen); /* OPTION-LENGTH */
+    cp += INT16SZ;
+    memset(cp, 0, padlen);
+    cp += padlen;
+
+    hp->arcount = htons(ntohs(hp->arcount) + 1);
+    return (cp - buf);
+}
diff --git a/resolv/res_query.cpp b/resolv/res_query.cpp
new file mode 100644
index 0000000..fbbcc31
--- /dev/null
+++ b/resolv/res_query.cpp
@@ -0,0 +1,380 @@
+/*	$NetBSD: res_query.c,v 1.7 2006/01/24 17:41:25 christos Exp $	*/
+
+/*
+ * Copyright (c) 1988, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define LOG_TAG "res_query"
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+
+#include "resolv_cache.h"
+#include "resolv_private.h"
+
+#if PACKETSZ > 1024
+#define MAXPACKET PACKETSZ
+#else
+#define MAXPACKET 1024
+#endif
+
+/*
+ * Formulate a normal query, send, and await answer.
+ * Returned answer is placed in supplied buffer "answer".
+ * Perform preliminary check of answer, returning success only
+ * if no error is indicated and the answer count is nonzero.
+ * Return the size of the response on success, -1 on error.
+ * Error number is left in *herrno.
+ *
+ * Caller must parse answer and determine whether it answers the question.
+ */
+int res_nquery(res_state statp, const char* name,  // domain name
+               int cl, int type,                   // class and type of query
+               u_char* answer,                     // buffer to put answer
+               int anslen,                         // size of answer buffer
+               int* herrno)                        // legacy and extended h_errno
+                                                   // NETD_RESOLV_H_ERRNO_EXT_*
+{
+    u_char buf[MAXPACKET];
+    HEADER* hp = (HEADER*) (void*) answer;
+    int n;
+    int rcode = NOERROR;
+    bool retried = false;
+
+again:
+    hp->rcode = NOERROR; /* default */
+
+    LOG(DEBUG) << __func__ << ": (" << cl << ", " << type << ")";
+
+    n = res_nmkquery(statp, QUERY, name, cl, type, NULL, 0, NULL, buf, sizeof(buf));
+    if (n > 0 && (statp->options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) != 0U && !retried)
+        n = res_nopt(statp, n, buf, sizeof(buf), anslen);
+    if (n <= 0) {
+        LOG(DEBUG) << __func__ << ": mkquery failed";
+        *herrno = NO_RECOVERY;
+        return n;
+    }
+    n = res_nsend(statp, buf, n, answer, anslen, &rcode, 0);
+    if (n < 0) {
+        /* if the query choked with EDNS0, retry without EDNS0 */
+        if ((statp->options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) != 0U &&
+            (statp->_flags & RES_F_EDNS0ERR) && !retried) {
+            LOG(DEBUG) << __func__ << ": retry without EDNS0";
+            retried = true;
+            goto again;
+        }
+        LOG(DEBUG) << __func__ << ": send error";
+
+        // Note that rcodes SERVFAIL, NOTIMP, REFUSED may cause res_nquery() to return a general
+        // error code EAI_AGAIN, but mapping the error code from rcode as res_queryN() does for
+        // getaddrinfo(). Different rcodes trigger different behaviors:
+        //
+        // - SERVFAIL, NOTIMP, REFUSED
+        //   These result in send_dg() returning 0, causing res_nsend() to try the next
+        //   nameserver. After all nameservers failed, res_nsend() returns -ETIMEDOUT, causing
+        //   res_nquery() to return EAI_AGAIN here regardless of the rcode from the DNS response.
+        //
+        // - NXDOMAIN, FORMERR
+        //   These rcodes may cause res_nsend() to return successfully (i.e. the result is a
+        //   positive integer). In this case, res_nquery() returns error number by referring
+        //   the rcode from the DNS response.
+        switch (rcode) {
+            case RCODE_TIMEOUT:  // Not defined in RFC.
+                // DNS metrics monitors DNS query timeout.
+                *herrno = NETD_RESOLV_H_ERRNO_EXT_TIMEOUT;  // extended h_errno.
+                break;
+            default:
+                *herrno = TRY_AGAIN;
+                break;
+        }
+        return n;
+    }
+
+    if (hp->rcode != NOERROR || ntohs(hp->ancount) == 0) {
+        LOG(DEBUG) << __func__ << ": rcode = (" << p_rcode(hp->rcode)
+                   << "), counts = an:" << ntohs(hp->ancount) << " ns:" << ntohs(hp->nscount)
+                   << " ar:" << ntohs(hp->arcount);
+
+        switch (hp->rcode) {
+            case NXDOMAIN:
+                *herrno = HOST_NOT_FOUND;
+                break;
+            case SERVFAIL:
+                *herrno = TRY_AGAIN;
+                break;
+            case NOERROR:
+                *herrno = NO_DATA;
+                break;
+            case FORMERR:
+            case NOTIMP:
+            case REFUSED:
+            default:
+                *herrno = NO_RECOVERY;
+                break;
+        }
+        return -1;
+    }
+    return n;
+}
+
+/*
+ * Formulate a normal query, send, and retrieve answer in supplied buffer.
+ * Return the size of the response on success, -1 on error.
+ * If enabled, implement search rules until answer or unrecoverable failure
+ * is detected.  Error code, if any, is left in *herrno.
+ */
+int res_nsearch(res_state statp, const char* name, /* domain name */
+                int cl, int type,                  /* class and type of query */
+                u_char* answer,                    /* buffer to put answer */
+                int anslen,                        /* size of answer */
+                int* herrno)                       /* legacy and extended
+                                                      h_errno NETD_RESOLV_H_ERRNO_EXT_* */
+{
+    const char *cp, *const *domain;
+    HEADER* hp = (HEADER*) (void*) answer;
+    u_int dots;
+    int trailing_dot, ret, saved_herrno;
+    int got_nodata = 0, got_servfail = 0, root_on_list = 0;
+    int tried_as_is = 0;
+    int searched = 0;
+
+    errno = 0;
+    *herrno = HOST_NOT_FOUND; /* True if we never query. */
+
+    dots = 0;
+    for (cp = name; *cp != '\0'; cp++) dots += (*cp == '.');
+    trailing_dot = 0;
+    if (cp > name && *--cp == '.') trailing_dot++;
+
+    /*
+     * If there are enough dots in the name, let's just give it a
+     * try 'as is'. The threshold can be set with the "ndots" option.
+     * Also, query 'as is', if there is a trailing dot in the name.
+     */
+    saved_herrno = -1;
+    if (dots >= statp->ndots || trailing_dot) {
+        ret = res_nquerydomain(statp, name, NULL, cl, type, answer, anslen, herrno);
+        if (ret > 0 || trailing_dot) return ret;
+        saved_herrno = *herrno;
+        tried_as_is++;
+    }
+
+    /*
+     * We do at least one level of search if
+     *	- there is no dot and RES_DEFNAME is set, or
+     *	- there is at least one dot, there is no trailing dot,
+     *	  and RES_DNSRCH is set.
+     */
+    if ((!dots && (statp->options & RES_DEFNAMES) != 0U) ||
+        (dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0U)) {
+        int done = 0;
+
+        /* Unfortunately we need to load network-specific info
+         * (dns servers, search domains) before
+         * the domain stuff is tried.  Will have a better
+         * fix after thread pools are used as this will
+         * be loaded once for the thread instead of each
+         * time a query is tried.
+         */
+        _resolv_populate_res_for_net(statp);
+
+        for (domain = (const char* const*) statp->dnsrch; *domain && !done; domain++) {
+            searched = 1;
+
+            if (domain[0][0] == '\0' || (domain[0][0] == '.' && domain[0][1] == '\0'))
+                root_on_list++;
+
+            ret = res_nquerydomain(statp, name, *domain, cl, type, answer, anslen, herrno);
+            if (ret > 0) return ret;
+
+            /*
+             * If no server present, give up.
+             * If name isn't found in this domain,
+             * keep trying higher domains in the search list
+             * (if that's enabled).
+             * On a NO_DATA error, keep trying, otherwise
+             * a wildcard entry of another type could keep us
+             * from finding this entry higher in the domain.
+             * If we get some other error (negative answer or
+             * server failure), then stop searching up,
+             * but try the input name below in case it's
+             * fully-qualified.
+             */
+            if (errno == ECONNREFUSED) {
+                *herrno = TRY_AGAIN;
+                return -1;
+            }
+
+            switch (*herrno) {
+                case NO_DATA:
+                    got_nodata++;
+                    break;
+                case HOST_NOT_FOUND:
+                    /* keep trying */
+                    break;
+                case TRY_AGAIN:
+                    if (hp->rcode == SERVFAIL) {
+                        /* try next search element, if any */
+                        got_servfail++;
+                        break;
+                    }
+                    [[fallthrough]];
+                default:
+                    /* anything else implies that we're done */
+                    done++;
+            }
+
+            /* if we got here for some reason other than DNSRCH,
+             * we only wanted one iteration of the loop, so stop.
+             */
+            if ((statp->options & RES_DNSRCH) == 0U) done++;
+        }
+    }
+
+    /*
+     * If the query has not already been tried as is then try it
+     * unless RES_NOTLDQUERY is set and there were no dots.
+     */
+    if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0U) &&
+        !(tried_as_is || root_on_list)) {
+        ret = res_nquerydomain(statp, name, NULL, cl, type, answer, anslen, herrno);
+        if (ret > 0) return ret;
+    }
+
+    /* if we got here, we didn't satisfy the search.
+     * if we did an initial full query, return that query's H_ERRNO
+     * (note that we wouldn't be here if that query had succeeded).
+     * else if we ever got a nodata, send that back as the reason.
+     * else send back meaningless H_ERRNO, that being the one from
+     * the last DNSRCH we did.
+     */
+    if (saved_herrno != -1)
+        *herrno = saved_herrno;
+    else if (got_nodata)
+        *herrno = NO_DATA;
+    else if (got_servfail)
+        *herrno = TRY_AGAIN;
+    return -1;
+}
+
+/*
+ * Perform a call on res_query on the concatenation of name and domain,
+ * removing a trailing dot from name if domain is NULL.
+ */
+int res_nquerydomain(res_state statp, const char* name, const char* domain, int cl,
+                     int type,       /* class and type of query */
+                     u_char* answer, /* buffer to put answer */
+                     int anslen,     /* size of answer */
+                     int* herrno)    /* legacy and extended h_errno NETD_RESOLV_H_ERRNO_EXT_* */
+{
+    char nbuf[MAXDNAME];
+    const char* longname = nbuf;
+    int n, d;
+
+    if (domain == NULL) {
+        LOG(DEBUG) << __func__ << ": (null, " << cl << ", " << type << ")";
+        /*
+         * Check for trailing '.';
+         * copy without '.' if present.
+         */
+        n = strlen(name);
+        if (n >= MAXDNAME) {
+            *herrno = NO_RECOVERY;
+            return -1;
+        }
+        n--;
+        if (n >= 0 && name[n] == '.') {
+            strncpy(nbuf, name, (size_t) n);
+            nbuf[n] = '\0';
+        } else
+            longname = name;
+    } else {
+        LOG(DEBUG) << __func__ << ": (" << cl << ", " << type << ")";
+        n = strlen(name);
+        d = strlen(domain);
+        if (n + d + 1 >= MAXDNAME) {
+            *herrno = NO_RECOVERY;
+            return -1;
+        }
+        snprintf(nbuf, sizeof(nbuf), "%s.%s", name, domain);
+    }
+    return res_nquery(statp, longname, cl, type, answer, anslen, herrno);
+}
diff --git a/resolv/res_send.cpp b/resolv/res_send.cpp
new file mode 100644
index 0000000..d89ad7e
--- /dev/null
+++ b/resolv/res_send.cpp
@@ -0,0 +1,1307 @@
+/*	$NetBSD: res_send.c,v 1.9 2006/01/24 17:41:25 christos Exp $	*/
+
+/*
+ * Copyright (c) 1985, 1989, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Send query to name server and wait for reply.
+ */
+
+#define LOG_TAG "res_send"
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android/multinetwork.h>  // ResNsendFlags
+
+#include <netdutils/Slice.h>
+#include "DnsTlsDispatcher.h"
+#include "DnsTlsTransport.h"
+#include "PrivateDnsConfiguration.h"
+#include "netd_resolv/resolv.h"
+#include "netd_resolv/stats.h"
+#include "private/android_filesystem_config.h"
+#include "res_state_ext.h"
+#include "resolv_cache.h"
+#include "resolv_private.h"
+
+// TODO: use the namespace something like android::netd_resolv for libnetd_resolv
+using namespace android::net;
+using android::netdutils::Slice;
+
+static DnsTlsDispatcher sDnsTlsDispatcher;
+
+static int get_salen(const struct sockaddr*);
+static struct sockaddr* get_nsaddr(res_state, size_t);
+static int send_vc(res_state, res_params* params, const u_char*, int, u_char*, int, int*, int,
+                   time_t*, int*, int*);
+static int send_dg(res_state, res_params* params, const u_char*, int, u_char*, int, int*, int, int*,
+                   int*, time_t*, int*, int*);
+static void Aerror(const res_state, const char*, int, const struct sockaddr*, int);
+static void Perror(const res_state, const char*, int);
+
+static int sock_eq(struct sockaddr*, struct sockaddr*);
+static int connect_with_timeout(int sock, const struct sockaddr* nsap, socklen_t salen,
+                                const struct timespec timeout);
+static int retrying_poll(const int sock, short events, const struct timespec* finish);
+static int res_tls_send(res_state, const Slice query, const Slice answer, int* rcode,
+                        bool* fallback);
+
+/* BIONIC-BEGIN: implement source port randomization */
+
+// BEGIN: Code copied from ISC eventlib
+// TODO: move away from this code
+
+#define BILLION 1000000000
+
+static struct timespec evConsTime(time_t sec, long nsec) {
+    struct timespec x;
+
+    x.tv_sec = sec;
+    x.tv_nsec = nsec;
+    return (x);
+}
+
+static struct timespec evAddTime(struct timespec addend1, struct timespec addend2) {
+    struct timespec x;
+
+    x.tv_sec = addend1.tv_sec + addend2.tv_sec;
+    x.tv_nsec = addend1.tv_nsec + addend2.tv_nsec;
+    if (x.tv_nsec >= BILLION) {
+        x.tv_sec++;
+        x.tv_nsec -= BILLION;
+    }
+    return (x);
+}
+
+static struct timespec evSubTime(struct timespec minuend, struct timespec subtrahend) {
+    struct timespec x;
+
+    x.tv_sec = minuend.tv_sec - subtrahend.tv_sec;
+    if (minuend.tv_nsec >= subtrahend.tv_nsec)
+        x.tv_nsec = minuend.tv_nsec - subtrahend.tv_nsec;
+    else {
+        x.tv_nsec = BILLION - subtrahend.tv_nsec + minuend.tv_nsec;
+        x.tv_sec--;
+    }
+    return (x);
+}
+
+static int evCmpTime(struct timespec a, struct timespec b) {
+#define SGN(x) ((x) < 0 ? (-1) : (x) > 0 ? (1) : (0));
+    time_t s = a.tv_sec - b.tv_sec;
+    long n;
+
+    if (s != 0) return SGN(s);
+
+    n = a.tv_nsec - b.tv_nsec;
+    return SGN(n);
+}
+
+static struct timespec evNowTime(void) {
+    struct timespec tsnow;
+    clock_gettime(CLOCK_REALTIME, &tsnow);
+    return tsnow;
+}
+
+static struct iovec evConsIovec(void* buf, size_t cnt) {
+    struct iovec ret;
+
+    memset(&ret, 0xf5, sizeof ret);
+    ret.iov_base = buf;
+    ret.iov_len = cnt;
+    return ret;
+}
+
+// END: Code copied from ISC eventlib
+
+static int random_bind(int s, int family) {
+    sockaddr_union u;
+    int j;
+    socklen_t slen;
+
+    /* clear all, this also sets the IP4/6 address to 'any' */
+    memset(&u, 0, sizeof u);
+
+    switch (family) {
+        case AF_INET:
+            u.sin.sin_family = family;
+            slen = sizeof u.sin;
+            break;
+        case AF_INET6:
+            u.sin6.sin6_family = family;
+            slen = sizeof u.sin6;
+            break;
+        default:
+            errno = EPROTO;
+            return -1;
+    }
+
+    /* first try to bind to a random source port a few times */
+    for (j = 0; j < 10; j++) {
+        /* find a random port between 1025 .. 65534 */
+        int port = 1025 + (arc4random_uniform(65535 - 1025));
+        if (family == AF_INET)
+            u.sin.sin_port = htons(port);
+        else
+            u.sin6.sin6_port = htons(port);
+
+        if (!bind(s, &u.sa, slen)) return 0;
+    }
+
+    // nothing after 10 attempts, our network table is probably busy
+    // let the system decide which port is best
+    if (family == AF_INET)
+        u.sin.sin_port = 0;
+    else
+        u.sin6.sin6_port = 0;
+
+    return bind(s, &u.sa, slen);
+}
+/* BIONIC-END */
+
+// Disables all nameservers other than selectedServer
+static void res_set_usable_server(int selectedServer, int nscount, bool usable_servers[]) {
+    int usableIndex = 0;
+    for (int ns = 0; ns < nscount; ns++) {
+        if (usable_servers[ns]) ++usableIndex;
+        if (usableIndex != selectedServer) usable_servers[ns] = false;
+    }
+}
+
+/* int
+ * res_isourserver(ina)
+ *	looks up "ina" in _res.ns_addr_list[]
+ * returns:
+ *	0  : not found
+ *	>0 : found
+ * author:
+ *	paul vixie, 29may94
+ */
+static int res_ourserver_p(const res_state statp, const sockaddr* sa) {
+    const sockaddr_in *inp, *srv;
+    const sockaddr_in6 *in6p, *srv6;
+    int ns;
+
+    switch (sa->sa_family) {
+        case AF_INET:
+            inp = (const struct sockaddr_in*) (const void*) sa;
+            for (ns = 0; ns < statp->nscount; ns++) {
+                srv = (struct sockaddr_in*) (void*) get_nsaddr(statp, (size_t) ns);
+                if (srv->sin_family == inp->sin_family && srv->sin_port == inp->sin_port &&
+                    (srv->sin_addr.s_addr == INADDR_ANY ||
+                     srv->sin_addr.s_addr == inp->sin_addr.s_addr))
+                    return 1;
+            }
+            break;
+        case AF_INET6:
+            if (statp->_u._ext.ext == NULL) break;
+            in6p = (const struct sockaddr_in6*) (const void*) sa;
+            for (ns = 0; ns < statp->nscount; ns++) {
+                srv6 = (struct sockaddr_in6*) (void*) get_nsaddr(statp, (size_t) ns);
+                if (srv6->sin6_family == in6p->sin6_family && srv6->sin6_port == in6p->sin6_port &&
+#ifdef HAVE_SIN6_SCOPE_ID
+                    (srv6->sin6_scope_id == 0 || srv6->sin6_scope_id == in6p->sin6_scope_id) &&
+#endif
+                    (IN6_IS_ADDR_UNSPECIFIED(&srv6->sin6_addr) ||
+                     IN6_ARE_ADDR_EQUAL(&srv6->sin6_addr, &in6p->sin6_addr)))
+                    return 1;
+            }
+            break;
+        default:
+            break;
+    }
+    return 0;
+}
+
+/* int
+ * res_nameinquery(name, type, cl, buf, eom)
+ *	look for (name, type, cl) in the query section of packet (buf, eom)
+ * requires:
+ *	buf + HFIXEDSZ <= eom
+ * returns:
+ *	-1 : format error
+ *	0  : not found
+ *	>0 : found
+ * author:
+ *	paul vixie, 29may94
+ */
+int res_nameinquery(const char* name, int type, int cl, const u_char* buf, const u_char* eom) {
+    const u_char* cp = buf + HFIXEDSZ;
+    int qdcount = ntohs(((const HEADER*) (const void*) buf)->qdcount);
+
+    while (qdcount-- > 0) {
+        char tname[MAXDNAME + 1];
+        int n = dn_expand(buf, eom, cp, tname, sizeof tname);
+        if (n < 0) return (-1);
+        cp += n;
+        if (cp + 2 * INT16SZ > eom) return (-1);
+        int ttype = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ;
+        int tclass = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ;
+        if (ttype == type && tclass == cl && ns_samename(tname, name) == 1) return (1);
+    }
+    return (0);
+}
+
+/* int
+ * res_queriesmatch(buf1, eom1, buf2, eom2)
+ *	is there a 1:1 mapping of (name,type,class)
+ *	in (buf1,eom1) and (buf2,eom2)?
+ * returns:
+ *	-1 : format error
+ *	0  : not a 1:1 mapping
+ *	>0 : is a 1:1 mapping
+ * author:
+ *	paul vixie, 29may94
+ */
+int res_queriesmatch(const u_char* buf1, const u_char* eom1, const u_char* buf2,
+                     const u_char* eom2) {
+    const u_char* cp = buf1 + HFIXEDSZ;
+    int qdcount = ntohs(((const HEADER*) (const void*) buf1)->qdcount);
+
+    if (buf1 + HFIXEDSZ > eom1 || buf2 + HFIXEDSZ > eom2) return (-1);
+
+    /*
+     * Only header section present in replies to
+     * dynamic update packets.
+     */
+    if ((((const HEADER*) (const void*) buf1)->opcode == ns_o_update) &&
+        (((const HEADER*) (const void*) buf2)->opcode == ns_o_update))
+        return (1);
+
+    if (qdcount != ntohs(((const HEADER*) (const void*) buf2)->qdcount)) return (0);
+    while (qdcount-- > 0) {
+        char tname[MAXDNAME + 1];
+        int n = dn_expand(buf1, eom1, cp, tname, sizeof tname);
+        if (n < 0) return (-1);
+        cp += n;
+        if (cp + 2 * INT16SZ > eom1) return (-1);
+        int ttype = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ;
+        int tclass = ntohs(*reinterpret_cast<const uint16_t*>(cp));
+        cp += INT16SZ;
+        if (!res_nameinquery(tname, ttype, tclass, buf2, eom2)) return (0);
+    }
+    return (1);
+}
+
+int res_nsend(res_state statp, const u_char* buf, int buflen, u_char* ans, int anssiz, int* rcode,
+              uint32_t flags) {
+    int gotsomewhere, terrno, v_circuit, resplen, n;
+    ResolvCacheStatus cache_status = RESOLV_CACHE_UNSUPPORTED;
+
+    if (anssiz < HFIXEDSZ) {
+        // TODO: Remove errno once callers stop using it
+        errno = EINVAL;
+        return -EINVAL;
+    }
+    LOG(DEBUG) << __func__;
+    res_pquery(buf, buflen);
+
+    v_circuit = (statp->options & RES_USEVC) || buflen > PACKETSZ;
+    gotsomewhere = 0;
+    terrno = ETIMEDOUT;
+
+    int anslen = 0;
+    cache_status = _resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen, flags);
+
+    if (cache_status == RESOLV_CACHE_FOUND) {
+        HEADER* hp = (HEADER*)(void*)ans;
+        *rcode = hp->rcode;
+        return anslen;
+    } else if (cache_status != RESOLV_CACHE_UNSUPPORTED) {
+        // had a cache miss for a known network, so populate the thread private
+        // data so the normal resolve path can do its thing
+        _resolv_populate_res_for_net(statp);
+    }
+    if (statp->nscount == 0) {
+        // We have no nameservers configured, so there's no point trying.
+        // Tell the cache the query failed, or any retries and anyone else asking the same
+        // question will block for PENDING_REQUEST_TIMEOUT seconds instead of failing fast.
+        _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
+
+        // TODO: Remove errno once callers stop using it
+        errno = ESRCH;
+        return -ESRCH;
+    }
+
+    /*
+     * If the ns_addr_list in the resolver context has changed, then
+     * invalidate our cached copy and the associated timing data.
+     */
+    if (statp->_u._ext.nscount != 0) {
+        int needclose = 0;
+        struct sockaddr_storage peer;
+        socklen_t peerlen;
+
+        if (statp->_u._ext.nscount != statp->nscount) {
+            needclose++;
+        } else {
+            for (int ns = 0; ns < statp->nscount; ns++) {
+                if (statp->nsaddr_list[ns].sin_family &&
+                    !sock_eq((struct sockaddr*) (void*) &statp->nsaddr_list[ns],
+                             (struct sockaddr*) (void*) &statp->_u._ext.ext->nsaddrs[ns])) {
+                    needclose++;
+                    break;
+                }
+
+                if (statp->_u._ext.nssocks[ns] == -1) continue;
+                peerlen = sizeof(peer);
+                if (getpeername(statp->_u._ext.nssocks[ns], (struct sockaddr*) (void*) &peer,
+                                &peerlen) < 0) {
+                    needclose++;
+                    break;
+                }
+                if (!sock_eq((struct sockaddr*) (void*) &peer, get_nsaddr(statp, (size_t) ns))) {
+                    needclose++;
+                    break;
+                }
+            }
+        }
+        if (needclose) {
+            res_nclose(statp);
+            statp->_u._ext.nscount = 0;
+        }
+    }
+
+    /*
+     * Maybe initialize our private copy of the ns_addr_list.
+     */
+    if (statp->_u._ext.nscount == 0) {
+        for (int ns = 0; ns < statp->nscount; ns++) {
+            statp->_u._ext.nstimes[ns] = RES_MAXTIME;
+            statp->_u._ext.nssocks[ns] = -1;
+            if (!statp->nsaddr_list[ns].sin_family) continue;
+            statp->_u._ext.ext->nsaddrs[ns].sin = statp->nsaddr_list[ns];
+        }
+        statp->_u._ext.nscount = statp->nscount;
+    }
+
+    /*
+     * Some resolvers want to even out the load on their nameservers.
+     * Note that RES_BLAST overrides RES_ROTATE.
+     */
+    if ((statp->options & RES_ROTATE) != 0U && (statp->options & RES_BLAST) == 0U) {
+        sockaddr_union inu;
+        struct sockaddr_in ina;
+        int lastns = statp->nscount - 1;
+        int fd;
+        u_int16_t nstime;
+
+        if (statp->_u._ext.ext != NULL) inu = statp->_u._ext.ext->nsaddrs[0];
+        ina = statp->nsaddr_list[0];
+        fd = statp->_u._ext.nssocks[0];
+        nstime = statp->_u._ext.nstimes[0];
+        for (int ns = 0; ns < lastns; ns++) {
+            if (statp->_u._ext.ext != NULL)
+                statp->_u._ext.ext->nsaddrs[ns] = statp->_u._ext.ext->nsaddrs[ns + 1];
+            statp->nsaddr_list[ns] = statp->nsaddr_list[ns + 1];
+            statp->_u._ext.nssocks[ns] = statp->_u._ext.nssocks[ns + 1];
+            statp->_u._ext.nstimes[ns] = statp->_u._ext.nstimes[ns + 1];
+        }
+        if (statp->_u._ext.ext != NULL) statp->_u._ext.ext->nsaddrs[lastns] = inu;
+        statp->nsaddr_list[lastns] = ina;
+        statp->_u._ext.nssocks[lastns] = fd;
+        statp->_u._ext.nstimes[lastns] = nstime;
+    }
+
+    res_stats stats[MAXNS];
+    res_params params;
+    int revision_id = resolv_cache_get_resolver_stats(statp->netid, &params, stats);
+    if (revision_id < 0) {
+        // TODO: Remove errno once callers stop using it
+        errno = ESRCH;
+        return -ESRCH;
+    }
+    bool usable_servers[MAXNS];
+    int usableServersCount = android_net_res_stats_get_usable_servers(
+            &params, stats, statp->nscount, usable_servers);
+
+    if ((flags & ANDROID_RESOLV_NO_RETRY) && usableServersCount > 1) {
+        auto hp = reinterpret_cast<const HEADER*>(buf);
+
+        // Select a random server based on the query id
+        int selectedServer = (hp->id % usableServersCount) + 1;
+        res_set_usable_server(selectedServer, statp->nscount, usable_servers);
+    }
+
+    /*
+     * Send request, RETRY times, or until successful.
+     */
+    int retryTimes = (flags & ANDROID_RESOLV_NO_RETRY) ? 1 : params.retry_count;
+
+    for (int attempt = 0; attempt < retryTimes; ++attempt) {
+
+        for (int ns = 0; ns < statp->nscount; ns++) {
+            if (!usable_servers[ns]) continue;
+            struct sockaddr* nsap;
+            int nsaplen;
+            time_t now = 0;
+            int delay = 0;
+            *rcode = RCODE_INTERNAL_ERROR;
+            nsap = get_nsaddr(statp, (size_t) ns);
+            nsaplen = get_salen(nsap);
+
+        same_ns:
+            // TODO: Since we expect there is only one DNS server being queried here while this
+            // function tries to query all of private DNS servers. Consider moving it to other
+            // reasonable place. In addition, maybe add stats for private DNS.
+            if (!statp->use_local_nameserver) {
+                bool fallback = false;
+                resplen = res_tls_send(statp, Slice(const_cast<u_char*>(buf), buflen),
+                                       Slice(ans, anssiz), rcode, &fallback);
+                if (resplen > 0) {
+                    if (cache_status == RESOLV_CACHE_NOTFOUND) {
+                        _resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
+                    }
+                    return resplen;
+                }
+                if (!fallback) {
+                    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
+                    res_nclose(statp);
+                    return -terrno;
+                }
+            }
+
+            [[maybe_unused]] static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+            [[maybe_unused]] char abuf[NI_MAXHOST];
+
+            if (getnameinfo(nsap, (socklen_t)nsaplen, abuf, sizeof(abuf), NULL, 0, niflags) == 0)
+                LOG(DEBUG) << __func__ << ": Querying server (# " << ns + 1
+                           << ") address = " << abuf;
+
+            if (v_circuit) {
+                /* Use VC; at most one attempt per server. */
+                bool shouldRecordStats = (attempt == 0);
+                attempt = retryTimes;
+
+                n = send_vc(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &now, rcode,
+                            &delay);
+
+                /*
+                 * Only record stats the first time we try a query. This ensures that
+                 * queries that deterministically fail (e.g., a name that always returns
+                 * SERVFAIL or times out) do not unduly affect the stats.
+                 */
+                if (shouldRecordStats) {
+                    res_sample sample;
+                    _res_stats_set_sample(&sample, now, *rcode, delay);
+                    _resolv_cache_add_resolver_stats_sample(statp->netid, revision_id, ns, &sample,
+                                                            params.max_samples);
+                }
+
+                LOG(INFO) << __func__ << ": used send_vc " << n;
+
+                if (n < 0) {
+                    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
+                    res_nclose(statp);
+                    return -terrno;
+                };
+                if (n == 0) goto next_ns;
+                resplen = n;
+            } else {
+                /* Use datagrams. */
+                LOG(INFO) << __func__ << ": using send_dg";
+
+                n = send_dg(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &v_circuit,
+                            &gotsomewhere, &now, rcode, &delay);
+
+                /* Only record stats the first time we try a query. See above. */
+                if (attempt == 0) {
+                    res_sample sample;
+                    _res_stats_set_sample(&sample, now, *rcode, delay);
+                    _resolv_cache_add_resolver_stats_sample(statp->netid, revision_id, ns, &sample,
+                                                            params.max_samples);
+                }
+
+                LOG(INFO) << __func__ << ": used send_dg " << n;
+
+                if (n < 0) {
+                    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
+                    res_nclose(statp);
+                    return -terrno;
+                };
+                if (n == 0) goto next_ns;
+                if (v_circuit) goto same_ns;
+                resplen = n;
+            }
+
+            LOG(DEBUG) << __func__ << ": got answer:";
+            res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+
+            if (cache_status == RESOLV_CACHE_NOTFOUND) {
+                _resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
+            }
+            /*
+             * If we have temporarily opened a virtual circuit,
+             * or if we haven't been asked to keep a socket open,
+             * close the socket.
+             */
+            if ((v_circuit && (statp->options & RES_USEVC) == 0U) ||
+                (statp->options & RES_STAYOPEN) == 0U) {
+                res_nclose(statp);
+            }
+            return (resplen);
+        next_ns:;
+        }  // for each ns
+    }  // for each retry
+    res_nclose(statp);
+    if (!v_circuit) {
+        if (!gotsomewhere) {
+            // TODO: Remove errno once callers stop using it
+            errno = ECONNREFUSED; /* no nameservers found */
+            terrno = ECONNREFUSED;
+        } else {
+            // TODO: Remove errno once callers stop using it
+            errno = ETIMEDOUT; /* no answer obtained */
+            terrno = ETIMEDOUT;
+        }
+    } else {
+        errno = terrno;
+    }
+    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
+    return -terrno;
+}
+
+/* Private */
+
+static int get_salen(const struct sockaddr* sa) {
+    if (sa->sa_family == AF_INET)
+        return (sizeof(struct sockaddr_in));
+    else if (sa->sa_family == AF_INET6)
+        return (sizeof(struct sockaddr_in6));
+    else
+        return (0); /* unknown, die on connect */
+}
+
+/*
+ * pick appropriate nsaddr_list for use.  see res_init() for initialization.
+ */
+static struct sockaddr* get_nsaddr(res_state statp, size_t n) {
+    if (!statp->nsaddr_list[n].sin_family && statp->_u._ext.ext) {
+        /*
+         * - statp->_u._ext.ext->nsaddrs[n] holds an address that is larger
+         *   than struct sockaddr, and
+         * - user code did not update statp->nsaddr_list[n].
+         */
+        return (struct sockaddr*) (void*) &statp->_u._ext.ext->nsaddrs[n];
+    } else {
+        /*
+         * - user code updated statp->nsaddr_list[n], or
+         * - statp->nsaddr_list[n] has the same content as
+         *   statp->_u._ext.ext->nsaddrs[n].
+         */
+        return (struct sockaddr*) (void*) &statp->nsaddr_list[n];
+    }
+}
+
+static struct timespec get_timeout(const res_state statp, const res_params* params, const int ns) {
+    int msec;
+    // Legacy algorithm which scales the timeout by nameserver number.
+    // For instance, with 4 nameservers: 5s, 2.5s, 5s, 10s
+    // This has no effect with 1 or 2 nameservers
+    msec = params->base_timeout_msec << ns;
+    if (ns > 0) {
+        msec /= statp->nscount;
+    }
+    // For safety, don't allow OEMs and experiments to configure a timeout shorter than 1s.
+    if (msec < 1000) {
+        msec = 1000;  // Use at least 1000ms
+    }
+    LOG(INFO) << __func__ << ": using timeout of " << msec << " msec";
+
+    struct timespec result;
+    result.tv_sec = msec / 1000;
+    result.tv_nsec = (msec % 1000) * 1000000;
+    return result;
+}
+
+static int send_vc(res_state statp, res_params* params, const u_char* buf, int buflen, u_char* ans,
+                   int anssiz, int* terrno, int ns, time_t* at, int* rcode, int* delay) {
+    *at = time(NULL);
+    *delay = 0;
+    const HEADER* hp = (const HEADER*) (const void*) buf;
+    HEADER* anhp = (HEADER*) (void*) ans;
+    struct sockaddr* nsap;
+    int nsaplen;
+    int truncating, connreset, n;
+    struct iovec iov[2];
+    u_char* cp;
+
+    LOG(INFO) << __func__ << ": using send_vc";
+
+    nsap = get_nsaddr(statp, (size_t) ns);
+    nsaplen = get_salen(nsap);
+
+    connreset = 0;
+same_ns:
+    truncating = 0;
+
+    struct timespec now = evNowTime();
+
+    /* Are we still talking to whom we want to talk to? */
+    if (statp->_vcsock >= 0 && (statp->_flags & RES_F_VC) != 0) {
+        struct sockaddr_storage peer;
+        socklen_t size = sizeof peer;
+        unsigned old_mark;
+        socklen_t mark_size = sizeof(old_mark);
+        if (getpeername(statp->_vcsock, (struct sockaddr*) (void*) &peer, &size) < 0 ||
+            !sock_eq((struct sockaddr*) (void*) &peer, nsap) ||
+            getsockopt(statp->_vcsock, SOL_SOCKET, SO_MARK, &old_mark, &mark_size) < 0 ||
+            old_mark != statp->_mark) {
+            res_nclose(statp);
+            statp->_flags &= ~RES_F_VC;
+        }
+    }
+
+    if (statp->_vcsock < 0 || (statp->_flags & RES_F_VC) == 0) {
+        if (statp->_vcsock >= 0) res_nclose(statp);
+
+        statp->_vcsock = socket(nsap->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        if (statp->_vcsock < 0) {
+            switch (errno) {
+                case EPROTONOSUPPORT:
+                case EPFNOSUPPORT:
+                case EAFNOSUPPORT:
+                    Perror(statp, "socket(vc)", errno);
+                    return 0;
+                default:
+                    *terrno = errno;
+                    Perror(statp, "socket(vc)", errno);
+                    return -1;
+            }
+        }
+        fchown(statp->_vcsock, AID_DNS, -1);
+        if (statp->_mark != MARK_UNSET) {
+            if (setsockopt(statp->_vcsock, SOL_SOCKET, SO_MARK, &statp->_mark,
+                           sizeof(statp->_mark)) < 0) {
+                *terrno = errno;
+                Perror(statp, "setsockopt", errno);
+                return -1;
+            }
+        }
+        errno = 0;
+        if (random_bind(statp->_vcsock, nsap->sa_family) < 0) {
+            *terrno = errno;
+            Aerror(statp, "bind/vc", errno, nsap, nsaplen);
+            res_nclose(statp);
+            return (0);
+        }
+        if (connect_with_timeout(statp->_vcsock, nsap, (socklen_t) nsaplen,
+                                 get_timeout(statp, params, ns)) < 0) {
+            *terrno = errno;
+            Aerror(statp, "connect/vc", errno, nsap, nsaplen);
+            res_nclose(statp);
+            /*
+             * The way connect_with_timeout() is implemented prevents us from reliably
+             * determining whether this was really a timeout or e.g. ECONNREFUSED. Since
+             * currently both cases are handled in the same way, there is no need to
+             * change this (yet). If we ever need to reliably distinguish between these
+             * cases, both connect_with_timeout() and retrying_poll() need to be
+             * modified, though.
+             */
+            *rcode = RCODE_TIMEOUT;
+            return (0);
+        }
+        statp->_flags |= RES_F_VC;
+    }
+
+    /*
+     * Send length & message
+     */
+    uint16_t len = htons(static_cast<uint16_t>(buflen));
+    iov[0] = evConsIovec(&len, INT16SZ);
+    iov[1] = evConsIovec((void*) buf, (size_t) buflen);
+    if (writev(statp->_vcsock, iov, 2) != (INT16SZ + buflen)) {
+        *terrno = errno;
+        Perror(statp, "write failed", errno);
+        res_nclose(statp);
+        return (0);
+    }
+    /*
+     * Receive length & response
+     */
+read_len:
+    cp = ans;
+    len = INT16SZ;
+    while ((n = read(statp->_vcsock, (char*) cp, (size_t) len)) > 0) {
+        cp += n;
+        if ((len -= n) == 0) break;
+    }
+    if (n <= 0) {
+        *terrno = errno;
+        Perror(statp, "read failed", errno);
+        res_nclose(statp);
+        /*
+         * A long running process might get its TCP
+         * connection reset if the remote server was
+         * restarted.  Requery the server instead of
+         * trying a new one.  When there is only one
+         * server, this means that a query might work
+         * instead of failing.  We only allow one reset
+         * per query to prevent looping.
+         */
+        if (*terrno == ECONNRESET && !connreset) {
+            connreset = 1;
+            res_nclose(statp);
+            goto same_ns;
+        }
+        res_nclose(statp);
+        return (0);
+    }
+    uint16_t resplen = ntohs(*reinterpret_cast<const uint16_t*>(ans));
+    if (resplen > anssiz) {
+        LOG(DEBUG) << __func__ << ": response truncated";
+        truncating = 1;
+        len = anssiz;
+    } else
+        len = resplen;
+    if (len < HFIXEDSZ) {
+        /*
+         * Undersized message.
+         */
+        LOG(DEBUG) << __func__ << ": undersized: " << len;
+        *terrno = EMSGSIZE;
+        res_nclose(statp);
+        return (0);
+    }
+    cp = ans;
+    while (len != 0 && (n = read(statp->_vcsock, (char*) cp, (size_t) len)) > 0) {
+        cp += n;
+        len -= n;
+    }
+    if (n <= 0) {
+        *terrno = errno;
+        Perror(statp, "read(vc)", errno);
+        res_nclose(statp);
+        return (0);
+    }
+
+    if (truncating) {
+        /*
+         * Flush rest of answer so connection stays in synch.
+         */
+        anhp->tc = 1;
+        len = resplen - anssiz;
+        while (len != 0) {
+            char junk[PACKETSZ];
+
+            n = read(statp->_vcsock, junk, (len > sizeof junk) ? sizeof junk : len);
+            if (n > 0)
+                len -= n;
+            else
+                break;
+        }
+    }
+    /*
+     * If the calling application has bailed out of
+     * a previous call and failed to arrange to have
+     * the circuit closed or the server has got
+     * itself confused, then drop the packet and
+     * wait for the correct one.
+     */
+    if (hp->id != anhp->id) {
+        LOG(DEBUG) << __func__ << ": ld answer (unexpected):";
+        res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+        goto read_len;
+    }
+
+    /*
+     * All is well, or the error is fatal.  Signal that the
+     * next nameserver ought not be tried.
+     */
+    if (resplen > 0) {
+        struct timespec done = evNowTime();
+        *delay = _res_stats_calculate_rtt(&done, &now);
+        *rcode = anhp->rcode;
+    }
+    return (resplen);
+}
+
+/* return -1 on error (errno set), 0 on success */
+static int connect_with_timeout(int sock, const struct sockaddr* nsap, socklen_t salen,
+                                const struct timespec timeout) {
+    int res, origflags;
+
+    origflags = fcntl(sock, F_GETFL, 0);
+    fcntl(sock, F_SETFL, origflags | O_NONBLOCK);
+
+    res = connect(sock, nsap, salen);
+    if (res < 0 && errno != EINPROGRESS) {
+        res = -1;
+        goto done;
+    }
+    if (res != 0) {
+        struct timespec now = evNowTime();
+        struct timespec finish = evAddTime(now, timeout);
+        LOG(INFO) << __func__ << ": " << sock << " send_vc";
+        res = retrying_poll(sock, POLLIN | POLLOUT, &finish);
+        if (res <= 0) {
+            res = -1;
+        }
+    }
+done:
+    fcntl(sock, F_SETFL, origflags);
+    LOG(INFO) << __func__ << ": " << sock << " connect_with_const timeout returning " << res;
+    return res;
+}
+
+static int retrying_poll(const int sock, const short events, const struct timespec* finish) {
+    struct timespec now, timeout;
+
+retry:
+    LOG(INFO) << __func__ << ": " << sock << " retrying_poll";
+
+    now = evNowTime();
+    if (evCmpTime(*finish, now) > 0)
+        timeout = evSubTime(*finish, now);
+    else
+        timeout = evConsTime(0L, 0L);
+    struct pollfd fds = {.fd = sock, .events = events};
+    int n = ppoll(&fds, 1, &timeout, /*sigmask=*/NULL);
+    if (n == 0) {
+        LOG(INFO) << __func__ << ": " << sock << "retrying_poll timeout";
+        errno = ETIMEDOUT;
+        return 0;
+    }
+    if (n < 0) {
+        if (errno == EINTR) goto retry;
+        PLOG(INFO) << __func__ << ": " << sock << " retrying_poll failed";
+        return n;
+    }
+    if (fds.revents & (POLLIN | POLLOUT | POLLERR)) {
+        int error;
+        socklen_t len = sizeof(error);
+        if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error) {
+            errno = error;
+            PLOG(INFO) << __func__ << ": " << sock << " retrying_poll getsockopt failed";
+            return -1;
+        }
+    }
+    LOG(INFO) << __func__ << ": " << sock << " retrying_poll returning " << n;
+    return n;
+}
+
+static int send_dg(res_state statp, res_params* params, const u_char* buf, int buflen, u_char* ans,
+                   int anssiz, int* terrno, int ns, int* v_circuit, int* gotsomewhere, time_t* at,
+                   int* rcode, int* delay) {
+    *at = time(NULL);
+    *delay = 0;
+    const HEADER* hp = (const HEADER*) (const void*) buf;
+    HEADER* anhp = (HEADER*) (void*) ans;
+    const struct sockaddr* nsap;
+    int nsaplen;
+    struct timespec now, timeout, finish, done;
+    struct sockaddr_storage from;
+    socklen_t fromlen;
+    int resplen, n, s;
+
+    nsap = get_nsaddr(statp, (size_t) ns);
+    nsaplen = get_salen(nsap);
+    if (statp->_u._ext.nssocks[ns] == -1) {
+        statp->_u._ext.nssocks[ns] = socket(nsap->sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+        if (statp->_u._ext.nssocks[ns] < 0) {
+            switch (errno) {
+                case EPROTONOSUPPORT:
+                case EPFNOSUPPORT:
+                case EAFNOSUPPORT:
+                    Perror(statp, "socket(dg)", errno);
+                    return (0);
+                default:
+                    *terrno = errno;
+                    Perror(statp, "socket(dg)", errno);
+                    return (-1);
+            }
+        }
+
+        fchown(statp->_u._ext.nssocks[ns], AID_DNS, -1);
+        if (statp->_mark != MARK_UNSET) {
+            if (setsockopt(statp->_u._ext.nssocks[ns], SOL_SOCKET, SO_MARK, &(statp->_mark),
+                           sizeof(statp->_mark)) < 0) {
+                res_nclose(statp);
+                return -1;
+            }
+        }
+#ifndef CANNOT_CONNECT_DGRAM
+        /*
+         * On a 4.3BSD+ machine (client and server,
+         * actually), sending to a nameserver datagram
+         * port with no nameserver will cause an
+         * ICMP port unreachable message to be returned.
+         * If our datagram socket is "connected" to the
+         * server, we get an ECONNREFUSED error on the next
+         * socket operation, and select returns if the
+         * error message is received.  We can thus detect
+         * the absence of a nameserver without timing out.
+         */
+        if (random_bind(statp->_u._ext.nssocks[ns], nsap->sa_family) < 0) {
+            Aerror(statp, "bind(dg)", errno, nsap, nsaplen);
+            res_nclose(statp);
+            return (0);
+        }
+        if (connect(statp->_u._ext.nssocks[ns], nsap, (socklen_t) nsaplen) < 0) {
+            Aerror(statp, "connect(dg)", errno, nsap, nsaplen);
+            res_nclose(statp);
+            return (0);
+        }
+#endif /* !CANNOT_CONNECT_DGRAM */
+        LOG(DEBUG) << __func__ << ": new DG socket";
+    }
+    s = statp->_u._ext.nssocks[ns];
+#ifndef CANNOT_CONNECT_DGRAM
+    if (send(s, (const char*) buf, (size_t) buflen, 0) != buflen) {
+        Perror(statp, "send", errno);
+        res_nclose(statp);
+        return 0;
+    }
+#else  /* !CANNOT_CONNECT_DGRAM */
+    if (sendto(s, (const char*) buf, buflen, 0, nsap, nsaplen) != buflen) {
+        Aerror(statp, "sendto", errno, nsap, nsaplen);
+        res_nclose(statp);
+        return 0;
+    }
+#endif /* !CANNOT_CONNECT_DGRAM */
+
+    // Wait for reply.
+    timeout = get_timeout(statp, params, ns);
+    now = evNowTime();
+    finish = evAddTime(now, timeout);
+retry:
+    n = retrying_poll(s, POLLIN, &finish);
+
+    if (n == 0) {
+        *rcode = RCODE_TIMEOUT;
+        LOG(DEBUG) << __func__ << ": timeout";
+        *gotsomewhere = 1;
+        return 0;
+    }
+    if (n < 0) {
+        Perror(statp, "poll", errno);
+        res_nclose(statp);
+        return 0;
+    }
+    errno = 0;
+    fromlen = sizeof(from);
+    resplen = recvfrom(s, (char*) ans, (size_t) anssiz, 0, (struct sockaddr*) (void*) &from,
+                       &fromlen);
+    if (resplen <= 0) {
+        Perror(statp, "recvfrom", errno);
+        res_nclose(statp);
+        return 0;
+    }
+    *gotsomewhere = 1;
+    if (resplen < HFIXEDSZ) {
+        /*
+         * Undersized message.
+         */
+        LOG(DEBUG) << __func__ << ": undersized: " << resplen;
+        *terrno = EMSGSIZE;
+        res_nclose(statp);
+        return 0;
+    }
+    if (hp->id != anhp->id) {
+        /*
+         * response from old query, ignore it.
+         * XXX - potential security hazard could
+         *	 be detected here.
+         */
+        LOG(DEBUG) << __func__ << ": old answer:";
+        res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+        goto retry;
+    }
+    if (!(statp->options & RES_INSECURE1) &&
+        !res_ourserver_p(statp, (struct sockaddr*) (void*) &from)) {
+        /*
+         * response from wrong server? ignore it.
+         * XXX - potential security hazard could
+         *	 be detected here.
+         */
+        LOG(DEBUG) << __func__ << ": not our server:";
+        res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+        goto retry;
+    }
+    if (anhp->rcode == FORMERR && (statp->options & RES_USE_EDNS0) != 0U) {
+        /*
+         * Do not retry if the server do not understand EDNS0.
+         * The case has to be captured here, as FORMERR packet do not
+         * carry query section, hence res_queriesmatch() returns 0.
+         */
+        LOG(DEBUG) << __func__ << ": server rejected query with EDNS0:";
+        res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+        /* record the error */
+        statp->_flags |= RES_F_EDNS0ERR;
+        res_nclose(statp);
+        return 0;
+    }
+    if (!(statp->options & RES_INSECURE2) &&
+        !res_queriesmatch(buf, buf + buflen, ans, ans + anssiz)) {
+        /*
+         * response contains wrong query? ignore it.
+         * XXX - potential security hazard could
+         *	 be detected here.
+         */
+        LOG(DEBUG) << __func__ << ": wrong query name:";
+        res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+        goto retry;
+    }
+    done = evNowTime();
+    *delay = _res_stats_calculate_rtt(&done, &now);
+    if (anhp->rcode == SERVFAIL || anhp->rcode == NOTIMP || anhp->rcode == REFUSED) {
+        LOG(DEBUG) << __func__ << ": server rejected query:";
+        res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
+        res_nclose(statp);
+        *rcode = anhp->rcode;
+        return 0;
+    }
+    if (!(statp->options & RES_IGNTC) && anhp->tc) {
+        /*
+         * To get the rest of answer,
+         * use TCP with same server.
+         */
+        LOG(DEBUG) << __func__ << ": truncated answer";
+        *v_circuit = 1;
+        res_nclose(statp);
+        return 1;
+    }
+    /*
+     * All is well, or the error is fatal.  Signal that the
+     * next nameserver ought not be tried.
+     */
+    if (resplen > 0) {
+        *rcode = anhp->rcode;
+    }
+    return resplen;
+}
+
+static void Aerror(const res_state statp, const char* string, int error,
+                   const struct sockaddr* address, int alen) {
+    const int save = errno;
+    char hbuf[NI_MAXHOST];
+    char sbuf[NI_MAXSERV];
+    constexpr int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+
+    if ((statp->options & RES_DEBUG) != 0U) {
+        if (getnameinfo(address, (socklen_t) alen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
+                        niflags)) {
+            strncpy(hbuf, "?", sizeof(hbuf) - 1);
+            hbuf[sizeof(hbuf) - 1] = '\0';
+            strncpy(sbuf, "?", sizeof(sbuf) - 1);
+            sbuf[sizeof(sbuf) - 1] = '\0';
+        }
+        LOG(DEBUG) << __func__ << ": " << string << " ([" << hbuf << "]." << sbuf
+                   << "): " << strerror(error);
+    }
+    errno = save;
+}
+
+static void Perror(const res_state statp, const char* string, int error) {
+    if ((statp->options & RES_DEBUG) != 0U) {
+        LOG(DEBUG) << __func__ << ": " << string << ": " << strerror(error);
+    }
+}
+
+static int sock_eq(struct sockaddr* a, struct sockaddr* b) {
+    struct sockaddr_in *a4, *b4;
+    struct sockaddr_in6 *a6, *b6;
+
+    if (a->sa_family != b->sa_family) return 0;
+    switch (a->sa_family) {
+        case AF_INET:
+            a4 = (struct sockaddr_in*) (void*) a;
+            b4 = (struct sockaddr_in*) (void*) b;
+            return a4->sin_port == b4->sin_port && a4->sin_addr.s_addr == b4->sin_addr.s_addr;
+        case AF_INET6:
+            a6 = (struct sockaddr_in6*) (void*) a;
+            b6 = (struct sockaddr_in6*) (void*) b;
+            return a6->sin6_port == b6->sin6_port &&
+#ifdef HAVE_SIN6_SCOPE_ID
+                   a6->sin6_scope_id == b6->sin6_scope_id &&
+#endif
+                   IN6_ARE_ADDR_EQUAL(&a6->sin6_addr, &b6->sin6_addr);
+        default:
+            return 0;
+    }
+}
+
+static int res_tls_send(res_state statp, const Slice query, const Slice answer, int* rcode,
+                        bool* fallback) {
+    int resplen = 0;
+    const unsigned netId = statp->netid;
+    const unsigned mark = statp->_mark;
+
+    PrivateDnsStatus privateDnsStatus = gPrivateDnsConfiguration.getStatus(netId);
+
+    if (privateDnsStatus.mode == PrivateDnsMode::OFF) {
+        *fallback = true;
+        return -1;
+    }
+
+    if (privateDnsStatus.validatedServers.empty()) {
+        if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
+            *fallback = true;
+            return -1;
+        } else {
+            // Sleep and iterate some small number of times checking for the
+            // arrival of resolved and validated server IP addresses, instead
+            // of returning an immediate error.
+            // This is needed because as soon as a network becomes the default network, apps will
+            // send DNS queries on that network. If no servers have yet validated, and we do not
+            // block those queries, they would immediately fail, causing application-visible errors.
+            // Note that this can happen even before the network validates, since an unvalidated
+            // network can become the default network if no validated networks are available.
+            //
+            // TODO: see if there is a better way to address this problem, such as buffering the
+            // queries in a queue or only blocking queries for the first few seconds after a default
+            // network change.
+            for (int i = 0; i < 42; i++) {
+                std::this_thread::sleep_for(std::chrono::milliseconds(100));
+                if (!gPrivateDnsConfiguration.getStatus(netId).validatedServers.empty()) {
+                    privateDnsStatus = gPrivateDnsConfiguration.getStatus(netId);
+                    break;
+                }
+            }
+            if (privateDnsStatus.validatedServers.empty()) {
+                return -1;
+            }
+        }
+    }
+
+    LOG(INFO) << __func__ << ": performing query over TLS";
+
+    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, mark, query,
+                                                  answer, &resplen);
+
+    LOG(INFO) << __func__ << ": TLS query result: " << static_cast<int>(response);
+
+    if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
+        // In opportunistic mode, handle falling back to cleartext in some
+        // cases (DNS shouldn't fail if a validated opportunistic mode server
+        // becomes unreachable for some reason).
+        switch (response) {
+            case DnsTlsTransport::Response::success:
+                *rcode = reinterpret_cast<HEADER*>(answer.base())->rcode;
+                return resplen;
+            case DnsTlsTransport::Response::network_error:
+                // No need to set the error timeout here since it will fallback to UDP.
+            case DnsTlsTransport::Response::internal_error:
+                // Note: this will cause cleartext queries to be emitted, with
+                // all of the EDNS0 goodness enabled. Fingers crossed.  :-/
+                *fallback = true;
+                [[fallthrough]];
+            default:
+                return -1;
+        }
+    } else {
+        // Strict mode
+        switch (response) {
+            case DnsTlsTransport::Response::success:
+                *rcode = reinterpret_cast<HEADER*>(answer.base())->rcode;
+                return resplen;
+            case DnsTlsTransport::Response::network_error:
+                // This case happens when the query stored in DnsTlsTransport is expired since
+                // either 1) the query has been tried for 3 times but no response or 2) fail to
+                // establish the connection with the server.
+                *rcode = RCODE_TIMEOUT;
+                [[fallthrough]];
+            default:
+                return -1;
+        }
+    }
+}
+
+int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags) {
+    res_state res = res_get_state();
+    res_setnetcontext(res, netContext);
+    _resolv_populate_res_for_net(res);
+    *rcode = NOERROR;
+    return res_nsend(res, msg, msgLen, ans, ansLen, rcode, flags);
+}
diff --git a/server/binder/android/net/UidRange.aidl b/resolv/res_send.h
similarity index 62%
copy from server/binder/android/net/UidRange.aidl
copy to resolv/res_send.h
index 55747d0..7fc77cb 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/resolv/res_send.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package android.net;
+#pragma once
 
-/**
- * An inclusive range of UIDs.
- *
- * {@hide}
- */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+#include "netd_resolv/resolv.h"  // struct android_net_context
+
+// Query dns with raw msg
+int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags);
diff --git a/resolv/res_state.cpp b/resolv/res_state.cpp
new file mode 100644
index 0000000..26febff
--- /dev/null
+++ b/resolv/res_state.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define LOG_TAG "res_state"
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h> /* for gettid() */
+
+#include <android-base/logging.h>
+
+#include "resolv_cache.h"
+#include "resolv_private.h"
+
+typedef struct {
+    // TODO: Have one __res_state per network so we don't have to repopulate frequently.
+    struct __res_state _nres[1];
+    struct res_static _rstatic[1];
+} _res_thread;
+
+static _res_thread* res_thread_alloc(void) {
+    _res_thread* rt = (_res_thread*) calloc(1, sizeof(*rt));
+
+    if (rt) {
+        memset(rt->_rstatic, 0, sizeof rt->_rstatic);
+    }
+    return rt;
+}
+
+static void res_static_done(struct res_static* rs) {
+    /* fortunately, there is nothing to do here, since the
+     * points in h_addr_ptrs and host_aliases should all
+     * point to 'hostbuf'
+     */
+    if (rs->hostf) { /* should not happen in theory, but just be safe */
+        fclose(rs->hostf);
+        rs->hostf = NULL;
+    }
+    free(rs->servent.s_aliases);
+}
+
+static void res_thread_free(void* _rt) {
+    _res_thread* rt = (_res_thread*) _rt;
+
+    LOG(VERBOSE) << __func__ << ": rt=" << rt << " for thread=" << gettid();
+
+    res_static_done(rt->_rstatic);
+    res_ndestroy(rt->_nres);
+    free(rt);
+}
+
+static pthread_key_t _res_key;
+
+__attribute__((constructor)) static void __res_key_init() {
+    pthread_key_create(&_res_key, res_thread_free);
+}
+
+static _res_thread* res_thread_get(void) {
+    _res_thread* rt = (_res_thread*) pthread_getspecific(_res_key);
+    if (rt != NULL) {
+        return rt;
+    }
+
+    /* It is the first time this function is called in this thread,
+     * we need to create a new thread-specific DNS resolver state. */
+    rt = res_thread_alloc();
+    if (rt == NULL) {
+        return NULL;
+    }
+    pthread_setspecific(_res_key, rt);
+
+    /* Reset the state, note that res_ninit() can now properly reset
+     * an existing state without leaking memory.
+     */
+    LOG(VERBOSE) << __func__ << ": tid=" << gettid() << ", rt=" << rt
+                 << " setting DNS state (options=" << rt->_nres->options << ")";
+    if (res_ninit(rt->_nres) < 0) {
+        /* This should not happen */
+        LOG(VERBOSE) << __func__ << ": tid=" << gettid() << " rt=" << rt
+                     << ", res_ninit() returned < 0";
+        res_thread_free(rt);
+        pthread_setspecific(_res_key, NULL);
+        return NULL;
+    }
+    return rt;
+}
+
+struct __res_state _nres;
+
+res_state res_get_state(void) {
+    _res_thread* rt = res_thread_get();
+
+    return rt ? rt->_nres : NULL;
+}
+
+res_static* res_get_static(void) {
+    _res_thread* rt = res_thread_get();
+
+    return rt ? rt->_rstatic : NULL;
+}
diff --git a/resolv/res_state_ext.h b/resolv/res_state_ext.h
new file mode 100644
index 0000000..6ed3398
--- /dev/null
+++ b/resolv/res_state_ext.h
@@ -0,0 +1,22 @@
+/*	$NetBSD: res_private.h,v 1.1.1.1 2004/05/20 17:18:54 christos Exp $	*/
+
+#ifndef NETD_RES_STATE_EXT_H
+#define NETD_RES_STATE_EXT_H
+
+#include "resolv_private.h"
+
+// TODO: consider inlining into res_state
+struct res_state_ext {
+    sockaddr_union nsaddrs[MAXNS];
+    struct sort_list {
+        int af;
+        union {
+            struct in_addr ina;
+            struct in6_addr in6a;
+        } addr, mask;
+    } sort_list[MAXRESOLVSORT];
+    char nsuffix[64];
+    char nsuffix2[64];
+};
+
+#endif  // NETD_RES_STATE_EXT_H
diff --git a/resolv/res_stats.cpp b/resolv/res_stats.cpp
new file mode 100644
index 0000000..ed6f084
--- /dev/null
+++ b/resolv/res_stats.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "res_stats"
+
+#include <arpa/nameser.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <android-base/logging.h>
+
+#include "netd_resolv/stats.h"
+
+
+// Calculate the round-trip-time from start time t0 and end time t1.
+int _res_stats_calculate_rtt(const timespec* t1, const timespec* t0) {
+    // Divide ns by one million to get ms, multiply s by thousand to get ms (obvious)
+    long ms0 = t0->tv_sec * 1000 + t0->tv_nsec / 1000000;
+    long ms1 = t1->tv_sec * 1000 + t1->tv_nsec / 1000000;
+    return (int) (ms1 - ms0);
+}
+
+// Create a sample for calculating server reachability statistics.
+void _res_stats_set_sample(res_sample* sample, time_t now, int rcode, int rtt) {
+    LOG(INFO) << __func__ << ": rcode = " << rcode << ", sec = " << rtt;
+    sample->at = now;
+    sample->rcode = rcode;
+    sample->rtt = rtt;
+}
+
+/* Clears all stored samples for the given server. */
+void _res_stats_clear_samples(res_stats* stats) {
+    stats->sample_count = stats->sample_next = 0;
+}
+
+/* Aggregates the reachability statistics for the given server based on on the stored samples. */
+void android_net_res_stats_aggregate(res_stats* stats, int* successes, int* errors, int* timeouts,
+                                     int* internal_errors, int* rtt_avg, time_t* last_sample_time) {
+    int s = 0;   // successes
+    int e = 0;   // errors
+    int t = 0;   // timouts
+    int ie = 0;  // internal errors
+    long rtt_sum = 0;
+    time_t last = 0;
+    int rtt_count = 0;
+    for (int i = 0; i < stats->sample_count; ++i) {
+        // Treat everything as an error that the code in send_dg() already considers a
+        // rejection by the server, i.e. SERVFAIL, NOTIMP and REFUSED. Assume that NXDOMAIN
+        // and NOTAUTH can actually occur for user queries. NOERROR with empty answer section
+        // is not treated as an error here either. FORMERR seems to sometimes be returned by
+        // some versions of BIND in response to DNSSEC or EDNS0. Whether to treat such responses
+        // as an indication of a broken server is unclear, though. For now treat such responses,
+        // as well as unknown codes as errors.
+        switch (stats->samples[i].rcode) {
+            case NOERROR:
+            case NOTAUTH:
+            case NXDOMAIN:
+                ++s;
+                rtt_sum += stats->samples[i].rtt;
+                ++rtt_count;
+                break;
+            case RCODE_TIMEOUT:
+                ++t;
+                break;
+            case RCODE_INTERNAL_ERROR:
+                ++ie;
+                break;
+            case SERVFAIL:
+            case NOTIMP:
+            case REFUSED:
+            default:
+                ++e;
+                break;
+        }
+    }
+    *successes = s;
+    *errors = e;
+    *timeouts = t;
+    *internal_errors = ie;
+    /* If there was at least one successful sample, calculate average RTT. */
+    if (rtt_count) {
+        *rtt_avg = rtt_sum / rtt_count;
+    } else {
+        *rtt_avg = -1;
+    }
+    /* If we had at least one sample, populate last sample time. */
+    if (stats->sample_count > 0) {
+        if (stats->sample_next > 0) {
+            last = stats->samples[stats->sample_next - 1].at;
+        } else {
+            last = stats->samples[stats->sample_count - 1].at;
+        }
+    }
+    *last_sample_time = last;
+}
+
+// Returns true if the server is considered usable, i.e. if the success rate is not lower than the
+// threshold for the stored stored samples. If not enough samples are stored, the server is
+// considered usable.
+static bool res_stats_usable_server(const res_params* params, res_stats* stats) {
+    int successes = -1;
+    int errors = -1;
+    int timeouts = -1;
+    int internal_errors = -1;
+    int rtt_avg = -1;
+    time_t last_sample_time = 0;
+    android_net_res_stats_aggregate(stats, &successes, &errors, &timeouts, &internal_errors,
+                                    &rtt_avg, &last_sample_time);
+    if (successes >= 0 && errors >= 0 && timeouts >= 0) {
+        int total = successes + errors + timeouts;
+        LOG(INFO) << __func__ << ": NS stats: S " << successes << " + E " << errors << " + T "
+                  << timeouts << " + I " << internal_errors << " = " << total
+                  << ", rtt = " << rtt_avg << ", min_samples = " << unsigned(params->min_samples);
+        if (total >= params->min_samples && (errors > 0 || timeouts > 0)) {
+            int success_rate = successes * 100 / total;
+            LOG(INFO) << __func__ << ": success rate " << success_rate;
+            if (success_rate < params->success_threshold) {
+                time_t now = time(NULL);
+                if (now - last_sample_time > params->sample_validity) {
+                    // Note: It might be worth considering to expire old servers after their expiry
+                    // date has been reached, however the code for returning the ring buffer to its
+                    // previous non-circular state would induce additional complexity.
+                    LOG(INFO) << __func__ << ": samples stale, retrying server";
+                    _res_stats_clear_samples(stats);
+                } else {
+                    LOG(INFO) << __func__ << ": too many resolution errors, ignoring server";
+                    return 0;
+                }
+            }
+        }
+    }
+    return 1;
+}
+
+int android_net_res_stats_get_usable_servers(const res_params* params, res_stats stats[],
+                                             int nscount, bool usable_servers[]) {
+    unsigned usable_servers_found = 0;
+    for (int ns = 0; ns < nscount; ns++) {
+        bool usable = res_stats_usable_server(params, &stats[ns]);
+        if (usable) {
+            ++usable_servers_found;
+        }
+        usable_servers[ns] = usable;
+    }
+    // If there are no usable servers, consider all of them usable.
+    // TODO: Explore other possibilities, such as enabling only the best N servers, etc.
+    if (usable_servers_found == 0) {
+        for (int ns = 0; ns < nscount; ns++) {
+            usable_servers[ns] = true;
+        }
+    }
+    return (usable_servers_found == 0) ? nscount : usable_servers_found;
+}
diff --git a/resolv/resolv_cache.h b/resolv/resolv_cache.h
new file mode 100644
index 0000000..0056076
--- /dev/null
+++ b/resolv/resolv_cache.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "netd_resolv/resolv.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+struct __res_state;
+
+/* sets the name server addresses to the provided res_state structure. The
+ * name servers are retrieved from the cache which is associated
+ * with the network to which the res_state structure is associated */
+void _resolv_populate_res_for_net(struct __res_state* statp);
+
+std::vector<unsigned> resolv_list_caches();
+
+typedef enum {
+    RESOLV_CACHE_UNSUPPORTED, /* the cache can't handle that kind of queries */
+                              /* or the answer buffer is too small */
+    RESOLV_CACHE_NOTFOUND,    /* the cache doesn't know about this query */
+    RESOLV_CACHE_FOUND,       /* the cache found the answer */
+    RESOLV_CACHE_SKIP         /* Don't do anything on cache */
+} ResolvCacheStatus;
+
+ResolvCacheStatus _resolv_cache_lookup(unsigned netid, const void* query, int querylen,
+                                       void* answer, int answersize, int* answerlen,
+                                       uint32_t flags);
+
+/* add a (query,answer) to the cache, only call if _resolv_cache_lookup
+ * did return RESOLV_CACHE_NOTFOUND
+ */
+void _resolv_cache_add(unsigned netid, const void* query, int querylen, const void* answer,
+                       int answerlen);
+
+/* Notify the cache a request failed */
+void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen, uint32_t flags);
+
+// Sets name servers for a given network.
+int resolv_set_nameservers_for_net(unsigned netid, const char** servers, int numservers,
+                                   const char* domains, const res_params* params);
+
+// Creates the cache associated with the given network.
+int resolv_create_cache_for_net(unsigned netid);
+
+// Deletes the cache associated with the given network.
+void resolv_delete_cache_for_net(unsigned netid);
diff --git a/resolv/resolv_private.h b/resolv/resolv_private.h
new file mode 100644
index 0000000..9fb0d48
--- /dev/null
+++ b/resolv/resolv_private.h
@@ -0,0 +1,240 @@
+/*	$NetBSD: resolv.h,v 1.31 2005/12/26 19:01:47 perry Exp $	*/
+
+/*
+ * Copyright (c) 1983, 1987, 1989
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ *	@(#)resolv.h	8.1 (Berkeley) 6/2/93
+ *	Id: resolv.h,v 1.7.2.11.4.2 2004/06/25 00:41:05 marka Exp
+ */
+
+#ifndef NETD_RESOLV_PRIVATE_H
+#define NETD_RESOLV_PRIVATE_H
+
+#include <android-base/logging.h>
+#include <net/if.h>
+#include <resolv.h>
+#include <time.h>
+#include <string>
+
+#include "netd_resolv/params.h"
+#include "netd_resolv/resolv.h"
+#include "netd_resolv/stats.h"
+#include "resolv_static.h"
+
+// Linux defines MAXHOSTNAMELEN as 64, while the domain name limit in
+// RFC 1034 and RFC 1035 is 255 octets.
+#ifdef MAXHOSTNAMELEN
+#undef MAXHOSTNAMELEN
+#endif
+#define MAXHOSTNAMELEN 256
+
+/*
+ * Global defines and variables for resolver stub.
+ */
+#define MAXDFLSRCH 3       /* # default domain levels to try */
+#define LOCALDOMAINPARTS 2 /* min levels in name that is "local" */
+
+#define RES_TIMEOUT 5000 /* min. milliseconds between retries */
+#define MAXRESOLVSORT 10  /* number of net to sort on */
+#define RES_MAXNDOTS 15   /* should reflect bit field size */
+#define RES_DFLRETRY 2    /* Default #/tries. */
+#define RES_MAXTIME 65535 /* Infinity, in milliseconds. */
+
+struct res_state_ext;
+
+struct __res_state {
+    unsigned netid;                        /* NetId: cache key and socket mark */
+    u_long options;                        /* option flags - see below. */
+    int nscount;                           /* number of name srvers */
+    struct sockaddr_in nsaddr_list[MAXNS]; /* address of name server */
+#define nsaddr nsaddr_list[0]              /* for backward compatibility */
+    u_short id;                            /* current message id */
+    char* dnsrch[MAXDNSRCH + 1];           /* components of domain to search */
+    char defdname[256];                    /* default domain (deprecated) */
+    unsigned ndots : 4;                    /* threshold for initial abs. query */
+    unsigned nsort : 4;                    /* number of elements in sort_list[] */
+    char unused[3];
+    struct {
+        struct in_addr addr;
+        uint32_t mask;
+    } sort_list[MAXRESOLVSORT];
+    unsigned _mark;       /* If non-0 SET_MARK to _mark on all request sockets */
+    int _vcsock;          /* PRIVATE: for res_send VC i/o */
+    u_int _flags;         /* PRIVATE: see below */
+    u_int _pad;           /* make _u 64 bit aligned */
+    union {
+        /* On an 32-bit arch this means 512b total. */
+        char pad[72 - 4 * sizeof(int) - 2 * sizeof(void*)];
+        struct {
+            uint16_t nscount;
+            uint16_t nstimes[MAXNS]; /* ms. */
+            int nssocks[MAXNS];
+            struct res_state_ext* ext; /* extention for IPv6 */
+        } _ext;
+    } _u;
+    struct res_static rstatic[1];
+    bool use_local_nameserver; /* DNS-over-TLS bypass */
+};
+
+typedef struct __res_state* res_state;
+
+/* Retrieve a local copy of the stats for the given netid. The buffer must have space for
+ * MAXNS __resolver_stats. Returns the revision id of the resolvers used.
+ */
+int resolv_cache_get_resolver_stats(unsigned netid, res_params* params, res_stats stats[MAXNS]);
+
+/* Add a sample to the shared struct for the given netid and server, provided that the
+ * revision_id of the stored servers has not changed.
+ */
+void _resolv_cache_add_resolver_stats_sample(unsigned netid, int revision_id, int ns,
+                                             const res_sample* sample, int max_samples);
+
+// Calculate the round-trip-time from start time t0 and end time t1.
+int _res_stats_calculate_rtt(const timespec* t1, const timespec* t0);
+
+// Create a sample for calculating server reachability statistics.
+void _res_stats_set_sample(res_sample* sample, time_t now, int rcode, int rtt);
+
+/* End of stats related definitions */
+
+// Flags for res_state->_flags
+#define RES_F_VC 0x00000001        // socket is TCP
+#define RES_F_EDNS0ERR 0x00000004  // EDNS0 caused errors
+
+/*
+ * Resolver options (keep these in synch with res_debug.c, please)
+ */
+#define RES_INIT 0x00000001           /* address initialized */
+#define RES_DEBUG 0x00000002          /* print debug messages */
+#define RES_AAONLY 0x00000004         /* authoritative answers only (!IMPL)*/
+#define RES_USEVC 0x00000008          /* use virtual circuit */
+#define RES_PRIMARY 0x00000010        /* query primary server only (!IMPL) */
+#define RES_IGNTC 0x00000020          /* ignore trucation errors */
+#define RES_RECURSE 0x00000040        /* recursion desired */
+#define RES_DEFNAMES 0x00000080       /* use default domain name */
+#define RES_STAYOPEN 0x00000100       /* Keep TCP socket open */
+#define RES_DNSRCH 0x00000200         /* search up local domain tree */
+#define RES_INSECURE1 0x00000400      /* type 1 security disabled */
+#define RES_INSECURE2 0x00000800      /* type 2 security disabled */
+#define RES_USE_INET6 0x00002000      /* use/map IPv6 in gethostbyname() */
+#define RES_ROTATE 0x00004000         /* rotate ns list after each query */
+#define RES_NOCHECKNAME 0x00008000    /* do not check names for sanity. */
+#define RES_KEEPTSIG 0x00010000       /* do not strip TSIG records */
+#define RES_BLAST 0x00020000          /* blast all recursive servers */
+#define RES_NOTLDQUERY 0x00100000     /* don't unqualified name as a tld */
+#define RES_USE_DNSSEC 0x00200000     /* use DNSSEC using OK bit in OPT */
+/* #define RES_DEBUG2	0x00400000 */ /* nslookup internal */
+/* KAME extensions: use higher bit to avoid conflict with ISC use */
+#define RES_USE_DNAME 0x10000000  /* use DNAME */
+#define RES_USE_EDNS0 0x40000000  /* use EDNS0 if configured */
+#define RES_NO_NIBBLE2 0x80000000 /* disable alternate nibble lookup */
+
+#define RES_DEFAULT (RES_RECURSE | RES_DEFNAMES | RES_DNSRCH | RES_NO_NIBBLE2)
+
+
+/*
+ * Error code extending h_errno codes defined in bionic/libc/include/netdb.h.
+ *
+ * This error code, including legacy h_errno, is returned from res_nquery(), res_nsearch(),
+ * res_nquerydomain(), res_queryN(), res_searchN() and res_querydomainN() for DNS metrics.
+ *
+ * TODO: Consider mapping legacy and extended h_errno into a unified resolver error code mapping.
+ */
+#define NETD_RESOLV_H_ERRNO_EXT_TIMEOUT RCODE_TIMEOUT
+
+extern const char* const _res_opcodes[];
+
+/* Things involving an internal (static) resolver context. */
+struct __res_state* res_get_state(void);
+
+int res_hnok(const char*);
+int res_ownok(const char*);
+int res_mailok(const char*);
+int res_dnok(const char*);
+int dn_skipname(const u_char*, const u_char*);
+void putlong(uint32_t, u_char*);
+void putshort(uint16_t, u_char*);
+
+// Thread-unsafe functions returning pointers to static buffers :-(
+// TODO: switch all res_debug to std::string
+const char* p_class(int);
+const char* p_type(int);
+const char* p_rcode(int);
+const char* p_section(int, int);
+
+int res_nameinquery(const char*, int, int, const u_char*, const u_char*);
+int res_queriesmatch(const u_char*, const u_char*, const u_char*, const u_char*);
+/* Things involving a resolver context. */
+int res_ninit(res_state);
+void res_pquery(const u_char*, int);
+
+int res_nquery(res_state, const char*, int, int, u_char*, int, int*);
+int res_nsearch(res_state, const char*, int, int, u_char*, int, int*);
+int res_nquerydomain(res_state, const char*, const char*, int, int, u_char*, int, int*);
+int res_nmkquery(res_state, int, const char*, int, int, const u_char*, int, const u_char*, u_char*,
+                 int);
+int res_nsend(res_state, const u_char*, int, u_char*, int, int*, uint32_t);
+void res_nclose(res_state);
+int res_nopt(res_state, int, u_char*, int, int);
+int res_vinit(res_state, int);
+void res_ndestroy(res_state);
+void res_setservers(res_state, const sockaddr_union*, int);
+int res_getservers(res_state, sockaddr_union*, int);
+
+struct android_net_context; /* forward */
+void res_setnetcontext(res_state, const struct android_net_context*);
+
+int getaddrinfo_numeric(const char* hostname, const char* servname, addrinfo hints,
+                        addrinfo** result);
+
+// Helper function for converting h_errno to the error codes visible to netd
+int herrnoToAiErrno(int herrno);
+
+// switch resolver log severity
+android::base::LogSeverity logSeverityStrToEnum(const std::string& logSeverityStr);
+
+#endif  // NETD_RESOLV_PRIVATE_H
diff --git a/resolv/resolv_static.h b/resolv/resolv_static.h
new file mode 100644
index 0000000..b250892
--- /dev/null
+++ b/resolv/resolv_static.h
@@ -0,0 +1,33 @@
+#ifndef NETD_RESOLV_STATIC_H
+#define NETD_RESOLV_STATIC_H
+
+#include <netdb.h>
+#include <stdio.h>
+
+/* this structure contains all the variables that were declared
+ * 'static' in the original NetBSD resolver code.
+ *
+ * this caused vast amounts of crashes and memory corruptions
+ * when the resolver was being used by multiple threads.
+ *
+ * (note: the OpenBSD/FreeBSD resolver has similar 'issues')
+ */
+
+#define MAXALIASES 35
+#define MAXADDRS 35
+
+struct res_static {
+    char* h_addr_ptrs[MAXADDRS + 1];
+    char* host_aliases[MAXALIASES];
+    char hostbuf[8 * 1024];
+    u_int32_t host_addr[16 / sizeof(u_int32_t)]; /* IPv4 or IPv6 */
+    FILE* hostf;
+    int stayopen;
+    const char* servent_ptr;
+    struct servent servent;
+    struct hostent host;
+};
+
+res_static* res_get_static();
+
+#endif  // NETD_RESOLV_STATIC_H
diff --git a/resolv/resolver_test.cpp b/resolv/resolver_test.cpp
new file mode 100644
index 0000000..84537ca
--- /dev/null
+++ b/resolv/resolver_test.cpp
@@ -0,0 +1,3471 @@
+/*
+ * 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 requied 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 "resolv_integration_test"
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h> /* poll */
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <numeric>
+#include <thread>
+
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <android/multinetwork.h>  // ResNsendFlags
+#include <cutils/sockets.h>
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+#include <openssl/base64.h>
+#include <private/android_filesystem_config.h>
+#include <utils/Log.h>
+
+#include "NetdClient.h"
+#include "netid_client.h"  // NETID_UNSET
+#include "netd_resolv/params.h"  // MAX_NS
+
+#include "dns_responder/dns_responder.h"
+#include "dns_responder/dns_responder_client.h"
+#include "dns_responder/dns_tls_frontend.h"
+
+#include "NetdConstants.h"
+#include "ResolverStats.h"
+
+#include "android/net/IDnsResolver.h"
+#include "binder/IServiceManager.h"
+#include "netdutils/ResponseCode.h"
+#include "netdutils/SocketOption.h"
+
+// TODO: make this dynamic and stop depending on implementation details.
+constexpr int TEST_NETID = 30;
+// Valid VPN netId range is 100 ~ 65535
+constexpr int TEST_VPN_NETID = 65502;
+constexpr int MAXPACKET = (8 * 1024);
+
+// Semi-public Bionic hook used by the NDK (frameworks/base/native/android/net.c)
+// Tested here for convenience.
+extern "C" int android_getaddrinfofornet(const char* hostname, const char* servname,
+                                         const addrinfo* hints, unsigned netid, unsigned mark,
+                                         struct addrinfo** result);
+
+using android::base::ParseInt;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::net::ResolverStats;
+using android::netdutils::enableSockopt;
+using android::netdutils::ResponseCode;
+
+// TODO: move into libnetdutils?
+namespace {
+ScopedAddrinfo safe_getaddrinfo(const char* node, const char* service,
+                                const struct addrinfo* hints) {
+    addrinfo* result = nullptr;
+    if (getaddrinfo(node, service, hints, &result) != 0) {
+        result = nullptr;  // Should already be the case, but...
+    }
+    return ScopedAddrinfo(result);
+}
+}  // namespace
+
+class ResolverTest : public ::testing::Test {
+  protected:
+    struct DnsRecord {
+        std::string host_name;  // host name
+        ns_type type;           // record type
+        std::string addr;       // ipv4/v6 address
+    };
+
+    void SetUp() { mDnsClient.SetUp(); }
+    void TearDown() {
+        mDnsClient.TearDown();
+    }
+
+    bool GetResolverInfo(std::vector<std::string>* servers, std::vector<std::string>* domains,
+                         std::vector<std::string>* tlsServers, res_params* params,
+                         std::vector<ResolverStats>* stats,
+                         int* wait_for_pending_req_timeout_count) {
+        using android::net::IDnsResolver;
+        std::vector<int32_t> params32;
+        std::vector<int32_t> stats32;
+        std::vector<int32_t> wait_for_pending_req_timeout_count32{0};
+        auto rv = mDnsClient.resolvService()->getResolverInfo(
+                TEST_NETID, servers, domains, tlsServers, &params32, &stats32,
+                &wait_for_pending_req_timeout_count32);
+
+        if (!rv.isOk() ||
+            params32.size() != static_cast<size_t>(IDnsResolver::RESOLVER_PARAMS_COUNT)) {
+            return false;
+        }
+        *params = res_params{
+                .sample_validity = static_cast<uint16_t>(
+                        params32[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY]),
+                .success_threshold = static_cast<uint8_t>(
+                        params32[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD]),
+                .min_samples =
+                        static_cast<uint8_t>(params32[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES]),
+                .max_samples =
+                        static_cast<uint8_t>(params32[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES]),
+                .base_timeout_msec = params32[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC],
+                .retry_count = params32[IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT],
+        };
+        *wait_for_pending_req_timeout_count = wait_for_pending_req_timeout_count32[0];
+        return ResolverStats::decodeAll(stats32, stats);
+    }
+
+    static std::string ToString(const hostent* he) {
+        if (he == nullptr) return "<null>";
+        char buffer[INET6_ADDRSTRLEN];
+        if (!inet_ntop(he->h_addrtype, he->h_addr_list[0], buffer, sizeof(buffer))) {
+            return "<invalid>";
+        }
+        return buffer;
+    }
+
+    static std::string ToString(const addrinfo* ai) {
+        if (!ai)
+            return "<null>";
+        for (const auto* aip = ai ; aip != nullptr ; aip = aip->ai_next) {
+            char host[NI_MAXHOST];
+            int rv = getnameinfo(aip->ai_addr, aip->ai_addrlen, host, sizeof(host), nullptr, 0,
+                    NI_NUMERICHOST);
+            if (rv != 0)
+                return gai_strerror(rv);
+            return host;
+        }
+        return "<invalid>";
+    }
+
+    static std::string ToString(const ScopedAddrinfo& ai) { return ToString(ai.get()); }
+
+    static std::vector<std::string> ToStrings(const addrinfo* ai) {
+        std::vector<std::string> hosts;
+        if (!ai) {
+            hosts.push_back("<null>");
+            return hosts;
+        }
+        for (const auto* aip = ai; aip != nullptr; aip = aip->ai_next) {
+            char host[NI_MAXHOST];
+            int rv = getnameinfo(aip->ai_addr, aip->ai_addrlen, host, sizeof(host), nullptr, 0,
+                                 NI_NUMERICHOST);
+            if (rv != 0) {
+                hosts.clear();
+                hosts.push_back(gai_strerror(rv));
+                return hosts;
+            } else {
+                hosts.push_back(host);
+            }
+        }
+        if (hosts.empty()) hosts.push_back("<invalid>");
+        return hosts;
+    }
+
+    static std::vector<std::string> ToStrings(const ScopedAddrinfo& ai) {
+        return ToStrings(ai.get());
+    }
+
+    size_t GetNumQueries(const test::DNSResponder& dns, const char* name) const {
+        auto queries = dns.queries();
+        size_t found = 0;
+        for (const auto& p : queries) {
+            if (p.first == name) {
+                ++found;
+            }
+        }
+        return found;
+    }
+
+    size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type,
+                                const char* name) const {
+        auto queries = dns.queries();
+        size_t found = 0;
+        for (const auto& p : queries) {
+            if (p.second == type && p.first == name) {
+                ++found;
+            }
+        }
+        return found;
+    }
+
+    bool WaitForPrefix64Detected(int netId, int timeoutMs) {
+        constexpr int intervalMs = 2;
+        const int limit = timeoutMs / intervalMs;
+        for (int count = 0; count <= limit; ++count) {
+            std::string prefix;
+            auto rv = mDnsClient.resolvService()->getPrefix64(netId, &prefix);
+            if (rv.isOk()) {
+                return true;
+            }
+            usleep(intervalMs * 1000);
+        }
+        return false;
+    }
+
+    void RunGetAddrInfoStressTest_Binder(unsigned num_hosts, unsigned num_threads,
+            unsigned num_queries) {
+        std::vector<std::string> domains = { "example.com" };
+        std::vector<std::unique_ptr<test::DNSResponder>> dns;
+        std::vector<std::string> servers;
+        std::vector<DnsResponderClient::DnsResponderClient::Mapping> mappings;
+        ASSERT_NO_FATAL_FAILURE(mDnsClient.SetupMappings(num_hosts, domains, &mappings));
+        ASSERT_NO_FATAL_FAILURE(mDnsClient.SetupDNSServers(MAXNS, mappings, &dns, &servers));
+
+        ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, domains, kDefaultParams));
+
+        auto t0 = std::chrono::steady_clock::now();
+        std::vector<std::thread> threads(num_threads);
+        for (std::thread& thread : threads) {
+            thread = std::thread([&mappings, num_queries]() {
+                for (unsigned i = 0 ; i < num_queries ; ++i) {
+                    uint32_t ofs = arc4random_uniform(mappings.size());
+                    auto& mapping = mappings[ofs];
+                    addrinfo* result = nullptr;
+                    int rv = getaddrinfo(mapping.host.c_str(), nullptr, nullptr, &result);
+                    EXPECT_EQ(0, rv) << "error [" << rv << "] " << gai_strerror(rv);
+                    if (rv == 0) {
+                        std::string result_str = ToString(result);
+                        EXPECT_TRUE(result_str == mapping.ip4 || result_str == mapping.ip6)
+                            << "result='" << result_str << "', ip4='" << mapping.ip4
+                            << "', ip6='" << mapping.ip6;
+                    }
+                    if (result) {
+                        freeaddrinfo(result);
+                        result = nullptr;
+                    }
+                }
+            });
+        }
+
+        for (std::thread& thread : threads) {
+            thread.join();
+        }
+        auto t1 = std::chrono::steady_clock::now();
+        ALOGI("%u hosts, %u threads, %u queries, %Es", num_hosts, num_threads, num_queries,
+                std::chrono::duration<double>(t1 - t0).count());
+
+        std::vector<std::string> res_servers;
+        std::vector<std::string> res_domains;
+        std::vector<std::string> res_tls_servers;
+        res_params res_params;
+        std::vector<ResolverStats> res_stats;
+        int wait_for_pending_req_timeout_count;
+        ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                    &res_stats, &wait_for_pending_req_timeout_count));
+        EXPECT_EQ(0, wait_for_pending_req_timeout_count);
+    }
+
+    void StartDns(test::DNSResponder& dns, const std::vector<DnsRecord>& records) {
+        for (const auto& r : records) {
+            dns.addMapping(r.host_name, r.type, r.addr);
+        }
+
+        ASSERT_TRUE(dns.startServer());
+        dns.clearQueries();
+    }
+
+    DnsResponderClient mDnsClient;
+
+    static constexpr char kLocalHost[] = "localhost";
+    static constexpr char kLocalHostAddr[] = "127.0.0.1";
+    static constexpr char kIp6LocalHost[] = "ip6-localhost";
+    static constexpr char kIp6LocalHostAddr[] = "::1";
+    static constexpr char kHelloExampleCom[] = "hello.example.com.";
+};
+
+TEST_F(ResolverTest, GetHostByName) {
+    constexpr char nonexistent_host_name[] = "nonexistent.example.com.";
+
+    test::DNSResponder dns;
+    StartDns(dns, {{kHelloExampleCom, ns_type::ns_t_a, "1.2.3.3"}});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    const hostent* result;
+    result = gethostbyname("nonexistent");
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, nonexistent_host_name));
+    ASSERT_TRUE(result == nullptr);
+    ASSERT_EQ(HOST_NOT_FOUND, h_errno);
+
+    dns.clearQueries();
+    result = gethostbyname("hello");
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, kHelloExampleCom));
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+}
+
+TEST_F(ResolverTest, GetHostByName_cnames) {
+    constexpr char host_name[] = "host.example.com.";
+    size_t cnamecount = 0;
+    test::DNSResponder dns;
+
+    const std::vector<DnsRecord> records = {
+            {kHelloExampleCom, ns_type::ns_t_cname, "a.example.com."},
+            {"a.example.com.", ns_type::ns_t_cname, "b.example.com."},
+            {"b.example.com.", ns_type::ns_t_cname, "c.example.com."},
+            {"c.example.com.", ns_type::ns_t_cname, "d.example.com."},
+            {"d.example.com.", ns_type::ns_t_cname, "e.example.com."},
+            {"e.example.com.", ns_type::ns_t_cname, host_name},
+            {host_name, ns_type::ns_t_a, "1.2.3.3"},
+            {host_name, ns_type::ns_t_aaaa, "2001:db8::42"},
+    };
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    // using gethostbyname2() to resolve ipv4 hello.example.com. to 1.2.3.3
+    // Ensure the v4 address and cnames are correct
+    const hostent* result;
+    result = gethostbyname2("hello", AF_INET);
+    ASSERT_FALSE(result == nullptr);
+
+    for (int i = 0; result != nullptr && result->h_aliases[i] != nullptr; i++) {
+        std::string domain_name = records[i].host_name.substr(0, records[i].host_name.size() - 1);
+        EXPECT_EQ(result->h_aliases[i], domain_name);
+        cnamecount++;
+    }
+    // The size of "Non-cname type" record in DNS records is 2
+    ASSERT_EQ(cnamecount, records.size() - 2);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+    EXPECT_EQ(1U, dns.queries().size()) << dns.dumpQueries();
+
+    // using gethostbyname2() to resolve ipv6 hello.example.com. to 2001:db8::42
+    // Ensure the v6 address and cnames are correct
+    cnamecount = 0;
+    dns.clearQueries();
+    result = gethostbyname2("hello", AF_INET6);
+    for (unsigned i = 0; result != nullptr && result->h_aliases[i] != nullptr; i++) {
+        std::string domain_name = records[i].host_name.substr(0, records[i].host_name.size() - 1);
+        EXPECT_EQ(result->h_aliases[i], domain_name);
+        cnamecount++;
+    }
+    // The size of "Non-cname type" DNS record in records is 2
+    ASSERT_EQ(cnamecount, records.size() - 2);
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(16, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ("2001:db8::42", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+}
+
+TEST_F(ResolverTest, GetHostByName_cnamesInfiniteLoop) {
+    test::DNSResponder dns;
+    const std::vector<DnsRecord> records = {
+            {kHelloExampleCom, ns_type::ns_t_cname, "a.example.com."},
+            {"a.example.com.", ns_type::ns_t_cname, kHelloExampleCom},
+    };
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    const hostent* result;
+    result = gethostbyname2("hello", AF_INET);
+    ASSERT_TRUE(result == nullptr);
+
+    dns.clearQueries();
+    result = gethostbyname2("hello", AF_INET6);
+    ASSERT_TRUE(result == nullptr);
+}
+
+TEST_F(ResolverTest, GetHostByName_localhost) {
+    constexpr char name_camelcase[] = "LocalHost";
+    constexpr char name_ip6_dot[] = "ip6-localhost.";
+    constexpr char name_ip6_fqdn[] = "ip6-localhost.example.com.";
+
+    // Add a dummy nameserver which shouldn't receive any queries
+    test::DNSResponder dns;
+    StartDns(dns, {});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    // Expect no DNS queries; localhost is resolved via /etc/hosts
+    const hostent* result = gethostbyname(kLocalHost);
+    EXPECT_TRUE(dns.queries().empty()) << dns.dumpQueries();
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ(kLocalHostAddr, ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+
+    // Ensure the hosts file resolver ignores case of hostnames
+    result = gethostbyname(name_camelcase);
+    EXPECT_TRUE(dns.queries().empty()) << dns.dumpQueries();
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ(kLocalHostAddr, ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+
+    // The hosts file also contains ip6-localhost, but gethostbyname() won't
+    // return it unless the RES_USE_INET6 option is set. This would be easy to
+    // change, but there's no point in changing the legacy behavior; new code
+    // should be calling getaddrinfo() anyway.
+    // So we check the legacy behavior, which results in amusing A-record
+    // lookups for ip6-localhost, with and without search domains appended.
+    dns.clearQueries();
+    result = gethostbyname(kIp6LocalHost);
+    EXPECT_EQ(2U, dns.queries().size()) << dns.dumpQueries();
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, name_ip6_dot)) << dns.dumpQueries();
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, name_ip6_fqdn)) << dns.dumpQueries();
+    ASSERT_TRUE(result == nullptr);
+
+    // Finally, use gethostbyname2() to resolve ip6-localhost to ::1 from
+    // the hosts file.
+    dns.clearQueries();
+    result = gethostbyname2(kIp6LocalHost, AF_INET6);
+    EXPECT_TRUE(dns.queries().empty()) << dns.dumpQueries();
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(16, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ(kIp6LocalHostAddr, ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+}
+
+TEST_F(ResolverTest, GetHostByName_numeric) {
+    // Add a dummy nameserver which shouldn't receive any queries
+    test::DNSResponder dns;
+    StartDns(dns, {});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    // Numeric v4 address: expect no DNS queries
+    constexpr char numeric_v4[] = "192.168.0.1";
+    const hostent* result = gethostbyname(numeric_v4);
+    EXPECT_EQ(0U, dns.queries().size());
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);  // v4
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ(numeric_v4, ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+
+    // gethostbyname() recognizes a v6 address, and fails with no DNS queries
+    constexpr char numeric_v6[] = "2001:db8::42";
+    dns.clearQueries();
+    result = gethostbyname(numeric_v6);
+    EXPECT_EQ(0U, dns.queries().size());
+    EXPECT_TRUE(result == nullptr);
+
+    // Numeric v6 address with gethostbyname2(): succeeds with no DNS queries
+    dns.clearQueries();
+    result = gethostbyname2(numeric_v6, AF_INET6);
+    EXPECT_EQ(0U, dns.queries().size());
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(16, result->h_length);  // v6
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ(numeric_v6, ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+
+    // Numeric v6 address with scope work with getaddrinfo(),
+    // but gethostbyname2() does not understand them; it issues two dns
+    // queries, then fails. This hardly ever happens, there's no point
+    // in fixing this. This test simply verifies the current (bogus)
+    // behavior to avoid further regressions (like crashes, or leaks).
+    constexpr char numeric_v6_scope[] = "fe80::1%lo";
+    dns.clearQueries();
+    result = gethostbyname2(numeric_v6_scope, AF_INET6);
+    EXPECT_EQ(2U, dns.queries().size());  // OUCH!
+    ASSERT_TRUE(result == nullptr);
+}
+
+TEST_F(ResolverTest, BinderSerialization) {
+    using android::net::IDnsResolver;
+    std::vector<int> params_offsets = {
+            IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY,
+            IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD,
+            IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES,
+            IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES,
+            IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC,
+            IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT,
+    };
+    const int size = static_cast<int>(params_offsets.size());
+    EXPECT_EQ(size, IDnsResolver::RESOLVER_PARAMS_COUNT);
+    std::sort(params_offsets.begin(), params_offsets.end());
+    for (int i = 0; i < size; ++i) {
+        EXPECT_EQ(params_offsets[i], i);
+    }
+}
+
+TEST_F(ResolverTest, GetHostByName_Binder) {
+    using android::net::IDnsResolver;
+
+    std::vector<std::string> domains = { "example.com" };
+    std::vector<std::unique_ptr<test::DNSResponder>> dns;
+    std::vector<std::string> servers;
+    std::vector<DnsResponderClient::Mapping> mappings;
+    ASSERT_NO_FATAL_FAILURE(mDnsClient.SetupMappings(1, domains, &mappings));
+    ASSERT_NO_FATAL_FAILURE(mDnsClient.SetupDNSServers(4, mappings, &dns, &servers));
+    ASSERT_EQ(1U, mappings.size());
+    const DnsResponderClient::Mapping& mapping = mappings[0];
+
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, domains, kDefaultParams));
+
+    const hostent* result = gethostbyname(mapping.host.c_str());
+    const size_t total_queries =
+            std::accumulate(dns.begin(), dns.end(), 0, [this, &mapping](size_t total, auto& d) {
+                return total + GetNumQueriesForType(*d, ns_type::ns_t_a, mapping.entry.c_str());
+            });
+
+    EXPECT_LE(1U, total_queries);
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ(mapping.ip4, ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+
+    std::vector<std::string> res_servers;
+    std::vector<std::string> res_domains;
+    std::vector<std::string> res_tls_servers;
+    res_params res_params;
+    std::vector<ResolverStats> res_stats;
+    int wait_for_pending_req_timeout_count;
+    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                &res_stats, &wait_for_pending_req_timeout_count));
+    EXPECT_EQ(servers.size(), res_servers.size());
+    EXPECT_EQ(domains.size(), res_domains.size());
+    EXPECT_EQ(0U, res_tls_servers.size());
+    ASSERT_EQ(static_cast<size_t>(IDnsResolver::RESOLVER_PARAMS_COUNT), kDefaultParams.size());
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY],
+              res_params.sample_validity);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD],
+              res_params.success_threshold);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES], res_params.min_samples);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES], res_params.max_samples);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC],
+              res_params.base_timeout_msec);
+    EXPECT_EQ(servers.size(), res_stats.size());
+
+    EXPECT_THAT(res_servers, testing::UnorderedElementsAreArray(servers));
+    EXPECT_THAT(res_domains, testing::UnorderedElementsAreArray(domains));
+}
+
+TEST_F(ResolverTest, GetAddrInfo) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char listen_addr2[] = "127.0.0.5";
+    constexpr char host_name[] = "howdy.example.com.";
+
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+    test::DNSResponder dns(listen_addr);
+    test::DNSResponder dns2(listen_addr2);
+    StartDns(dns, records);
+    StartDns(dns2, records);
+
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork({listen_addr}));
+    dns.clearQueries();
+    dns2.clearQueries();
+
+    ScopedAddrinfo result = safe_getaddrinfo("howdy", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    size_t found = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, found);
+    // Could be A or AAAA
+    std::string result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
+        << ", result_str='" << result_str << "'";
+
+    // Verify that the name is cached.
+    size_t old_found = found;
+    result = safe_getaddrinfo("howdy", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    found = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, found);
+    EXPECT_EQ(old_found, found);
+    result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
+        << result_str;
+
+    // Change the DNS resolver, ensure that queries are still cached.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork({listen_addr2}));
+    dns.clearQueries();
+    dns2.clearQueries();
+
+    result = safe_getaddrinfo("howdy", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    found = GetNumQueries(dns, host_name);
+    size_t found2 = GetNumQueries(dns2, host_name);
+    EXPECT_EQ(0U, found);
+    EXPECT_LE(0U, found2);
+
+    // Could be A or AAAA
+    result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
+        << ", result_str='" << result_str << "'";
+}
+
+TEST_F(ResolverTest, GetAddrInfoV4) {
+    test::DNSResponder dns;
+    StartDns(dns, {{kHelloExampleCom, ns_type::ns_t_a, "1.2.3.5"}});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    const addrinfo hints = {.ai_family = AF_INET};
+    ScopedAddrinfo result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ(1U, GetNumQueries(dns, kHelloExampleCom));
+    EXPECT_EQ("1.2.3.5", ToString(result));
+}
+
+TEST_F(ResolverTest, GetAddrInfo_localhost) {
+    // Add a dummy nameserver which shouldn't receive any queries
+    test::DNSResponder dns;
+    StartDns(dns, {});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    ScopedAddrinfo result = safe_getaddrinfo(kLocalHost, nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    // Expect no DNS queries; localhost is resolved via /etc/hosts
+    EXPECT_TRUE(dns.queries().empty()) << dns.dumpQueries();
+    EXPECT_EQ(kLocalHostAddr, ToString(result));
+
+    result = safe_getaddrinfo(kIp6LocalHost, nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    // Expect no DNS queries; ip6-localhost is resolved via /etc/hosts
+    EXPECT_TRUE(dns.queries().empty()) << dns.dumpQueries();
+    EXPECT_EQ(kIp6LocalHostAddr, ToString(result));
+}
+
+// Verify if the resolver correctly handle multiple queries simultaneously
+// step 1: set dns server#1 into deferred responding mode.
+// step 2: thread#1 query "hello.example.com." --> resolver send query to server#1.
+// step 3: thread#2 query "hello.example.com." --> resolver hold the request and wait for
+//           response of previous pending query sent by thread#1.
+// step 4: thread#3 query "konbanha.example.com." --> resolver send query to server#3. Server
+//           respond to resolver immediately.
+// step 5: check if server#1 get 1 query by thread#1, server#2 get 0 query, server#3 get 1 query.
+// step 6: resume dns server#1 to respond dns query in step#2.
+// step 7: thread#1 and #2 should get returned from DNS query after step#6. Also, check the
+//           number of queries in server#2 is 0 to ensure thread#2 does not wake up unexpectedly
+//           before signaled by thread#1.
+TEST_F(ResolverTest, GetAddrInfoV4_deferred_resp) {
+    const char* listen_addr1 = "127.0.0.9";
+    const char* listen_addr2 = "127.0.0.10";
+    const char* listen_addr3 = "127.0.0.11";
+    const char* listen_srv = "53";
+    const char* host_name_deferred = "hello.example.com.";
+    const char* host_name_normal = "konbanha.example.com.";
+    test::DNSResponder dns1(listen_addr1, listen_srv, 250, ns_rcode::ns_r_servfail);
+    test::DNSResponder dns2(listen_addr2, listen_srv, 250, ns_rcode::ns_r_servfail);
+    test::DNSResponder dns3(listen_addr3, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns1.addMapping(host_name_deferred, ns_type::ns_t_a, "1.2.3.4");
+    dns2.addMapping(host_name_deferred, ns_type::ns_t_a, "1.2.3.4");
+    dns3.addMapping(host_name_normal, ns_type::ns_t_a, "1.2.3.5");
+    ASSERT_TRUE(dns1.startServer());
+    ASSERT_TRUE(dns2.startServer());
+    ASSERT_TRUE(dns3.startServer());
+    const std::vector<std::string> servers_for_t1 = {listen_addr1};
+    const std::vector<std::string> servers_for_t2 = {listen_addr2};
+    const std::vector<std::string> servers_for_t3 = {listen_addr3};
+    addrinfo hints = {.ai_family = AF_INET};
+    const std::vector<int> params = {300, 25, 8, 8, 5000};
+    bool t3_task_done = false;
+
+    dns1.setDeferredResp(true);
+    std::thread t1([&, this]() {
+        ASSERT_TRUE(
+                mDnsClient.SetResolversForNetwork(servers_for_t1, kDefaultSearchDomains, params));
+        ScopedAddrinfo result = safe_getaddrinfo(host_name_deferred, nullptr, &hints);
+        // t3's dns query should got returned first
+        EXPECT_TRUE(t3_task_done);
+        EXPECT_EQ(1U, GetNumQueries(dns1, host_name_deferred));
+        EXPECT_TRUE(result != nullptr);
+        EXPECT_EQ("1.2.3.4", ToString(result));
+    });
+
+    // ensuring t1 and t2 handler functions are processed in order
+    usleep(100 * 1000);
+    std::thread t2([&, this]() {
+        ASSERT_TRUE(
+                mDnsClient.SetResolversForNetwork(servers_for_t2, kDefaultSearchDomains, params));
+        ScopedAddrinfo result = safe_getaddrinfo(host_name_deferred, nullptr, &hints);
+        EXPECT_TRUE(t3_task_done);
+        EXPECT_EQ(0U, GetNumQueries(dns2, host_name_deferred));
+        EXPECT_TRUE(result != nullptr);
+        EXPECT_EQ("1.2.3.4", ToString(result));
+
+        std::vector<std::string> res_servers;
+        std::vector<std::string> res_domains;
+        std::vector<std::string> res_tls_servers;
+        res_params res_params;
+        std::vector<ResolverStats> res_stats;
+        int wait_for_pending_req_timeout_count;
+        ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                    &res_stats, &wait_for_pending_req_timeout_count));
+        EXPECT_EQ(0, wait_for_pending_req_timeout_count);
+    });
+
+    // ensuring t2 and t3 handler functions are processed in order
+    usleep(100 * 1000);
+    std::thread t3([&, this]() {
+        ASSERT_TRUE(
+                mDnsClient.SetResolversForNetwork(servers_for_t3, kDefaultSearchDomains, params));
+        ScopedAddrinfo result = safe_getaddrinfo(host_name_normal, nullptr, &hints);
+        EXPECT_EQ(1U, GetNumQueries(dns1, host_name_deferred));
+        EXPECT_EQ(0U, GetNumQueries(dns2, host_name_deferred));
+        EXPECT_EQ(1U, GetNumQueries(dns3, host_name_normal));
+        EXPECT_TRUE(result != nullptr);
+        EXPECT_EQ("1.2.3.5", ToString(result));
+
+        t3_task_done = true;
+        dns1.setDeferredResp(false);
+    });
+    t3.join();
+    t1.join();
+    t2.join();
+}
+
+TEST_F(ResolverTest, GetAddrInfo_cnames) {
+    constexpr char host_name[] = "host.example.com.";
+    test::DNSResponder dns;
+    const std::vector<DnsRecord> records = {
+            {kHelloExampleCom, ns_type::ns_t_cname, "a.example.com."},
+            {"a.example.com.", ns_type::ns_t_cname, "b.example.com."},
+            {"b.example.com.", ns_type::ns_t_cname, "c.example.com."},
+            {"c.example.com.", ns_type::ns_t_cname, "d.example.com."},
+            {"d.example.com.", ns_type::ns_t_cname, "e.example.com."},
+            {"e.example.com.", ns_type::ns_t_cname, host_name},
+            {host_name, ns_type::ns_t_a, "1.2.3.3"},
+            {host_name, ns_type::ns_t_aaaa, "2001:db8::42"},
+    };
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    addrinfo hints = {.ai_family = AF_INET};
+    ScopedAddrinfo result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+
+    dns.clearQueries();
+    hints = {.ai_family = AF_INET6};
+    result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ("2001:db8::42", ToString(result));
+}
+
+TEST_F(ResolverTest, GetAddrInfo_cnamesNoIpAddress) {
+    test::DNSResponder dns;
+    const std::vector<DnsRecord> records = {
+            {kHelloExampleCom, ns_type::ns_t_cname, "a.example.com."},
+    };
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    addrinfo hints = {.ai_family = AF_INET};
+    ScopedAddrinfo result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result == nullptr);
+
+    dns.clearQueries();
+    hints = {.ai_family = AF_INET6};
+    result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result == nullptr);
+}
+
+TEST_F(ResolverTest, GetAddrInfo_cnamesIllegalRdata) {
+    test::DNSResponder dns;
+    const std::vector<DnsRecord> records = {
+            {kHelloExampleCom, ns_type::ns_t_cname, ".!#?"},
+    };
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    addrinfo hints = {.ai_family = AF_INET};
+    ScopedAddrinfo result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result == nullptr);
+
+    dns.clearQueries();
+    hints = {.ai_family = AF_INET6};
+    result = safe_getaddrinfo("hello", nullptr, &hints);
+    EXPECT_TRUE(result == nullptr);
+}
+
+TEST_F(ResolverTest, MultidomainResolution) {
+    constexpr char host_name[] = "nihao.example2.com.";
+    std::vector<std::string> searchDomains = { "example1.com", "example2.com", "example3.com" };
+
+    test::DNSResponder dns("127.0.0.6");
+    StartDns(dns, {{host_name, ns_type::ns_t_a, "1.2.3.3"}});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork({"127.0.0.6"}, searchDomains));
+
+    const hostent* result = gethostbyname("nihao");
+
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+}
+
+TEST_F(ResolverTest, GetAddrInfoV6_numeric) {
+    constexpr char host_name[] = "ohayou.example.com.";
+    constexpr char numeric_addr[] = "fe80::1%lo";
+
+    test::DNSResponder dns;
+    dns.setResponseProbability(0.0);
+    StartDns(dns, {{host_name, ns_type::ns_t_aaaa, "2001:db8::5"}});
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    addrinfo hints = {.ai_family = AF_INET6};
+    ScopedAddrinfo result = safe_getaddrinfo(numeric_addr, nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ(numeric_addr, ToString(result));
+    EXPECT_TRUE(dns.queries().empty());  // Ensure no DNS queries were sent out
+
+    // Now try a non-numeric hostname query with the AI_NUMERICHOST flag set.
+    // We should fail without sending out a DNS query.
+    hints.ai_flags |= AI_NUMERICHOST;
+    result = safe_getaddrinfo(host_name, nullptr, &hints);
+    EXPECT_TRUE(result == nullptr);
+    EXPECT_TRUE(dns.queries().empty());  // Ensure no DNS queries were sent out
+}
+
+TEST_F(ResolverTest, GetAddrInfoV6_failing) {
+    constexpr char listen_addr0[] = "127.0.0.7";
+    constexpr char listen_addr1[] = "127.0.0.8";
+    const char* host_name = "ohayou.example.com.";
+
+    test::DNSResponder dns0(listen_addr0);
+    test::DNSResponder dns1(listen_addr1);
+    dns0.setResponseProbability(0.0);
+    StartDns(dns0, {{host_name, ns_type::ns_t_aaaa, "2001:db8::5"}});
+    StartDns(dns1, {{host_name, ns_type::ns_t_aaaa, "2001:db8::6"}});
+
+    std::vector<std::string> servers = { listen_addr0, listen_addr1 };
+    // <sample validity in s> <success threshold in percent> <min samples> <max samples>
+    int sample_count = 8;
+    const std::vector<int> params = { 300, 25, sample_count, sample_count };
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, kDefaultSearchDomains, params));
+
+    // Repeatedly perform resolutions for non-existing domains until MAXNSSAMPLES resolutions have
+    // reached the dns0, which is set to fail. No more requests should then arrive at that server
+    // for the next sample_lifetime seconds.
+    // TODO: This approach is implementation-dependent, change once metrics reporting is available.
+    const addrinfo hints = {.ai_family = AF_INET6};
+    for (int i = 0; i < sample_count; ++i) {
+        std::string domain = StringPrintf("nonexistent%d", i);
+        ScopedAddrinfo result = safe_getaddrinfo(domain.c_str(), nullptr, &hints);
+    }
+    // Due to 100% errors for all possible samples, the server should be ignored from now on and
+    // only the second one used for all following queries, until NSSAMPLE_VALIDITY is reached.
+    dns0.clearQueries();
+    dns1.clearQueries();
+    ScopedAddrinfo result = safe_getaddrinfo("ohayou", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ(0U, GetNumQueries(dns0, host_name));
+    EXPECT_EQ(1U, GetNumQueries(dns1, host_name));
+}
+
+TEST_F(ResolverTest, GetAddrInfoV6_nonresponsive) {
+    constexpr char listen_addr0[] = "127.0.0.7";
+    constexpr char listen_addr1[] = "127.0.0.8";
+    constexpr char listen_srv[] = "53";
+    constexpr char host_name1[] = "ohayou.example.com.";
+    constexpr char host_name2[] = "ciao.example.com.";
+    const std::vector<DnsRecord> records0 = {
+            {host_name1, ns_type::ns_t_aaaa, "2001:db8::5"},
+            {host_name2, ns_type::ns_t_aaaa, "2001:db8::5"},
+    };
+    const std::vector<DnsRecord> records1 = {
+            {host_name1, ns_type::ns_t_aaaa, "2001:db8::6"},
+            {host_name2, ns_type::ns_t_aaaa, "2001:db8::6"},
+    };
+
+    // dns0 does not respond with 100% probability, while
+    // dns1 responds normally, at least initially.
+    test::DNSResponder dns0(listen_addr0, listen_srv, 250, static_cast<ns_rcode>(-1));
+    test::DNSResponder dns1(listen_addr1, listen_srv, 250, static_cast<ns_rcode>(-1));
+    dns0.setResponseProbability(0.0);
+    StartDns(dns0, records0);
+    StartDns(dns1, records1);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork({listen_addr0, listen_addr1}));
+
+    const addrinfo hints = {.ai_family = AF_INET6};
+
+    // dns0 will ignore the request, and we'll fallback to dns1 after the first
+    // retry.
+    ScopedAddrinfo result = safe_getaddrinfo(host_name1, nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ(1U, GetNumQueries(dns0, host_name1));
+    EXPECT_EQ(1U, GetNumQueries(dns1, host_name1));
+
+    // Now make dns1 also ignore 100% requests... The resolve should alternate
+    // retries between the nameservers and fail after 4 attempts.
+    dns1.setResponseProbability(0.0);
+    addrinfo* result2 = nullptr;
+    EXPECT_EQ(EAI_NODATA, getaddrinfo(host_name2, nullptr, &hints, &result2));
+    EXPECT_EQ(nullptr, result2);
+    EXPECT_EQ(4U, GetNumQueries(dns0, host_name2));
+    EXPECT_EQ(4U, GetNumQueries(dns1, host_name2));
+}
+
+TEST_F(ResolverTest, GetAddrInfoV6_concurrent) {
+    constexpr char listen_addr0[] = "127.0.0.9";
+    constexpr char listen_addr1[] = "127.0.0.10";
+    constexpr char listen_addr2[] = "127.0.0.11";
+    constexpr char host_name[] = "konbanha.example.com.";
+
+    test::DNSResponder dns0(listen_addr0);
+    test::DNSResponder dns1(listen_addr1);
+    test::DNSResponder dns2(listen_addr2);
+    StartDns(dns0, {{host_name, ns_type::ns_t_aaaa, "2001:db8::5"}});
+    StartDns(dns1, {{host_name, ns_type::ns_t_aaaa, "2001:db8::6"}});
+    StartDns(dns2, {{host_name, ns_type::ns_t_aaaa, "2001:db8::7"}});
+
+    const std::vector<std::string> servers = { listen_addr0, listen_addr1, listen_addr2 };
+    std::vector<std::thread> threads(10);
+    for (std::thread& thread : threads) {
+       thread = std::thread([this, &servers]() {
+            unsigned delay = arc4random_uniform(1*1000*1000); // <= 1s
+            usleep(delay);
+            std::vector<std::string> serverSubset;
+            for (const auto& server : servers) {
+                if (arc4random_uniform(2)) {
+                    serverSubset.push_back(server);
+                }
+            }
+            if (serverSubset.empty()) serverSubset = servers;
+            ASSERT_TRUE(mDnsClient.SetResolversForNetwork(serverSubset));
+            const addrinfo hints = {.ai_family = AF_INET6};
+            addrinfo* result = nullptr;
+            int rv = getaddrinfo("konbanha", nullptr, &hints, &result);
+            EXPECT_EQ(0, rv) << "error [" << rv << "] " << gai_strerror(rv);
+            if (result) {
+                freeaddrinfo(result);
+                result = nullptr;
+            }
+        });
+    }
+    for (std::thread& thread : threads) {
+        thread.join();
+    }
+
+    std::vector<std::string> res_servers;
+    std::vector<std::string> res_domains;
+    std::vector<std::string> res_tls_servers;
+    res_params res_params;
+    std::vector<ResolverStats> res_stats;
+    int wait_for_pending_req_timeout_count;
+    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                &res_stats, &wait_for_pending_req_timeout_count));
+    EXPECT_EQ(0, wait_for_pending_req_timeout_count);
+}
+
+TEST_F(ResolverTest, GetAddrInfoStressTest_Binder_100) {
+    const unsigned num_hosts = 100;
+    const unsigned num_threads = 100;
+    const unsigned num_queries = 100;
+    ASSERT_NO_FATAL_FAILURE(RunGetAddrInfoStressTest_Binder(num_hosts, num_threads, num_queries));
+}
+
+TEST_F(ResolverTest, GetAddrInfoStressTest_Binder_100000) {
+    const unsigned num_hosts = 100000;
+    const unsigned num_threads = 100;
+    const unsigned num_queries = 100;
+    ASSERT_NO_FATAL_FAILURE(RunGetAddrInfoStressTest_Binder(num_hosts, num_threads, num_queries));
+}
+
+TEST_F(ResolverTest, EmptySetup) {
+    using android::net::IDnsResolver;
+    std::vector<std::string> servers;
+    std::vector<std::string> domains;
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, domains));
+    std::vector<std::string> res_servers;
+    std::vector<std::string> res_domains;
+    std::vector<std::string> res_tls_servers;
+    res_params res_params;
+    std::vector<ResolverStats> res_stats;
+    int wait_for_pending_req_timeout_count;
+    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                &res_stats, &wait_for_pending_req_timeout_count));
+    EXPECT_EQ(0U, res_servers.size());
+    EXPECT_EQ(0U, res_domains.size());
+    EXPECT_EQ(0U, res_tls_servers.size());
+    ASSERT_EQ(static_cast<size_t>(IDnsResolver::RESOLVER_PARAMS_COUNT), kDefaultParams.size());
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_SAMPLE_VALIDITY],
+              res_params.sample_validity);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_SUCCESS_THRESHOLD],
+              res_params.success_threshold);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_MIN_SAMPLES], res_params.min_samples);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_MAX_SAMPLES], res_params.max_samples);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_BASE_TIMEOUT_MSEC],
+              res_params.base_timeout_msec);
+    EXPECT_EQ(kDefaultParams[IDnsResolver::RESOLVER_PARAMS_RETRY_COUNT], res_params.retry_count);
+}
+
+TEST_F(ResolverTest, SearchPathChange) {
+    constexpr char listen_addr[] = "127.0.0.13";
+    constexpr char host_name1[] = "test13.domain1.org.";
+    constexpr char host_name2[] = "test13.domain2.org.";
+    std::vector<std::string> servers = { listen_addr };
+    std::vector<std::string> domains = { "domain1.org" };
+
+    const std::vector<DnsRecord> records = {
+            {host_name1, ns_type::ns_t_aaaa, "2001:db8::13"},
+            {host_name2, ns_type::ns_t_aaaa, "2001:db8::1:13"},
+    };
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, domains));
+
+    const addrinfo hints = {.ai_family = AF_INET6};
+    ScopedAddrinfo result = safe_getaddrinfo("test13", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ(1U, dns.queries().size());
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name1));
+    EXPECT_EQ("2001:db8::13", ToString(result));
+
+    // Test that changing the domain search path on its own works.
+    domains = { "domain2.org" };
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, domains));
+    dns.clearQueries();
+
+    result = safe_getaddrinfo("test13", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_EQ(1U, dns.queries().size());
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name2));
+    EXPECT_EQ("2001:db8::1:13", ToString(result));
+}
+
+static std::string base64Encode(const std::vector<uint8_t>& input) {
+    size_t out_len;
+    EXPECT_EQ(1, EVP_EncodedLength(&out_len, input.size()));
+    // out_len includes the trailing NULL.
+    uint8_t output_bytes[out_len];
+    EXPECT_EQ(out_len - 1, EVP_EncodeBlock(output_bytes, input.data(), input.size()));
+    return std::string(reinterpret_cast<char*>(output_bytes));
+}
+
+// If we move this function to dns_responder_client, it will complicate the dependency need of
+// dns_tls_frontend.h.
+static void setupTlsServers(const std::vector<std::string>& servers,
+                            std::vector<std::unique_ptr<test::DnsTlsFrontend>>* tls,
+                            std::vector<std::string>* fingerprints) {
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+
+    for (const auto& server : servers) {
+        auto t = std::make_unique<test::DnsTlsFrontend>(server, listen_tls, server, listen_udp);
+        t = std::make_unique<test::DnsTlsFrontend>(server, listen_tls, server, listen_udp);
+        t->startServer();
+        fingerprints->push_back(base64Encode(t->fingerprint()));
+        tls->push_back(std::move(t));
+    }
+}
+
+TEST_F(ResolverTest, MaxServerPrune_Binder) {
+    std::vector<std::string> domains;
+    std::vector<std::unique_ptr<test::DNSResponder>> dns;
+    std::vector<std::unique_ptr<test::DnsTlsFrontend>> tls;
+    std::vector<std::string> servers;
+    std::vector<std::string> fingerprints;
+    std::vector<DnsResponderClient::Mapping> mappings;
+
+    for (unsigned i = 0; i < MAXDNSRCH + 1; i++) {
+        domains.push_back(StringPrintf("example%u.com", i));
+    }
+    ASSERT_NO_FATAL_FAILURE(mDnsClient.SetupMappings(1, domains, &mappings));
+    ASSERT_NO_FATAL_FAILURE(mDnsClient.SetupDNSServers(MAXNS + 1, mappings, &dns, &servers));
+    ASSERT_NO_FATAL_FAILURE(setupTlsServers(servers, &tls, &fingerprints));
+
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, domains, kDefaultParams, "", fingerprints));
+
+    // If the private DNS validation hasn't completed yet before backend DNS servers stop,
+    // TLS servers will get stuck in handleOneRequest(), which causes this test stuck in
+    // ~DnsTlsFrontend() because the TLS server loop threads can't be terminated.
+    // So, wait for private DNS validation done before stopping backend DNS servers.
+    for (int i = 0; i < MAXNS; i++) {
+        ALOGI("Waiting for private DNS validation on %s.", tls[i]->listen_address().c_str());
+        EXPECT_TRUE(tls[i]->waitForQueries(1, 5000));
+        ALOGI("private DNS validation on %s done.", tls[i]->listen_address().c_str());
+    }
+
+    std::vector<std::string> res_servers;
+    std::vector<std::string> res_domains;
+    std::vector<std::string> res_tls_servers;
+    res_params res_params;
+    std::vector<ResolverStats> res_stats;
+    int wait_for_pending_req_timeout_count;
+    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                &res_stats, &wait_for_pending_req_timeout_count));
+
+    // Check the size of the stats and its contents.
+    EXPECT_EQ(static_cast<size_t>(MAXNS), res_servers.size());
+    EXPECT_EQ(static_cast<size_t>(MAXNS), res_tls_servers.size());
+    EXPECT_EQ(static_cast<size_t>(MAXDNSRCH), res_domains.size());
+    EXPECT_TRUE(std::equal(servers.begin(), servers.begin() + MAXNS, res_servers.begin()));
+    EXPECT_TRUE(std::equal(servers.begin(), servers.begin() + MAXNS, res_tls_servers.begin()));
+    EXPECT_TRUE(std::equal(domains.begin(), domains.begin() + MAXDNSRCH, res_domains.begin()));
+}
+
+TEST_F(ResolverTest, ResolverStats) {
+    constexpr char listen_addr1[] = "127.0.0.4";
+    constexpr char listen_addr2[] = "127.0.0.5";
+    constexpr char listen_addr3[] = "127.0.0.6";
+
+    // Set server 1 timeout.
+    test::DNSResponder dns1(listen_addr1, "53", 250, static_cast<ns_rcode>(-1));
+    dns1.setResponseProbability(0.0);
+    ASSERT_TRUE(dns1.startServer());
+
+    // Set server 2 responding server failure.
+    test::DNSResponder dns2(listen_addr2);
+    dns2.setResponseProbability(0.0);
+    ASSERT_TRUE(dns2.startServer());
+
+    // Set server 3 workable.
+    test::DNSResponder dns3(listen_addr3);
+    dns3.addMapping(kHelloExampleCom, ns_type::ns_t_a, "1.2.3.4");
+    ASSERT_TRUE(dns3.startServer());
+
+    std::vector<std::string> servers = {listen_addr1, listen_addr2, listen_addr3};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    dns3.clearQueries();
+    const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+    ScopedAddrinfo result = safe_getaddrinfo("hello", nullptr, &hints);
+    size_t found = GetNumQueries(dns3, kHelloExampleCom);
+    EXPECT_LE(1U, found);
+    std::string result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4") << ", result_str='" << result_str << "'";
+
+    std::vector<std::string> res_servers;
+    std::vector<std::string> res_domains;
+    std::vector<std::string> res_tls_servers;
+    res_params res_params;
+    std::vector<ResolverStats> res_stats;
+    int wait_for_pending_req_timeout_count;
+    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_tls_servers, &res_params,
+                                &res_stats, &wait_for_pending_req_timeout_count));
+
+    EXPECT_EQ(1, res_stats[0].timeouts);
+    EXPECT_EQ(1, res_stats[1].errors);
+    EXPECT_EQ(1, res_stats[2].successes);
+}
+
+// Test what happens if the specified TLS server is nonexistent.
+TEST_F(ResolverTest, GetHostByName_TlsMissing) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char host_name[] = "tlsmissing.example.com.";
+
+    test::DNSResponder dns;
+    StartDns(dns, {{host_name, ns_type::ns_t_a, "1.2.3.3"}});
+    std::vector<std::string> servers = { listen_addr };
+
+    // There's nothing listening on this address, so validation will either fail or
+    /// hang.  Either way, queries will continue to flow to the DNSResponder.
+    ASSERT_TRUE(
+            mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "", {}));
+
+    const hostent* result;
+
+    result = gethostbyname("tlsmissing");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+}
+
+// Test what happens if the specified TLS server replies with garbage.
+TEST_F(ResolverTest, GetHostByName_TlsBroken) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char host_name1[] = "tlsbroken1.example.com.";
+    constexpr char host_name2[] = "tlsbroken2.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name1, ns_type::ns_t_a, "1.2.3.1"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.2"},
+    };
+
+    test::DNSResponder dns;
+    StartDns(dns, records);
+    std::vector<std::string> servers = { listen_addr };
+
+    // Bind the specified private DNS socket but don't respond to any client sockets yet.
+    int s = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
+    ASSERT_TRUE(s >= 0);
+    struct sockaddr_in tlsServer = {
+        .sin_family = AF_INET,
+        .sin_port = htons(853),
+    };
+    ASSERT_TRUE(inet_pton(AF_INET, listen_addr, &tlsServer.sin_addr));
+    ASSERT_TRUE(enableSockopt(s, SOL_SOCKET, SO_REUSEPORT).ok());
+    ASSERT_TRUE(enableSockopt(s, SOL_SOCKET, SO_REUSEADDR).ok());
+    ASSERT_FALSE(bind(s, reinterpret_cast<struct sockaddr*>(&tlsServer), sizeof(tlsServer)));
+    ASSERT_FALSE(listen(s, 1));
+
+    // Trigger TLS validation.
+    ASSERT_TRUE(
+            mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "", {}));
+
+    struct sockaddr_storage cliaddr;
+    socklen_t sin_size = sizeof(cliaddr);
+    int new_fd = accept4(s, reinterpret_cast<struct sockaddr *>(&cliaddr), &sin_size, SOCK_CLOEXEC);
+    ASSERT_TRUE(new_fd > 0);
+
+    // We've received the new file descriptor but not written to it or closed, so the
+    // validation is still pending.  Queries should still flow correctly because the
+    // server is not used until validation succeeds.
+    const hostent* result;
+    result = gethostbyname("tlsbroken1");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.1", ToString(result));
+
+    // Now we cause the validation to fail.
+    std::string garbage = "definitely not a valid TLS ServerHello";
+    write(new_fd, garbage.data(), garbage.size());
+    close(new_fd);
+
+    // Validation failure shouldn't interfere with lookups, because lookups won't be sent
+    // to the TLS server unless validation succeeds.
+    result = gethostbyname("tlsbroken2");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.2", ToString(result));
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+    close(s);
+}
+
+TEST_F(ResolverTest, GetHostByName_Tls) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name1[] = "tls1.example.com.";
+    constexpr char host_name2[] = "tls2.example.com.";
+    constexpr char host_name3[] = "tls3.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name1, ns_type::ns_t_a, "1.2.3.1"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.2"},
+            {host_name3, ns_type::ns_t_a, "1.2.3.3"},
+    };
+
+    test::DNSResponder dns;
+    StartDns(dns, records);
+    std::vector<std::string> servers = { listen_addr };
+
+    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+    ASSERT_TRUE(tls.startServer());
+    ASSERT_TRUE(
+            mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "", {}));
+
+    const hostent* result;
+
+    // Wait for validation to complete.
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+
+    result = gethostbyname("tls1");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.1", ToString(result));
+
+    // Wait for query to get counted.
+    EXPECT_TRUE(tls.waitForQueries(2, 5000));
+
+    // Stop the TLS server.  Since we're in opportunistic mode, queries will
+    // fall back to the locally-assigned (clear text) nameservers.
+    tls.stopServer();
+
+    dns.clearQueries();
+    result = gethostbyname("tls2");
+    EXPECT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.2", ToString(result));
+    const auto queries = dns.queries();
+    EXPECT_EQ(1U, queries.size());
+    EXPECT_EQ("tls2.example.com.", queries[0].first);
+    EXPECT_EQ(ns_t_a, queries[0].second);
+
+    // Reset the resolvers without enabling TLS.  Queries should still be routed
+    // to the UDP endpoint.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    result = gethostbyname("tls3");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+}
+
+TEST_F(ResolverTest, GetHostByName_TlsFingerprint) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    test::DNSResponder dns;
+    ASSERT_TRUE(dns.startServer());
+    for (int chain_length = 1; chain_length <= 3; ++chain_length) {
+        std::string host_name = StringPrintf("tlsfingerprint%d.example.com.", chain_length);
+        dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.1");
+        std::vector<std::string> servers = { listen_addr };
+
+        test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+        tls.set_chain_length(chain_length);
+        ASSERT_TRUE(tls.startServer());
+        ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams,
+                                                   "", {base64Encode(tls.fingerprint())}));
+
+        const hostent* result;
+
+        // Wait for validation to complete.
+        EXPECT_TRUE(tls.waitForQueries(1, 5000));
+
+        result = gethostbyname(StringPrintf("tlsfingerprint%d", chain_length).c_str());
+        EXPECT_FALSE(result == nullptr);
+        if (result) {
+            EXPECT_EQ("1.2.3.1", ToString(result));
+
+            // Wait for query to get counted.
+            EXPECT_TRUE(tls.waitForQueries(2, 5000));
+        }
+
+        // Clear TLS bit to ensure revalidation.
+        ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+        tls.stopServer();
+    }
+}
+
+TEST_F(ResolverTest, GetHostByName_BadTlsFingerprint) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name[] = "badtlsfingerprint.example.com.";
+
+    test::DNSResponder dns;
+    StartDns(dns, {{host_name, ns_type::ns_t_a, "1.2.3.1"}});
+    std::vector<std::string> servers = { listen_addr };
+
+    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+    ASSERT_TRUE(tls.startServer());
+    std::vector<uint8_t> bad_fingerprint = tls.fingerprint();
+    bad_fingerprint[5] += 1;  // Corrupt the fingerprint.
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "",
+                                               {base64Encode(bad_fingerprint)}));
+
+    // The initial validation should fail at the fingerprint check before
+    // issuing a query.
+    EXPECT_FALSE(tls.waitForQueries(1, 500));
+
+    // A fingerprint was provided and failed to match, so the query should fail.
+    EXPECT_EQ(nullptr, gethostbyname("badtlsfingerprint"));
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+}
+
+// Test that we can pass two different fingerprints, and connection succeeds as long as
+// at least one of them matches the server.
+TEST_F(ResolverTest, GetHostByName_TwoTlsFingerprints) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name[] = "twotlsfingerprints.example.com.";
+
+    test::DNSResponder dns;
+    StartDns(dns, {{host_name, ns_type::ns_t_a, "1.2.3.1"}});
+    std::vector<std::string> servers = { listen_addr };
+
+    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+    ASSERT_TRUE(tls.startServer());
+    std::vector<uint8_t> bad_fingerprint = tls.fingerprint();
+    bad_fingerprint[5] += 1;  // Corrupt the fingerprint.
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(
+            servers, kDefaultSearchDomains, kDefaultParams, "",
+            {base64Encode(bad_fingerprint), base64Encode(tls.fingerprint())}));
+
+    const hostent* result;
+
+    // Wait for validation to complete.
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+
+    result = gethostbyname("twotlsfingerprints");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.1", ToString(result));
+
+    // Wait for query to get counted.
+    EXPECT_TRUE(tls.waitForQueries(2, 5000));
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+}
+
+TEST_F(ResolverTest, GetHostByName_TlsFingerprintGoesBad) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name1[] = "tlsfingerprintgoesbad1.example.com.";
+    constexpr char host_name2[] = "tlsfingerprintgoesbad2.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name1, ns_type::ns_t_a, "1.2.3.1"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.2"},
+    };
+
+    test::DNSResponder dns;
+    StartDns(dns, records);
+    std::vector<std::string> servers = { listen_addr };
+
+    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+    ASSERT_TRUE(tls.startServer());
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "",
+                                               {base64Encode(tls.fingerprint())}));
+
+    const hostent* result;
+
+    // Wait for validation to complete.
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+
+    result = gethostbyname("tlsfingerprintgoesbad1");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.1", ToString(result));
+
+    // Wait for query to get counted.
+    EXPECT_TRUE(tls.waitForQueries(2, 5000));
+
+    // Restart the TLS server.  This will generate a new certificate whose fingerprint
+    // no longer matches the stored fingerprint.
+    tls.stopServer();
+    tls.startServer();
+
+    result = gethostbyname("tlsfingerprintgoesbad2");
+    ASSERT_TRUE(result == nullptr);
+    EXPECT_EQ(HOST_NOT_FOUND, h_errno);
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+}
+
+TEST_F(ResolverTest, GetHostByName_TlsFailover) {
+    constexpr char listen_addr1[] = "127.0.0.3";
+    constexpr char listen_addr2[] = "127.0.0.4";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name1[] = "tlsfailover1.example.com.";
+    constexpr char host_name2[] = "tlsfailover2.example.com.";
+    const std::vector<DnsRecord> records1 = {
+            {host_name1, ns_type::ns_t_a, "1.2.3.1"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.2"},
+    };
+    const std::vector<DnsRecord> records2 = {
+            {host_name1, ns_type::ns_t_a, "1.2.3.3"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.4"},
+    };
+
+    test::DNSResponder dns1(listen_addr1);
+    test::DNSResponder dns2(listen_addr2);
+    StartDns(dns1, records1);
+    StartDns(dns2, records2);
+
+    std::vector<std::string> servers = { listen_addr1, listen_addr2 };
+
+    test::DnsTlsFrontend tls1(listen_addr1, listen_tls, listen_addr1, listen_udp);
+    test::DnsTlsFrontend tls2(listen_addr2, listen_tls, listen_addr2, listen_udp);
+    ASSERT_TRUE(tls1.startServer());
+    ASSERT_TRUE(tls2.startServer());
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(
+            servers, kDefaultSearchDomains, kDefaultParams, "",
+            {base64Encode(tls1.fingerprint()), base64Encode(tls2.fingerprint())}));
+
+    const hostent* result;
+
+    // Wait for validation to complete.
+    EXPECT_TRUE(tls1.waitForQueries(1, 5000));
+    EXPECT_TRUE(tls2.waitForQueries(1, 5000));
+
+    result = gethostbyname("tlsfailover1");
+    ASSERT_FALSE(result == nullptr);
+    EXPECT_EQ("1.2.3.1", ToString(result));
+
+    // Wait for query to get counted.
+    EXPECT_TRUE(tls1.waitForQueries(2, 5000));
+    // No new queries should have reached tls2.
+    EXPECT_EQ(1, tls2.queries());
+
+    // Stop tls1.  Subsequent queries should attempt to reach tls1, fail, and retry to tls2.
+    tls1.stopServer();
+
+    result = gethostbyname("tlsfailover2");
+    EXPECT_EQ("1.2.3.4", ToString(result));
+
+    // Wait for query to get counted.
+    EXPECT_TRUE(tls2.waitForQueries(2, 5000));
+
+    // No additional queries should have reached the insecure servers.
+    EXPECT_EQ(2U, dns1.queries().size());
+    EXPECT_EQ(2U, dns2.queries().size());
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+}
+
+TEST_F(ResolverTest, GetHostByName_BadTlsName) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name[] = "badtlsname.example.com.";
+
+    test::DNSResponder dns;
+    StartDns(dns, {{host_name, ns_type::ns_t_a, "1.2.3.1"}});
+    std::vector<std::string> servers = { listen_addr };
+
+    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+    ASSERT_TRUE(tls.startServer());
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams,
+                                               "www.example.com", {}));
+
+    // The TLS server's certificate doesn't chain to a known CA, and a nonempty name was specified,
+    // so the client should fail the TLS handshake before ever issuing a query.
+    EXPECT_FALSE(tls.waitForQueries(1, 500));
+
+    // The query should fail hard, because a name was specified.
+    EXPECT_EQ(nullptr, gethostbyname("badtlsname"));
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Tls) {
+    constexpr char listen_addr[] = "127.0.0.3";
+    constexpr char listen_udp[] = "53";
+    constexpr char listen_tls[] = "853";
+    constexpr char host_name[] = "addrinfotls.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns;
+    StartDns(dns, records);
+    std::vector<std::string> servers = { listen_addr };
+
+    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
+    ASSERT_TRUE(tls.startServer());
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "",
+                                               {base64Encode(tls.fingerprint())}));
+
+    // Wait for validation to complete.
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+
+    dns.clearQueries();
+    ScopedAddrinfo result = safe_getaddrinfo("addrinfotls", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    size_t found = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, found);
+    // Could be A or AAAA
+    std::string result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
+        << ", result_str='" << result_str << "'";
+    // Wait for both A and AAAA queries to get counted.
+    EXPECT_TRUE(tls.waitForQueries(3, 5000));
+
+    // Clear TLS bit.
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+}
+
+TEST_F(ResolverTest, TlsBypass) {
+    const char OFF[] = "off";
+    const char OPPORTUNISTIC[] = "opportunistic";
+    const char STRICT[] = "strict";
+
+    const char GETHOSTBYNAME[] = "gethostbyname";
+    const char GETADDRINFO[] = "getaddrinfo";
+    const char GETADDRINFOFORNET[] = "getaddrinfofornet";
+
+    const unsigned BYPASS_NETID = NETID_USE_LOCAL_NAMESERVERS | TEST_NETID;
+
+    const std::vector<uint8_t> NOOP_FINGERPRINT(SHA256_SIZE, 0U);
+
+    const char ADDR4[] = "192.0.2.1";
+    const char ADDR6[] = "2001:db8::1";
+
+    const char cleartext_addr[] = "127.0.0.53";
+    const char cleartext_port[] = "53";
+    const char tls_port[] = "853";
+    const std::vector<std::string> servers = { cleartext_addr };
+
+    test::DNSResponder dns(cleartext_addr);
+    ASSERT_TRUE(dns.startServer());
+
+    test::DnsTlsFrontend tls(cleartext_addr, tls_port, cleartext_addr, cleartext_port);
+
+    struct TestConfig {
+        const std::string mode;
+        const bool withWorkingTLS;
+        const std::string method;
+
+        std::string asHostName() const {
+            return StringPrintf("%s.%s.%s.",
+                                mode.c_str(),
+                                withWorkingTLS ? "tlsOn" : "tlsOff",
+                                method.c_str());
+        }
+    } testConfigs[]{
+        {OFF,           false, GETHOSTBYNAME},
+        {OPPORTUNISTIC, false, GETHOSTBYNAME},
+        {STRICT,        false, GETHOSTBYNAME},
+        {OFF,           true,  GETHOSTBYNAME},
+        {OPPORTUNISTIC, true,  GETHOSTBYNAME},
+        {STRICT,        true,  GETHOSTBYNAME},
+        {OFF,           false, GETADDRINFO},
+        {OPPORTUNISTIC, false, GETADDRINFO},
+        {STRICT,        false, GETADDRINFO},
+        {OFF,           true,  GETADDRINFO},
+        {OPPORTUNISTIC, true,  GETADDRINFO},
+        {STRICT,        true,  GETADDRINFO},
+        {OFF,           false, GETADDRINFOFORNET},
+        {OPPORTUNISTIC, false, GETADDRINFOFORNET},
+        {STRICT,        false, GETADDRINFOFORNET},
+        {OFF,           true,  GETADDRINFOFORNET},
+        {OPPORTUNISTIC, true,  GETADDRINFOFORNET},
+        {STRICT,        true,  GETADDRINFOFORNET},
+    };
+
+    for (const auto& config : testConfigs) {
+        const std::string testHostName = config.asHostName();
+        SCOPED_TRACE(testHostName);
+
+        // Don't tempt test bugs due to caching.
+        const char* host_name = testHostName.c_str();
+        dns.addMapping(host_name, ns_type::ns_t_a, ADDR4);
+        dns.addMapping(host_name, ns_type::ns_t_aaaa, ADDR6);
+
+        if (config.withWorkingTLS) ASSERT_TRUE(tls.startServer());
+
+        if (config.mode == OFF) {
+            ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers, kDefaultSearchDomains,
+                                                          kDefaultParams));
+        } else if (config.mode == OPPORTUNISTIC) {
+            ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains,
+                                                       kDefaultParams, "", {}));
+            // Wait for validation to complete.
+            if (config.withWorkingTLS) EXPECT_TRUE(tls.waitForQueries(1, 5000));
+        } else if (config.mode == STRICT) {
+            // We use the existence of fingerprints to trigger strict mode,
+            // rather than hostname validation.
+            const auto& fingerprint =
+                    (config.withWorkingTLS) ? tls.fingerprint() : NOOP_FINGERPRINT;
+            ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains,
+                                                       kDefaultParams, "",
+                                                       {base64Encode(fingerprint)}));
+            // Wait for validation to complete.
+            if (config.withWorkingTLS) EXPECT_TRUE(tls.waitForQueries(1, 5000));
+        } else {
+            FAIL() << "Unsupported Private DNS mode: " << config.mode;
+        }
+
+        const int tlsQueriesBefore = tls.queries();
+
+        const hostent* h_result = nullptr;
+        ScopedAddrinfo ai_result;
+
+        if (config.method == GETHOSTBYNAME) {
+            ASSERT_EQ(0, setNetworkForResolv(BYPASS_NETID));
+            h_result = gethostbyname(host_name);
+
+            EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
+            ASSERT_FALSE(h_result == nullptr);
+            ASSERT_EQ(4, h_result->h_length);
+            ASSERT_FALSE(h_result->h_addr_list[0] == nullptr);
+            EXPECT_EQ(ADDR4, ToString(h_result));
+            EXPECT_TRUE(h_result->h_addr_list[1] == nullptr);
+        } else if (config.method == GETADDRINFO) {
+            ASSERT_EQ(0, setNetworkForResolv(BYPASS_NETID));
+            ai_result = safe_getaddrinfo(host_name, nullptr, nullptr);
+            EXPECT_TRUE(ai_result != nullptr);
+
+            EXPECT_LE(1U, GetNumQueries(dns, host_name));
+            // Could be A or AAAA
+            const std::string result_str = ToString(ai_result);
+            EXPECT_TRUE(result_str == ADDR4 || result_str == ADDR6)
+                << ", result_str='" << result_str << "'";
+        } else if (config.method == GETADDRINFOFORNET) {
+            addrinfo* raw_ai_result = nullptr;
+            EXPECT_EQ(0, android_getaddrinfofornet(host_name, /*servname=*/nullptr,
+                                                   /*hints=*/nullptr, BYPASS_NETID, MARK_UNSET,
+                                                   &raw_ai_result));
+            ai_result.reset(raw_ai_result);
+
+            EXPECT_LE(1U, GetNumQueries(dns, host_name));
+            // Could be A or AAAA
+            const std::string result_str = ToString(ai_result);
+            EXPECT_TRUE(result_str == ADDR4 || result_str == ADDR6)
+                << ", result_str='" << result_str << "'";
+        } else {
+            FAIL() << "Unsupported query method: " << config.method;
+        }
+
+        const int tlsQueriesAfter = tls.queries();
+        EXPECT_EQ(0, tlsQueriesAfter - tlsQueriesBefore);
+
+        // Clear per-process resolv netid.
+        ASSERT_EQ(0, setNetworkForResolv(NETID_UNSET));
+        tls.stopServer();
+        dns.clearQueries();
+    }
+}
+
+TEST_F(ResolverTest, StrictMode_NoTlsServers) {
+    const std::vector<uint8_t> NOOP_FINGERPRINT(SHA256_SIZE, 0U);
+    constexpr char cleartext_addr[] = "127.0.0.53";
+    const std::vector<std::string> servers = { cleartext_addr };
+    constexpr char host_name[] = "strictmode.notlsips.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(cleartext_addr);
+    StartDns(dns, records);
+
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, {},
+                                               "", {base64Encode(NOOP_FINGERPRINT)}));
+
+    addrinfo* ai_result = nullptr;
+    EXPECT_NE(0, getaddrinfo(host_name, nullptr, nullptr, &ai_result));
+    EXPECT_EQ(0U, GetNumQueries(dns, host_name));
+}
+
+namespace {
+
+int getAsyncResponse(int fd, int* rcode, uint8_t* buf, int bufLen) {
+    struct pollfd wait_fd[1];
+    wait_fd[0].fd = fd;
+    wait_fd[0].events = POLLIN;
+    short revents;
+    int ret;
+
+    ret = poll(wait_fd, 1, -1);
+    revents = wait_fd[0].revents;
+    if (revents & POLLIN) {
+        int n = resNetworkResult(fd, rcode, buf, bufLen);
+        // Verify that resNetworkResult() closed the fd
+        char dummy;
+        EXPECT_EQ(-1, read(fd, &dummy, sizeof dummy));
+        EXPECT_EQ(EBADF, errno);
+        return n;
+    }
+    return -1;
+}
+
+std::string toString(uint8_t* buf, int bufLen, int ipType) {
+    ns_msg handle;
+    int ancount, n = 0;
+    ns_rr rr;
+
+    if (ns_initparse((const uint8_t*) buf, bufLen, &handle) >= 0) {
+        ancount = ns_msg_count(handle, ns_s_an);
+        if (ns_parserr(&handle, ns_s_an, n, &rr) == 0) {
+            const uint8_t* rdata = ns_rr_rdata(rr);
+            char buffer[INET6_ADDRSTRLEN];
+            if (inet_ntop(ipType, (const char*) rdata, buffer, sizeof(buffer))) {
+                return buffer;
+            }
+        }
+    }
+    return "";
+}
+
+int dns_open_proxy() {
+    int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (s == -1) {
+        return -1;
+    }
+    const int one = 1;
+    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+    static const struct sockaddr_un proxy_addr = {
+            .sun_family = AF_UNIX,
+            .sun_path = "/dev/socket/dnsproxyd",
+    };
+
+    if (TEMP_FAILURE_RETRY(connect(s, (const struct sockaddr*) &proxy_addr, sizeof(proxy_addr))) !=
+        0) {
+        close(s);
+        return -1;
+    }
+
+    return s;
+}
+
+void expectAnswersValid(int fd, int ipType, const std::string& expectedAnswer) {
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+
+    int res = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ(expectedAnswer, toString(buf, res, ipType));
+}
+
+void expectAnswersNotValid(int fd, int expectedErrno) {
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+
+    int res = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_EQ(expectedErrno, res);
+}
+
+}  // namespace
+
+TEST_F(ResolverTest, Async_NormalQueryV4V6) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char host_name[] = "howdy.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    uint8_t buf[MAXPACKET] = {};
+    int rcode;
+    int res = getAsyncResponse(fd2, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("::1.2.3.4", toString(buf, res, AF_INET6));
+
+    res = getAsyncResponse(fd1, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("1.2.3.4", toString(buf, res, AF_INET));
+
+    EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+
+    // Re-query verify cache works
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    res = getAsyncResponse(fd2, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("::1.2.3.4", toString(buf, res, AF_INET6));
+
+    res = getAsyncResponse(fd1, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("1.2.3.4", toString(buf, res, AF_INET));
+
+    EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+}
+
+TEST_F(ResolverTest, Async_BadQuery) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char host_name[] = "howdy.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    static struct {
+        int fd;
+        const char* dname;
+        const int queryType;
+        const int expectRcode;
+    } kTestData[] = {
+            {-1, "", ns_t_aaaa, 0},
+            {-1, "as65ass46", ns_t_aaaa, 0},
+            {-1, "454564564564", ns_t_aaaa, 0},
+            {-1, "h645235", ns_t_a, 0},
+            {-1, "www.google.com", ns_t_a, 0},
+    };
+
+    for (auto& td : kTestData) {
+        SCOPED_TRACE(td.dname);
+        td.fd = resNetworkQuery(TEST_NETID, td.dname, ns_c_in, td.queryType, 0);
+        EXPECT_TRUE(td.fd != -1);
+    }
+
+    // dns_responder return empty resp(packet only contains query part) with no error currently
+    for (const auto& td : kTestData) {
+        uint8_t buf[MAXPACKET] = {};
+        int rcode;
+        SCOPED_TRACE(td.dname);
+        int res = getAsyncResponse(td.fd, &rcode, buf, MAXPACKET);
+        EXPECT_GT(res, 0);
+        EXPECT_EQ(rcode, td.expectRcode);
+    }
+}
+
+TEST_F(ResolverTest, Async_EmptyAnswer) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char host_name[] = "howdy.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // TODO: Disable retry to make this test explicit.
+    auto& cv = dns.getCv();
+    auto& cvMutex = dns.getCvMutex();
+    int fd1;
+    // Wait on the condition variable to ensure that the DNS server has handled our first query.
+    {
+        std::unique_lock lk(cvMutex);
+        fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+        EXPECT_TRUE(fd1 != -1);
+        EXPECT_EQ(std::cv_status::no_timeout, cv.wait_for(lk, std::chrono::seconds(1)));
+    }
+
+    dns.setResponseProbability(0.0);
+
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    EXPECT_TRUE(fd2 != -1);
+
+    int fd3 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    EXPECT_TRUE(fd3 != -1);
+
+    uint8_t buf[MAXPACKET] = {};
+    int rcode;
+
+    // expect no response
+    int res = getAsyncResponse(fd3, &rcode, buf, MAXPACKET);
+    EXPECT_EQ(-ETIMEDOUT, res);
+
+    // expect no response
+    memset(buf, 0, MAXPACKET);
+    res = getAsyncResponse(fd2, &rcode, buf, MAXPACKET);
+    EXPECT_EQ(-ETIMEDOUT, res);
+
+    dns.setResponseProbability(1.0);
+
+    int fd4 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    EXPECT_TRUE(fd4 != -1);
+
+    memset(buf, 0, MAXPACKET);
+    res = getAsyncResponse(fd4, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("1.2.3.4", toString(buf, res, AF_INET));
+
+    memset(buf, 0, MAXPACKET);
+    res = getAsyncResponse(fd1, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("::1.2.3.4", toString(buf, res, AF_INET6));
+}
+
+TEST_F(ResolverTest, Async_MalformedQuery) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char host_name[] = "howdy.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    int fd = dns_open_proxy();
+    EXPECT_TRUE(fd > 0);
+
+    const std::string badMsg = "16-52512#";
+    static const struct {
+        const std::string cmd;
+        const int expectErr;
+    } kTestData[] = {
+            // Too few arguments
+            {"resnsend " + badMsg + '\0', -EINVAL},
+            // Bad netId
+            {"resnsend badnetId 0 " + badMsg + '\0', -EINVAL},
+            // Bad raw data
+            {"resnsend " + std::to_string(TEST_NETID) + " 0 " + badMsg + '\0', -EILSEQ},
+    };
+
+    for (unsigned int i = 0; i < std::size(kTestData); i++) {
+        auto& td = kTestData[i];
+        SCOPED_TRACE(td.cmd);
+        ssize_t rc = TEMP_FAILURE_RETRY(write(fd, td.cmd.c_str(), td.cmd.size()));
+        EXPECT_EQ(rc, static_cast<ssize_t>(td.cmd.size()));
+
+        int32_t tmp;
+        rc = TEMP_FAILURE_RETRY(read(fd, &tmp, sizeof(tmp)));
+        EXPECT_TRUE(rc > 0);
+        EXPECT_EQ(static_cast<int>(ntohl(tmp)), td.expectErr);
+    }
+    // Normal query with answer buffer
+    // This is raw data of query "howdy.example.com" type 1 class 1
+    std::string query = "81sBAAABAAAAAAAABWhvd2R5B2V4YW1wbGUDY29tAAABAAE=";
+    std::string cmd = "resnsend " + std::to_string(TEST_NETID) + " 0 " + query + '\0';
+    ssize_t rc = TEMP_FAILURE_RETRY(write(fd, cmd.c_str(), cmd.size()));
+    EXPECT_EQ(rc, static_cast<ssize_t>(cmd.size()));
+
+    uint8_t smallBuf[1] = {};
+    int rcode;
+    rc = getAsyncResponse(fd, &rcode, smallBuf, 1);
+    EXPECT_EQ(-EMSGSIZE, rc);
+
+    // Do the normal test with large buffer again
+    fd = dns_open_proxy();
+    EXPECT_TRUE(fd > 0);
+    rc = TEMP_FAILURE_RETRY(write(fd, cmd.c_str(), cmd.size()));
+    EXPECT_EQ(rc, static_cast<ssize_t>(cmd.size()));
+    uint8_t buf[MAXPACKET] = {};
+    rc = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_EQ("1.2.3.4", toString(buf, rc, AF_INET));
+}
+
+TEST_F(ResolverTest, Async_CacheFlags) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char host_name[] = "howdy.example.com.";
+    constexpr char another_host_name[] = "howdy.example2.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+            {another_host_name, ns_type::ns_t_a, "1.2.3.5"},
+            {another_host_name, ns_type::ns_t_aaaa, "::1.2.3.5"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // ANDROID_RESOLV_NO_CACHE_STORE
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd1 != -1);
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd2 != -1);
+    int fd3 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd3 != -1);
+
+    expectAnswersValid(fd3, AF_INET, "1.2.3.4");
+    expectAnswersValid(fd2, AF_INET, "1.2.3.4");
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // No cache exists, expect 3 queries
+    EXPECT_EQ(3U, GetNumQueries(dns, host_name));
+
+    // Re-query and cache
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+
+    EXPECT_TRUE(fd1 != -1);
+
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // Now we have cache, expect 4 queries
+    EXPECT_EQ(4U, GetNumQueries(dns, host_name));
+
+    // ANDROID_RESOLV_NO_CACHE_LOOKUP
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                          ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                          ANDROID_RESOLV_NO_CACHE_LOOKUP);
+
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    expectAnswersValid(fd2, AF_INET, "1.2.3.4");
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // Skip cache, expect 6 queries
+    EXPECT_EQ(6U, GetNumQueries(dns, host_name));
+
+    // Re-query verify cache works
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                          ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd1 != -1);
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // Cache hits,  expect still 6 queries
+    EXPECT_EQ(6U, GetNumQueries(dns, host_name));
+
+    // Start to verify if ANDROID_RESOLV_NO_CACHE_LOOKUP does write response into cache
+    dns.clearQueries();
+
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa,
+                          ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa,
+                          ANDROID_RESOLV_NO_CACHE_LOOKUP);
+
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    expectAnswersValid(fd2, AF_INET6, "::1.2.3.4");
+    expectAnswersValid(fd1, AF_INET6, "::1.2.3.4");
+
+    // Skip cache, expect 2 queries
+    EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+
+    // Re-query without flags
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    expectAnswersValid(fd2, AF_INET6, "::1.2.3.4");
+    expectAnswersValid(fd1, AF_INET6, "::1.2.3.4");
+
+    // Cache hits, expect still 2 queries
+    EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+
+    // Test both ANDROID_RESOLV_NO_CACHE_STORE and ANDROID_RESOLV_NO_CACHE_LOOKUP are set
+    dns.clearQueries();
+
+    // Make sure that the cache of "howdy.example2.com" exists.
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example2.com", ns_c_in, ns_t_aaaa, 0);
+    EXPECT_TRUE(fd1 != -1);
+    expectAnswersValid(fd1, AF_INET6, "::1.2.3.5");
+    EXPECT_EQ(1U, GetNumQueries(dns, another_host_name));
+
+    // Re-query with testFlags
+    const int testFlag = ANDROID_RESOLV_NO_CACHE_STORE | ANDROID_RESOLV_NO_CACHE_LOOKUP;
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example2.com", ns_c_in, ns_t_aaaa, testFlag);
+    EXPECT_TRUE(fd1 != -1);
+    expectAnswersValid(fd1, AF_INET6, "::1.2.3.5");
+    // Expect cache lookup is skipped.
+    EXPECT_EQ(2U, GetNumQueries(dns, another_host_name));
+
+    // Do another query with testFlags
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example2.com", ns_c_in, ns_t_a, testFlag);
+    EXPECT_TRUE(fd1 != -1);
+    expectAnswersValid(fd1, AF_INET, "1.2.3.5");
+    // Expect cache lookup is skipped.
+    EXPECT_EQ(3U, GetNumQueries(dns, another_host_name));
+
+    // Re-query with no flags
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example2.com", ns_c_in, ns_t_a, 0);
+    EXPECT_TRUE(fd1 != -1);
+    expectAnswersValid(fd1, AF_INET, "1.2.3.5");
+    // Expect no cache hit because cache storing is also skipped in previous query.
+    EXPECT_EQ(4U, GetNumQueries(dns, another_host_name));
+}
+
+TEST_F(ResolverTest, Async_NoRetryFlag) {
+    constexpr char listen_addr0[] = "127.0.0.4";
+    constexpr char listen_addr1[] = "127.0.0.6";
+    constexpr char host_name[] = "howdy.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns0(listen_addr0);
+    test::DNSResponder dns1(listen_addr1);
+    StartDns(dns0, records);
+    StartDns(dns1, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork({listen_addr0, listen_addr1}));
+
+    dns0.clearQueries();
+    dns1.clearQueries();
+
+    dns0.setResponseProbability(0.0);
+    dns1.setResponseProbability(0.0);
+
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_RETRY);
+    EXPECT_TRUE(fd1 != -1);
+
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa,
+                              ANDROID_RESOLV_NO_RETRY);
+    EXPECT_TRUE(fd2 != -1);
+
+    // expect no response
+    expectAnswersNotValid(fd1, -ETIMEDOUT);
+    expectAnswersNotValid(fd2, -ETIMEDOUT);
+
+    // No retry case, expect total 2 queries. The server is selected randomly.
+    EXPECT_EQ(2U, GetNumQueries(dns0, host_name) + GetNumQueries(dns1, host_name));
+
+    dns0.clearQueries();
+    dns1.clearQueries();
+
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    EXPECT_TRUE(fd1 != -1);
+
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+    EXPECT_TRUE(fd2 != -1);
+
+    // expect no response
+    expectAnswersNotValid(fd1, -ETIMEDOUT);
+    expectAnswersNotValid(fd2, -ETIMEDOUT);
+
+    // Retry case, expect 4 queries
+    EXPECT_EQ(4U, GetNumQueries(dns0, host_name));
+    EXPECT_EQ(4U, GetNumQueries(dns1, host_name));
+}
+
+TEST_F(ResolverTest, Async_VerifyQueryID) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char host_name[] = "howdy.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    const uint8_t queryBuf1[] = {
+            /* Header */
+            0x55, 0x66, /* Transaction ID */
+            0x01, 0x00, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x00, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x05, 0x68, 0x6f, 0x77, 0x64, 0x79, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x01,                   /* Type */
+            0x00, 0x01                    /* Class */
+    };
+
+    int fd = resNetworkSend(TEST_NETID, queryBuf1, sizeof(queryBuf1), 0);
+    EXPECT_TRUE(fd != -1);
+
+    uint8_t buf[MAXPACKET] = {};
+    int rcode;
+
+    int res = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("1.2.3.4", toString(buf, res, AF_INET));
+
+    auto hp = reinterpret_cast<HEADER*>(buf);
+    EXPECT_EQ(21862U, htons(hp->id));
+
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+
+    const uint8_t queryBuf2[] = {
+            /* Header */
+            0x00, 0x53, /* Transaction ID */
+            0x01, 0x00, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x00, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x05, 0x68, 0x6f, 0x77, 0x64, 0x79, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x01,                   /* Type */
+            0x00, 0x01                    /* Class */
+    };
+
+    // Re-query verify cache works and query id is correct
+    fd = resNetworkSend(TEST_NETID, queryBuf2, sizeof(queryBuf2), 0);
+
+    EXPECT_TRUE(fd != -1);
+
+    res = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ("1.2.3.4", toString(buf, res, AF_INET));
+
+    EXPECT_EQ(0x0053U, htons(hp->id));
+
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+}
+
+// This test checks that the resolver should not generate the request containing OPT RR when using
+// cleartext DNS. If we query the DNS server not supporting EDNS0 and it reponds with
+// FORMERR_ON_EDNS, we will fallback to no EDNS0 and try again. If the server does no response, we
+// won't retry so that we get no answer.
+TEST_F(ResolverTest, BrokenEdns) {
+    typedef test::DNSResponder::Edns Edns;
+    enum ExpectResult { EXPECT_FAILURE, EXPECT_SUCCESS };
+
+    const char OFF[] = "off";
+    const char OPPORTUNISTIC_UDP[] = "opportunistic_udp";
+    const char OPPORTUNISTIC_TLS[] = "opportunistic_tls";
+    const char STRICT[] = "strict";
+    const char GETHOSTBYNAME[] = "gethostbyname";
+    const char GETADDRINFO[] = "getaddrinfo";
+    const std::vector<uint8_t> NOOP_FINGERPRINT(SHA256_SIZE, 0U);
+    const char ADDR4[] = "192.0.2.1";
+    const char CLEARTEXT_ADDR[] = "127.0.0.53";
+    const char CLEARTEXT_PORT[] = "53";
+    const char TLS_PORT[] = "853";
+    const std::vector<std::string> servers = { CLEARTEXT_ADDR };
+
+    test::DNSResponder dns(CLEARTEXT_ADDR, CLEARTEXT_PORT, 250, ns_rcode::ns_r_servfail);
+    ASSERT_TRUE(dns.startServer());
+
+    test::DnsTlsFrontend tls(CLEARTEXT_ADDR, TLS_PORT, CLEARTEXT_ADDR, CLEARTEXT_PORT);
+
+    static const struct TestConfig {
+        std::string mode;
+        std::string method;
+        Edns edns;
+        ExpectResult expectResult;
+
+        std::string asHostName() const {
+            const char* ednsString;
+            switch (edns) {
+                case Edns::ON:
+                    ednsString = "ednsOn";
+                    break;
+                case Edns::FORMERR_ON_EDNS:
+                    ednsString = "ednsFormerr";
+                    break;
+                case Edns::DROP:
+                    ednsString = "ednsDrop";
+                    break;
+                default:
+                    ednsString = "";
+                    break;
+            }
+            return StringPrintf("%s.%s.%s.", mode.c_str(), method.c_str(), ednsString);
+        }
+    } testConfigs[] = {
+            // In OPPORTUNISTIC_TLS, we get no answer if the DNS server supports TLS but not EDNS0.
+            // Could such server exist? if so, we might need to fallback to query cleartext DNS.
+            // Another thing is that {OPPORTUNISTIC_TLS, Edns::DROP} and {STRICT, Edns::DROP} are
+            // commented out since TLS timeout is not configurable.
+            // TODO: Uncomment them after TLS timeout is configurable.
+            {OFF,               GETHOSTBYNAME, Edns::ON,      EXPECT_SUCCESS},
+            {OPPORTUNISTIC_UDP, GETHOSTBYNAME, Edns::ON,      EXPECT_SUCCESS},
+            {OPPORTUNISTIC_TLS, GETHOSTBYNAME, Edns::ON,      EXPECT_SUCCESS},
+            {STRICT,            GETHOSTBYNAME, Edns::ON,      EXPECT_SUCCESS},
+            {OFF,               GETHOSTBYNAME, Edns::FORMERR_ON_EDNS, EXPECT_SUCCESS},
+            {OPPORTUNISTIC_UDP, GETHOSTBYNAME, Edns::FORMERR_ON_EDNS, EXPECT_SUCCESS},
+            {OPPORTUNISTIC_TLS, GETHOSTBYNAME, Edns::FORMERR_ON_EDNS, EXPECT_FAILURE},
+            {STRICT,            GETHOSTBYNAME, Edns::FORMERR_ON_EDNS, EXPECT_FAILURE},
+            {OFF,               GETHOSTBYNAME, Edns::DROP,    EXPECT_SUCCESS},
+            {OPPORTUNISTIC_UDP, GETHOSTBYNAME, Edns::DROP,    EXPECT_SUCCESS},
+            //{OPPORTUNISTIC_TLS, GETHOSTBYNAME, Edns::DROP,    EXPECT_FAILURE},
+            //{STRICT,            GETHOSTBYNAME, Edns::DROP,    EXPECT_FAILURE},
+            {OFF,               GETADDRINFO,   Edns::ON,      EXPECT_SUCCESS},
+            {OPPORTUNISTIC_UDP, GETADDRINFO,   Edns::ON,      EXPECT_SUCCESS},
+            {OPPORTUNISTIC_TLS, GETADDRINFO,   Edns::ON,      EXPECT_SUCCESS},
+            {STRICT,            GETADDRINFO,   Edns::ON,      EXPECT_SUCCESS},
+            {OFF,               GETADDRINFO,   Edns::FORMERR_ON_EDNS, EXPECT_SUCCESS},
+            {OPPORTUNISTIC_UDP, GETADDRINFO,   Edns::FORMERR_ON_EDNS, EXPECT_SUCCESS},
+            {OPPORTUNISTIC_TLS, GETADDRINFO,   Edns::FORMERR_ON_EDNS, EXPECT_FAILURE},
+            {STRICT,            GETADDRINFO,   Edns::FORMERR_ON_EDNS, EXPECT_FAILURE},
+            {OFF,               GETADDRINFO,   Edns::DROP,    EXPECT_SUCCESS},
+            {OPPORTUNISTIC_UDP, GETADDRINFO,   Edns::DROP,    EXPECT_SUCCESS},
+            //{OPPORTUNISTIC_TLS, GETADDRINFO,   Edns::DROP,   EXPECT_FAILURE},
+            //{STRICT,            GETADDRINFO,   Edns::DROP,   EXPECT_FAILURE},
+    };
+
+    for (const auto& config : testConfigs) {
+        const std::string testHostName = config.asHostName();
+        SCOPED_TRACE(testHostName);
+
+        const char* host_name = testHostName.c_str();
+        dns.addMapping(host_name, ns_type::ns_t_a, ADDR4);
+        dns.setEdns(config.edns);
+
+        if (config.mode == OFF) {
+            ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+        } else if (config.mode == OPPORTUNISTIC_UDP) {
+            ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains,
+                                                       kDefaultParams, "", {}));
+        } else if (config.mode == OPPORTUNISTIC_TLS) {
+            ASSERT_TRUE(tls.startServer());
+            ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains,
+                                                       kDefaultParams, "", {}));
+            // Wait for validation to complete.
+            EXPECT_TRUE(tls.waitForQueries(1, 5000));
+        } else if (config.mode == STRICT) {
+            ASSERT_TRUE(tls.startServer());
+            ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains,
+                                                       kDefaultParams, "",
+                                                       {base64Encode(tls.fingerprint())}));
+            // Wait for validation to complete.
+            EXPECT_TRUE(tls.waitForQueries(1, 5000));
+        }
+
+        if (config.method == GETHOSTBYNAME) {
+            const hostent* h_result = gethostbyname(host_name);
+            if (config.expectResult == EXPECT_SUCCESS) {
+                EXPECT_LE(1U, GetNumQueries(dns, host_name));
+                ASSERT_TRUE(h_result != nullptr);
+                ASSERT_EQ(4, h_result->h_length);
+                ASSERT_FALSE(h_result->h_addr_list[0] == nullptr);
+                EXPECT_EQ(ADDR4, ToString(h_result));
+                EXPECT_TRUE(h_result->h_addr_list[1] == nullptr);
+            } else {
+                EXPECT_EQ(0U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
+                ASSERT_TRUE(h_result == nullptr);
+                ASSERT_EQ(HOST_NOT_FOUND, h_errno);
+            }
+        } else if (config.method == GETADDRINFO) {
+            ScopedAddrinfo ai_result;
+            addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+            ai_result = safe_getaddrinfo(host_name, nullptr, &hints);
+            if (config.expectResult == EXPECT_SUCCESS) {
+                EXPECT_TRUE(ai_result != nullptr);
+                EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+                const std::string result_str = ToString(ai_result);
+                EXPECT_EQ(ADDR4, result_str);
+            } else {
+                EXPECT_TRUE(ai_result == nullptr);
+                EXPECT_EQ(0U, GetNumQueries(dns, host_name));
+            }
+        } else {
+            FAIL() << "Unsupported query method: " << config.method;
+        }
+
+        tls.stopServer();
+        dns.clearQueries();
+    }
+}
+
+// DNS-over-TLS validation success, but server does not respond to TLS query after a while.
+// Resolver should have a reasonable number of retries instead of spinning forever. We don't have
+// an efficient way to know if resolver is stuck in an infinite loop. However, test case will be
+// failed due to timeout.
+TEST_F(ResolverTest, UnstableTls) {
+    const char CLEARTEXT_ADDR[] = "127.0.0.53";
+    const char CLEARTEXT_PORT[] = "53";
+    const char TLS_PORT[] = "853";
+    const char* host_name1 = "nonexistent1.example.com.";
+    const char* host_name2 = "nonexistent2.example.com.";
+    const std::vector<std::string> servers = {CLEARTEXT_ADDR};
+
+    test::DNSResponder dns(CLEARTEXT_ADDR, CLEARTEXT_PORT, 250, ns_rcode::ns_r_servfail);
+    ASSERT_TRUE(dns.startServer());
+    dns.setEdns(test::DNSResponder::Edns::FORMERR_ON_EDNS);
+    test::DnsTlsFrontend tls(CLEARTEXT_ADDR, TLS_PORT, CLEARTEXT_ADDR, CLEARTEXT_PORT);
+    ASSERT_TRUE(tls.startServer());
+    ASSERT_TRUE(
+            mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "", {}));
+    // Wait for validation complete.
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+    // Shutdown TLS server to get an error. It's similar to no response case but without waiting.
+    tls.stopServer();
+
+    const hostent* h_result = gethostbyname(host_name1);
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name1));
+    ASSERT_TRUE(h_result == nullptr);
+    ASSERT_EQ(HOST_NOT_FOUND, h_errno);
+
+    addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+    ScopedAddrinfo ai_result = safe_getaddrinfo(host_name2, nullptr, &hints);
+    EXPECT_TRUE(ai_result == nullptr);
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name2));
+}
+
+// DNS-over-TLS validation success, but server does not respond to TLS query after a while.
+// Moreover, server responds RCODE=FORMERR even on non-EDNS query.
+TEST_F(ResolverTest, BogusDnsServer) {
+    const char CLEARTEXT_ADDR[] = "127.0.0.53";
+    const char CLEARTEXT_PORT[] = "53";
+    const char TLS_PORT[] = "853";
+    const char* host_name1 = "nonexistent1.example.com.";
+    const char* host_name2 = "nonexistent2.example.com.";
+    const std::vector<std::string> servers = {CLEARTEXT_ADDR};
+
+    test::DNSResponder dns(CLEARTEXT_ADDR, CLEARTEXT_PORT, 250, ns_rcode::ns_r_servfail);
+    ASSERT_TRUE(dns.startServer());
+    test::DnsTlsFrontend tls(CLEARTEXT_ADDR, TLS_PORT, CLEARTEXT_ADDR, CLEARTEXT_PORT);
+    ASSERT_TRUE(tls.startServer());
+    ASSERT_TRUE(
+            mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "", {}));
+    // Wait for validation complete.
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+    // Shutdown TLS server to get an error. It's similar to no response case but without waiting.
+    tls.stopServer();
+    dns.setEdns(test::DNSResponder::Edns::FORMERR_UNCOND);
+
+    const hostent* h_result = gethostbyname(host_name1);
+    EXPECT_EQ(0U, GetNumQueries(dns, host_name1));
+    ASSERT_TRUE(h_result == nullptr);
+    ASSERT_EQ(HOST_NOT_FOUND, h_errno);
+
+    addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+    ScopedAddrinfo ai_result = safe_getaddrinfo(host_name2, nullptr, &hints);
+    EXPECT_TRUE(ai_result == nullptr);
+    EXPECT_EQ(0U, GetNumQueries(dns, host_name2));
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64Synthesize) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "v4only.example.com.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // hints are necessary in order to let netd know which type of addresses the caller is
+    // interested in.
+    const addrinfo hints = {.ai_family = AF_UNSPEC};
+    ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    // TODO: BUG: there should only be two queries, one AAAA (which returns no records) and one A
+    // (which returns 1.2.3.4). But there is an extra AAAA.
+    EXPECT_EQ(3U, GetNumQueries(dns, host_name));
+
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, "64:ff9b::102:304");
+
+    // Stopping NAT64 prefix discovery disables synthesis.
+    EXPECT_TRUE(mDnsClient.resolvService()->stopPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_FALSE(WaitForPrefix64Detected(TEST_NETID, 300));
+
+    dns.clearQueries();
+
+    result = safe_getaddrinfo("v4only", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    // TODO: BUG: there should only be one query, an AAAA (which returns no records), because the
+    // A is already cached. But there is an extra AAAA.
+    EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+
+    result_str = ToString(result);
+    EXPECT_EQ(result_str, "1.2.3.4");
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64QuerySpecified) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "v4only.example.com.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Ensure to synthesize AAAA if AF_INET6 is specified, and not to synthesize AAAA
+    // in AF_INET case.
+    addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET6;
+    ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, "64:ff9b::102:304");
+
+    hints.ai_family = AF_INET;
+    result = safe_getaddrinfo("v4only", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_LE(2U, GetNumQueries(dns, host_name));
+    result_str = ToString(result);
+    EXPECT_EQ(result_str, "1.2.3.4");
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64QueryUnspecifiedV6) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "v4v6.example.com.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "2001:db8::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    const addrinfo hints = {.ai_family = AF_UNSPEC};
+    ScopedAddrinfo result = safe_getaddrinfo("v4v6", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_LE(2U, GetNumQueries(dns, host_name));
+
+    // In AF_UNSPEC case, do not synthesize AAAA if there's at least one AAAA answer.
+    const std::vector<std::string> result_strs = ToStrings(result);
+    for (const auto& str : result_strs) {
+        EXPECT_TRUE(str == "1.2.3.4" || str == "2001:db8::102:304")
+                << ", result_str='" << str << "'";
+    }
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64QueryUnspecifiedNoV6) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "v4v6.example.com.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    const addrinfo hints = {.ai_family = AF_UNSPEC};
+    ScopedAddrinfo result = safe_getaddrinfo("v4v6", nullptr, &hints);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_LE(2U, GetNumQueries(dns, host_name));
+
+    // In AF_UNSPEC case, synthesize AAAA if there's no AAAA answer.
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, "64:ff9b::102:304");
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64QuerySpecialUseIPv4Addresses) {
+    constexpr char THIS_NETWORK[] = "this_network";
+    constexpr char LOOPBACK[] = "loopback";
+    constexpr char LINK_LOCAL[] = "link_local";
+    constexpr char MULTICAST[] = "multicast";
+    constexpr char LIMITED_BROADCAST[] = "limited_broadcast";
+
+    constexpr char ADDR_THIS_NETWORK[] = "0.0.0.1";
+    constexpr char ADDR_LOOPBACK[] = "127.0.0.1";
+    constexpr char ADDR_LINK_LOCAL[] = "169.254.0.1";
+    constexpr char ADDR_MULTICAST[] = "224.0.0.1";
+    constexpr char ADDR_LIMITED_BROADCAST[] = "255.255.255.255";
+
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, {{dns64_name, ns_type::ns_t_aaaa, "64:ff9b::"}});
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    static const struct TestConfig {
+        std::string name;
+        std::string addr;
+
+        std::string asHostName() const { return StringPrintf("%s.example.com.", name.c_str()); }
+    } testConfigs[]{
+        {THIS_NETWORK,      ADDR_THIS_NETWORK},
+        {LOOPBACK,          ADDR_LOOPBACK},
+        {LINK_LOCAL,        ADDR_LINK_LOCAL},
+        {MULTICAST,         ADDR_MULTICAST},
+        {LIMITED_BROADCAST, ADDR_LIMITED_BROADCAST}
+    };
+
+    for (const auto& config : testConfigs) {
+        const std::string testHostName = config.asHostName();
+        SCOPED_TRACE(testHostName);
+
+        const char* host_name = testHostName.c_str();
+        dns.addMapping(host_name, ns_type::ns_t_a, config.addr.c_str());
+
+        addrinfo hints;
+        memset(&hints, 0, sizeof(hints));
+        hints.ai_family = AF_INET6;
+        ScopedAddrinfo result = safe_getaddrinfo(config.name.c_str(), nullptr, &hints);
+        // In AF_INET6 case, don't return IPv4 answers
+        EXPECT_TRUE(result == nullptr);
+        EXPECT_LE(2U, GetNumQueries(dns, host_name));
+        dns.clearQueries();
+
+        memset(&hints, 0, sizeof(hints));
+        hints.ai_family = AF_UNSPEC;
+        result = safe_getaddrinfo(config.name.c_str(), nullptr, &hints);
+        EXPECT_TRUE(result != nullptr);
+        // Expect IPv6 query only. IPv4 answer has been cached in previous query.
+        EXPECT_LE(1U, GetNumQueries(dns, host_name));
+        // In AF_UNSPEC case, don't synthesize special use IPv4 address.
+        std::string result_str = ToString(result);
+        EXPECT_EQ(result_str, config.addr.c_str());
+        dns.clearQueries();
+    }
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64QueryWithNullArgumentHints) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "v4only.example.com.";
+    constexpr char host_name2[] = "v4v6.example.com.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name2, ns_type::ns_t_aaaa, "2001:db8::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Assign argument hints of getaddrinfo() as null is equivalent to set ai_family AF_UNSPEC.
+    // In AF_UNSPEC case, synthesize AAAA if there has A answer only.
+    ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_LE(2U, GetNumQueries(dns, host_name));
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, "64:ff9b::102:304");
+    dns.clearQueries();
+
+    // In AF_UNSPEC case, do not synthesize AAAA if there's at least one AAAA answer.
+    result = safe_getaddrinfo("v4v6", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    EXPECT_LE(2U, GetNumQueries(dns, host_name2));
+    std::vector<std::string> result_strs = ToStrings(result);
+    for (const auto& str : result_strs) {
+        EXPECT_TRUE(str == "1.2.3.4" || str == "2001:db8::102:304")
+                << ", result_str='" << str << "'";
+    }
+}
+
+TEST_F(ResolverTest, GetAddrInfo_Dns64QueryNullArgumentNode) {
+    constexpr char ADDR_ANYADDR_V4[] = "0.0.0.0";
+    constexpr char ADDR_ANYADDR_V6[] = "::";
+    constexpr char ADDR_LOCALHOST_V4[] = "127.0.0.1";
+    constexpr char ADDR_LOCALHOST_V6[] = "::1";
+
+    constexpr char PORT_NAME_HTTP[] = "http";
+    constexpr char PORT_NUMBER_HTTP[] = "80";
+
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, {{dns64_name, ns_type::ns_t_aaaa, "64:ff9b::"}});
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // If node is null, return address is listed by libc/getaddrinfo.c as follows.
+    // - passive socket -> anyaddr (0.0.0.0 or ::)
+    // - non-passive socket -> localhost (127.0.0.1 or ::1)
+    static const struct TestConfig {
+        int flag;
+        std::string addr_v4;
+        std::string addr_v6;
+
+        std::string asParameters() const {
+            return StringPrintf("flag=%d, addr_v4=%s, addr_v6=%s", flag, addr_v4.c_str(),
+                                addr_v6.c_str());
+        }
+    } testConfigs[]{
+        {0 /* non-passive */, ADDR_LOCALHOST_V4, ADDR_LOCALHOST_V6},
+        {AI_PASSIVE,          ADDR_ANYADDR_V4,   ADDR_ANYADDR_V6}
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(config.asParameters());
+
+        addrinfo hints = {
+                .ai_family = AF_UNSPEC,  // any address family
+                .ai_socktype = 0,        // any type
+                .ai_protocol = 0,        // any protocol
+                .ai_flags = config.flag,
+        };
+
+        // Assign hostname as null and service as port name.
+        ScopedAddrinfo result = safe_getaddrinfo(nullptr, PORT_NAME_HTTP, &hints);
+        ASSERT_TRUE(result != nullptr);
+
+        // Can't be synthesized because it should not get into Netd.
+        std::vector<std::string> result_strs = ToStrings(result);
+        for (const auto& str : result_strs) {
+            EXPECT_TRUE(str == config.addr_v4 || str == config.addr_v6)
+                    << ", result_str='" << str << "'";
+        }
+
+        // Assign hostname as null and service as numeric port number.
+        hints.ai_flags = config.flag | AI_NUMERICSERV;
+        result = safe_getaddrinfo(nullptr, PORT_NUMBER_HTTP, &hints);
+        ASSERT_TRUE(result != nullptr);
+
+        // Can't be synthesized because it should not get into Netd.
+        result_strs = ToStrings(result);
+        for (const auto& str : result_strs) {
+            EXPECT_TRUE(str == config.addr_v4 || str == config.addr_v6)
+                    << ", result_str='" << str << "'";
+        }
+    }
+}
+
+TEST_F(ResolverTest, GetHostByAddr_ReverseDnsQueryWithHavingNat64Prefix) {
+    struct hostent* result = nullptr;
+    struct in_addr v4addr;
+    struct in6_addr v6addr;
+
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char ptr_name[] = "v4v6.example.com.";
+    // PTR record for IPv4 address 1.2.3.4
+    constexpr char ptr_addr_v4[] = "4.3.2.1.in-addr.arpa.";
+    // PTR record for IPv6 address 2001:db8::102:304
+    constexpr char ptr_addr_v6[] =
+            "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {ptr_addr_v4, ns_type::ns_t_ptr, ptr_name},
+            {ptr_addr_v6, ns_type::ns_t_ptr, ptr_name},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Reverse IPv4 DNS query. Prefix should have no effect on it.
+    inet_pton(AF_INET, "1.2.3.4", &v4addr);
+    result = gethostbyaddr(&v4addr, sizeof(v4addr), AF_INET);
+    ASSERT_TRUE(result != nullptr);
+    std::string result_str = result->h_name ? result->h_name : "null";
+    EXPECT_EQ(result_str, "v4v6.example.com");
+
+    // Reverse IPv6 DNS query. Prefix should have no effect on it.
+    inet_pton(AF_INET6, "2001:db8::102:304", &v6addr);
+    result = gethostbyaddr(&v6addr, sizeof(v6addr), AF_INET6);
+    ASSERT_TRUE(result != nullptr);
+    result_str = result->h_name ? result->h_name : "null";
+    EXPECT_EQ(result_str, "v4v6.example.com");
+}
+
+TEST_F(ResolverTest, GetHostByAddr_ReverseDns64Query) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char ptr_name[] = "v4only.example.com.";
+    // PTR record for IPv4 address 1.2.3.4
+    constexpr char ptr_addr_v4[] = "4.3.2.1.in-addr.arpa.";
+    // PTR record for IPv6 address 64:ff9b::1.2.3.4
+    constexpr char ptr_addr_v6_nomapping[] =
+            "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa.";
+    constexpr char ptr_name_v6_synthesis[] = "v6synthesis.example.com.";
+    // PTR record for IPv6 address 64:ff9b::5.6.7.8
+    constexpr char ptr_addr_v6_synthesis[] =
+            "8.0.7.0.6.0.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {ptr_addr_v4, ns_type::ns_t_ptr, ptr_name},
+            {ptr_addr_v6_synthesis, ns_type::ns_t_ptr, ptr_name_v6_synthesis},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    // "ptr_addr_v6_nomapping" is not mapped in DNS server
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Synthesized PTR record doesn't exist on DNS server
+    // Reverse IPv6 DNS64 query while DNS server doesn't have an answer for synthesized address.
+    // After querying synthesized address failed, expect that prefix is removed from IPv6
+    // synthesized address and do reverse IPv4 query instead.
+    struct in6_addr v6addr;
+    inet_pton(AF_INET6, "64:ff9b::1.2.3.4", &v6addr);
+    struct hostent* result = gethostbyaddr(&v6addr, sizeof(v6addr), AF_INET6);
+    ASSERT_TRUE(result != nullptr);
+    EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v6_nomapping));  // PTR record not exist
+    EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v4));            // PTR record exist
+    std::string result_str = result->h_name ? result->h_name : "null";
+    EXPECT_EQ(result_str, "v4only.example.com");
+    // Check that return address has been mapped from IPv4 to IPv6 address because Netd
+    // removes NAT64 prefix and does IPv4 DNS reverse lookup in this case. Then, Netd
+    // fakes the return IPv4 address as original queried IPv6 address.
+    result_str = ToString(result);
+    EXPECT_EQ(result_str, "64:ff9b::102:304");
+    dns.clearQueries();
+
+    // Synthesized PTR record exists on DNS server
+    // Reverse IPv6 DNS64 query while DNS server has an answer for synthesized address.
+    // Expect to Netd pass through synthesized address for DNS queries.
+    inet_pton(AF_INET6, "64:ff9b::5.6.7.8", &v6addr);
+    result = gethostbyaddr(&v6addr, sizeof(v6addr), AF_INET6);
+    ASSERT_TRUE(result != nullptr);
+    EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v6_synthesis));
+    result_str = result->h_name ? result->h_name : "null";
+    EXPECT_EQ(result_str, "v6synthesis.example.com");
+}
+
+TEST_F(ResolverTest, GetHostByAddr_ReverseDns64QueryFromHostFile) {
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "localhost";
+    // The address is synthesized by prefix64:localhost.
+    constexpr char host_addr[] = "64:ff9b::7f00:1";
+    constexpr char listen_addr[] = "::1";
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, {{dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"}});
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Using synthesized "localhost" address to be a trick for resolving host name
+    // from host file /etc/hosts and "localhost" is the only name in /etc/hosts. Note that this is
+    // not realistic: the code never synthesizes AAAA records for addresses in 127.0.0.0/8.
+    struct in6_addr v6addr;
+    inet_pton(AF_INET6, host_addr, &v6addr);
+    struct hostent* result = gethostbyaddr(&v6addr, sizeof(v6addr), AF_INET6);
+    ASSERT_TRUE(result != nullptr);
+    // Expect no DNS queries; localhost is resolved via /etc/hosts.
+    EXPECT_EQ(0U, GetNumQueries(dns, host_name));
+
+    ASSERT_EQ(sizeof(in6_addr), (unsigned) result->h_length);
+    ASSERT_EQ(AF_INET6, result->h_addrtype);
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, host_addr);
+    result_str = result->h_name ? result->h_name : "null";
+    EXPECT_EQ(result_str, host_name);
+}
+
+TEST_F(ResolverTest, GetNameInfo_ReverseDnsQueryWithHavingNat64Prefix) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char ptr_name[] = "v4v6.example.com.";
+    // PTR record for IPv4 address 1.2.3.4
+    constexpr char ptr_addr_v4[] = "4.3.2.1.in-addr.arpa.";
+    // PTR record for IPv6 address 2001:db8::102:304
+    constexpr char ptr_addr_v6[] =
+            "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {ptr_addr_v4, ns_type::ns_t_ptr, ptr_name},
+            {ptr_addr_v6, ns_type::ns_t_ptr, ptr_name},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    static const struct TestConfig {
+        int flag;
+        int family;
+        std::string addr;
+        std::string host;
+
+        std::string asParameters() const {
+            return StringPrintf("flag=%d, family=%d, addr=%s, host=%s", flag, family, addr.c_str(),
+                                host.c_str());
+        }
+    } testConfigs[]{
+        {NI_NAMEREQD,    AF_INET,  "1.2.3.4",           "v4v6.example.com"},
+        {NI_NUMERICHOST, AF_INET,  "1.2.3.4",           "1.2.3.4"},
+        {0,              AF_INET,  "1.2.3.4",           "v4v6.example.com"},
+        {0,              AF_INET,  "5.6.7.8",           "5.6.7.8"},           // unmapped
+        {NI_NAMEREQD,    AF_INET6, "2001:db8::102:304", "v4v6.example.com"},
+        {NI_NUMERICHOST, AF_INET6, "2001:db8::102:304", "2001:db8::102:304"},
+        {0,              AF_INET6, "2001:db8::102:304", "v4v6.example.com"},
+        {0,              AF_INET6, "2001:db8::506:708", "2001:db8::506:708"}, // unmapped
+    };
+
+    // Reverse IPv4/IPv6 DNS query. Prefix should have no effect on it.
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(config.asParameters());
+
+        int rv;
+        char host[NI_MAXHOST];
+        struct sockaddr_in sin;
+        struct sockaddr_in6 sin6;
+        if (config.family == AF_INET) {
+            memset(&sin, 0, sizeof(sin));
+            sin.sin_family = AF_INET;
+            inet_pton(AF_INET, config.addr.c_str(), &sin.sin_addr);
+            rv = getnameinfo((const struct sockaddr*) &sin, sizeof(sin), host, sizeof(host),
+                             nullptr, 0, config.flag);
+            if (config.flag == NI_NAMEREQD) EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v4));
+        } else if (config.family == AF_INET6) {
+            memset(&sin6, 0, sizeof(sin6));
+            sin6.sin6_family = AF_INET6;
+            inet_pton(AF_INET6, config.addr.c_str(), &sin6.sin6_addr);
+            rv = getnameinfo((const struct sockaddr*) &sin6, sizeof(sin6), host, sizeof(host),
+                             nullptr, 0, config.flag);
+            if (config.flag == NI_NAMEREQD) EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v6));
+        }
+        ASSERT_EQ(0, rv);
+        std::string result_str = host;
+        EXPECT_EQ(result_str, config.host);
+        dns.clearQueries();
+    }
+}
+
+TEST_F(ResolverTest, GetNameInfo_ReverseDns64Query) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char ptr_name[] = "v4only.example.com.";
+    // PTR record for IPv4 address 1.2.3.4
+    constexpr char ptr_addr_v4[] = "4.3.2.1.in-addr.arpa.";
+    // PTR record for IPv6 address 64:ff9b::1.2.3.4
+    constexpr char ptr_addr_v6_nomapping[] =
+            "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa.";
+    constexpr char ptr_name_v6_synthesis[] = "v6synthesis.example.com.";
+    // PTR record for IPv6 address 64:ff9b::5.6.7.8
+    constexpr char ptr_addr_v6_synthesis[] =
+            "8.0.7.0.6.0.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {ptr_addr_v4, ns_type::ns_t_ptr, ptr_name},
+            {ptr_addr_v6_synthesis, ns_type::ns_t_ptr, ptr_name_v6_synthesis},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    static const struct TestConfig {
+        bool hasSynthesizedPtrRecord;
+        int flag;
+        std::string addr;
+        std::string host;
+
+        std::string asParameters() const {
+            return StringPrintf("hasSynthesizedPtrRecord=%d, flag=%d, addr=%s, host=%s",
+                                hasSynthesizedPtrRecord, flag, addr.c_str(), host.c_str());
+        }
+    } testConfigs[]{
+        {false, NI_NAMEREQD,    "64:ff9b::102:304", "v4only.example.com"},
+        {false, NI_NUMERICHOST, "64:ff9b::102:304", "64:ff9b::102:304"},
+        {false, 0,              "64:ff9b::102:304", "v4only.example.com"},
+        {true,  NI_NAMEREQD,    "64:ff9b::506:708", "v6synthesis.example.com"},
+        {true,  NI_NUMERICHOST, "64:ff9b::506:708", "64:ff9b::506:708"},
+        {true,  0,              "64:ff9b::506:708", "v6synthesis.example.com"}
+    };
+
+    // hasSynthesizedPtrRecord = false
+    //   Synthesized PTR record doesn't exist on DNS server
+    //   Reverse IPv6 DNS64 query while DNS server doesn't have an answer for synthesized address.
+    //   After querying synthesized address failed, expect that prefix is removed from IPv6
+    //   synthesized address and do reverse IPv4 query instead.
+    //
+    // hasSynthesizedPtrRecord = true
+    //   Synthesized PTR record exists on DNS server
+    //   Reverse IPv6 DNS64 query while DNS server has an answer for synthesized address.
+    //   Expect to just pass through synthesized address for DNS queries.
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(config.asParameters());
+
+        char host[NI_MAXHOST];
+        struct sockaddr_in6 sin6;
+        memset(&sin6, 0, sizeof(sin6));
+        sin6.sin6_family = AF_INET6;
+        inet_pton(AF_INET6, config.addr.c_str(), &sin6.sin6_addr);
+        int rv = getnameinfo((const struct sockaddr*) &sin6, sizeof(sin6), host, sizeof(host),
+                             nullptr, 0, config.flag);
+        ASSERT_EQ(0, rv);
+        if (config.flag == NI_NAMEREQD) {
+            if (config.hasSynthesizedPtrRecord) {
+                EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v6_synthesis));
+            } else {
+                EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v6_nomapping));  // PTR record not exist.
+                EXPECT_LE(1U, GetNumQueries(dns, ptr_addr_v4));            // PTR record exist.
+            }
+        }
+        std::string result_str = host;
+        EXPECT_EQ(result_str, config.host);
+        dns.clearQueries();
+    }
+}
+
+TEST_F(ResolverTest, GetNameInfo_ReverseDns64QueryFromHostFile) {
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "localhost";
+    // The address is synthesized by prefix64:localhost.
+    constexpr char host_addr[] = "64:ff9b::7f00:1";
+    constexpr char listen_addr[] = "::1";
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, {{dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"}});
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Using synthesized "localhost" address to be a trick for resolving host name
+    // from host file /etc/hosts and "localhost" is the only name in /etc/hosts. Note that this is
+    // not realistic: the code never synthesizes AAAA records for addresses in 127.0.0.0/8.
+    char host[NI_MAXHOST];
+    struct sockaddr_in6 sin6 = {.sin6_family = AF_INET6};
+    inet_pton(AF_INET6, host_addr, &sin6.sin6_addr);
+    int rv = getnameinfo((const struct sockaddr*) &sin6, sizeof(sin6), host, sizeof(host), nullptr,
+                         0, NI_NAMEREQD);
+    ASSERT_EQ(0, rv);
+    // Expect no DNS queries; localhost is resolved via /etc/hosts.
+    EXPECT_EQ(0U, GetNumQueries(dns, host_name));
+
+    std::string result_str = host;
+    EXPECT_EQ(result_str, host_name);
+}
+
+TEST_F(ResolverTest, GetHostByName2_Dns64Synthesize) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "ipv4only.example.com.";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Query an IPv4-only hostname. Expect that gets a synthesized address.
+    struct hostent* result = gethostbyname2("ipv4only", AF_INET6);
+    ASSERT_TRUE(result != nullptr);
+    EXPECT_LE(1U, GetNumQueries(dns, host_name));
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, "64:ff9b::102:304");
+}
+
+TEST_F(ResolverTest, GetHostByName2_DnsQueryWithHavingNat64Prefix) {
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    constexpr char host_name[] = "v4v6.example.com.";
+    constexpr char listen_addr[] = "::1";
+    const std::vector<DnsRecord> records = {
+            {dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"},
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "2001:db8::1.2.3.4"},
+    };
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, records);
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // IPv4 DNS query. Prefix should have no effect on it.
+    struct hostent* result = gethostbyname2("v4v6", AF_INET);
+    ASSERT_TRUE(result != nullptr);
+    EXPECT_LE(1U, GetNumQueries(dns, host_name));
+    std::string result_str = ToString(result);
+    EXPECT_EQ(result_str, "1.2.3.4");
+    dns.clearQueries();
+
+    // IPv6 DNS query. Prefix should have no effect on it.
+    result = gethostbyname2("v4v6", AF_INET6);
+    ASSERT_TRUE(result != nullptr);
+    EXPECT_LE(1U, GetNumQueries(dns, host_name));
+    result_str = ToString(result);
+    EXPECT_EQ(result_str, "2001:db8::102:304");
+}
+
+TEST_F(ResolverTest, GetHostByName2_Dns64QuerySpecialUseIPv4Addresses) {
+    constexpr char THIS_NETWORK[] = "this_network";
+    constexpr char LOOPBACK[] = "loopback";
+    constexpr char LINK_LOCAL[] = "link_local";
+    constexpr char MULTICAST[] = "multicast";
+    constexpr char LIMITED_BROADCAST[] = "limited_broadcast";
+
+    constexpr char ADDR_THIS_NETWORK[] = "0.0.0.1";
+    constexpr char ADDR_LOOPBACK[] = "127.0.0.1";
+    constexpr char ADDR_LINK_LOCAL[] = "169.254.0.1";
+    constexpr char ADDR_MULTICAST[] = "224.0.0.1";
+    constexpr char ADDR_LIMITED_BROADCAST[] = "255.255.255.255";
+
+    constexpr char listen_addr[] = "::1";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, {{dns64_name, ns_type::ns_t_aaaa, "64:ff9b::"}});
+    const std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork(servers));
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    static const struct TestConfig {
+        std::string name;
+        std::string addr;
+
+        std::string asHostName() const {
+            return StringPrintf("%s.example.com.",
+                                name.c_str());
+        }
+    } testConfigs[]{
+        {THIS_NETWORK,      ADDR_THIS_NETWORK},
+        {LOOPBACK,          ADDR_LOOPBACK},
+        {LINK_LOCAL,        ADDR_LINK_LOCAL},
+        {MULTICAST,         ADDR_MULTICAST},
+        {LIMITED_BROADCAST, ADDR_LIMITED_BROADCAST}
+    };
+
+    for (const auto& config : testConfigs) {
+        const std::string testHostName = config.asHostName();
+        SCOPED_TRACE(testHostName);
+
+        const char* host_name = testHostName.c_str();
+        dns.addMapping(host_name, ns_type::ns_t_a, config.addr.c_str());
+
+        struct hostent* result = gethostbyname2(config.name.c_str(), AF_INET6);
+        EXPECT_LE(1U, GetNumQueries(dns, host_name));
+
+        // In AF_INET6 case, don't synthesize special use IPv4 address.
+        // Expect to have no answer
+        EXPECT_EQ(nullptr, result);
+
+        dns.clearQueries();
+    }
+}
+
+TEST_F(ResolverTest, PrefixDiscoveryBypassTls) {
+    constexpr char listen_addr[] = "::1";
+    constexpr char cleartext_port[] = "53";
+    constexpr char tls_port[] = "853";
+    constexpr char dns64_name[] = "ipv4only.arpa.";
+    const std::vector<std::string> servers = {listen_addr};
+
+    test::DNSResponder dns(listen_addr);
+    StartDns(dns, {{dns64_name, ns_type::ns_t_aaaa, "64:ff9b::192.0.0.170"}});
+    test::DnsTlsFrontend tls(listen_addr, tls_port, listen_addr, cleartext_port);
+    ASSERT_TRUE(tls.startServer());
+
+    // Setup OPPORTUNISTIC mode and wait for the validation complete.
+    ASSERT_TRUE(
+            mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "", {}));
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+    tls.clearQueries();
+
+    // Start NAT64 prefix discovery and wait for it complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Verify it bypassed TLS even though there's a TLS server available.
+    EXPECT_EQ(0, tls.queries());
+    EXPECT_EQ(1U, GetNumQueries(dns, dns64_name));
+
+    // Restart the testing network to reset the cache.
+    mDnsClient.TearDown();
+    mDnsClient.SetUp();
+    dns.clearQueries();
+
+    // Setup STRICT mode and wait for the validation complete.
+    ASSERT_TRUE(mDnsClient.SetResolversWithTls(servers, kDefaultSearchDomains, kDefaultParams, "",
+                                               {base64Encode(tls.fingerprint())}));
+    EXPECT_TRUE(tls.waitForQueries(1, 5000));
+    tls.clearQueries();
+
+    // Start NAT64 prefix discovery and wait for it to complete.
+    EXPECT_TRUE(mDnsClient.resolvService()->startPrefix64Discovery(TEST_NETID).isOk());
+    EXPECT_TRUE(WaitForPrefix64Detected(TEST_NETID, 1000));
+
+    // Verify it bypassed TLS despite STRICT mode.
+    EXPECT_EQ(0, tls.queries());
+    EXPECT_EQ(1U, GetNumQueries(dns, dns64_name));
+}
+
+namespace {
+
+class ScopedSetNetworkForProcess {
+  public:
+    explicit ScopedSetNetworkForProcess(unsigned netId) {
+        mStoredNetId = getNetworkForProcess();
+        if (netId == mStoredNetId) return;
+        EXPECT_EQ(0, setNetworkForProcess(netId));
+    }
+    ~ScopedSetNetworkForProcess() { EXPECT_EQ(0, setNetworkForProcess(mStoredNetId)); }
+
+  private:
+    unsigned mStoredNetId;
+};
+
+class ScopedSetNetworkForResolv {
+  public:
+    explicit ScopedSetNetworkForResolv(unsigned netId) { EXPECT_EQ(0, setNetworkForResolv(netId)); }
+    ~ScopedSetNetworkForResolv() { EXPECT_EQ(0, setNetworkForResolv(NETID_UNSET)); }
+};
+
+void sendCommand(int fd, const std::string& cmd) {
+    ssize_t rc = TEMP_FAILURE_RETRY(write(fd, cmd.c_str(), cmd.size() + 1));
+    EXPECT_EQ(rc, static_cast<ssize_t>(cmd.size() + 1));
+}
+
+int32_t readBE32(int fd) {
+    int32_t tmp;
+    int n = TEMP_FAILURE_RETRY(read(fd, &tmp, sizeof(tmp)));
+    EXPECT_TRUE(n > 0);
+    return ntohl(tmp);
+}
+
+int readResponseCode(int fd) {
+    char buf[4];
+    int n = TEMP_FAILURE_RETRY(read(fd, &buf, sizeof(buf)));
+    EXPECT_TRUE(n > 0);
+    // The format of response code is that 4 bytes for the code & null.
+    buf[3] = '\0';
+    int result;
+    EXPECT_TRUE(ParseInt(buf, &result));
+    return result;
+}
+
+bool checkAndClearUseLocalNameserversFlag(unsigned* netid) {
+    if (netid == nullptr || ((*netid) & NETID_USE_LOCAL_NAMESERVERS) == 0) {
+        return false;
+    }
+    *netid = (*netid) & ~NETID_USE_LOCAL_NAMESERVERS;
+    return true;
+}
+
+android::net::UidRangeParcel makeUidRangeParcel(int start, int stop) {
+    android::net::UidRangeParcel res;
+    res.start = start;
+    res.stop = stop;
+
+    return res;
+}
+
+void expectNetIdWithLocalNameserversFlag(unsigned netId) {
+    unsigned dnsNetId = 0;
+    EXPECT_EQ(0, getNetworkForDns(&dnsNetId));
+    EXPECT_TRUE(checkAndClearUseLocalNameserversFlag(&dnsNetId));
+    EXPECT_EQ(netId, static_cast<unsigned>(dnsNetId));
+}
+
+void expectDnsNetIdEquals(unsigned netId) {
+    unsigned dnsNetId = 0;
+    EXPECT_EQ(0, getNetworkForDns(&dnsNetId));
+    EXPECT_EQ(netId, static_cast<unsigned>(dnsNetId));
+}
+
+void expectDnsNetIdIsDefaultNetwork(android::net::INetd* netdService) {
+    int currentNetid;
+    EXPECT_TRUE(netdService->networkGetDefault(&currentNetid).isOk());
+    expectDnsNetIdEquals(currentNetid);
+}
+
+void expectDnsNetIdWithVpn(android::net::INetd* netdService, unsigned vpnNetId,
+                           unsigned expectedNetId) {
+    EXPECT_TRUE(netdService->networkCreateVpn(vpnNetId, false /* secure */).isOk());
+    uid_t uid = getuid();
+    // Add uid to VPN
+    EXPECT_TRUE(netdService->networkAddUidRanges(vpnNetId, {makeUidRangeParcel(uid, uid)}).isOk());
+    expectDnsNetIdEquals(expectedNetId);
+    EXPECT_TRUE(netdService->networkDestroy(vpnNetId).isOk());
+}
+
+}  // namespace
+
+TEST_F(ResolverTest, getDnsNetId) {
+    // We've called setNetworkForProcess in SetupOemNetwork, so reset to default first.
+    setNetworkForProcess(NETID_UNSET);
+
+    expectDnsNetIdIsDefaultNetwork(mDnsClient.netdService());
+    expectDnsNetIdWithVpn(mDnsClient.netdService(), TEST_VPN_NETID, TEST_VPN_NETID);
+
+    // Test with setNetworkForProcess
+    {
+        ScopedSetNetworkForProcess scopedSetNetworkForProcess(TEST_NETID);
+        expectDnsNetIdEquals(TEST_NETID);
+    }
+
+    // Test with setNetworkForProcess with NETID_USE_LOCAL_NAMESERVERS
+    {
+        ScopedSetNetworkForProcess scopedSetNetworkForProcess(TEST_NETID |
+                                                              NETID_USE_LOCAL_NAMESERVERS);
+        expectNetIdWithLocalNameserversFlag(TEST_NETID);
+    }
+
+    // Test with setNetworkForResolv
+    {
+        ScopedSetNetworkForResolv scopedSetNetworkForResolv(TEST_NETID);
+        expectDnsNetIdEquals(TEST_NETID);
+    }
+
+    // Test with setNetworkForResolv with NETID_USE_LOCAL_NAMESERVERS
+    {
+        ScopedSetNetworkForResolv scopedSetNetworkForResolv(TEST_NETID |
+                                                            NETID_USE_LOCAL_NAMESERVERS);
+        expectNetIdWithLocalNameserversFlag(TEST_NETID);
+    }
+
+    // Test with setNetworkForResolv under bypassable vpn
+    {
+        ScopedSetNetworkForResolv scopedSetNetworkForResolv(TEST_NETID);
+        expectDnsNetIdWithVpn(mDnsClient.netdService(), TEST_VPN_NETID, TEST_NETID);
+    }
+
+    // Create socket connected to DnsProxyListener
+    int fd = dns_open_proxy();
+    EXPECT_TRUE(fd > 0);
+    unique_fd ufd(fd);
+
+    // Test command with wrong netId
+    sendCommand(fd, "getdnsnetid abc");
+    EXPECT_EQ(ResponseCode::DnsProxyQueryResult, readResponseCode(fd));
+    EXPECT_EQ(-EINVAL, readBE32(fd));
+
+    // Test unsupported command
+    sendCommand(fd, "getdnsnetidNotSupported");
+    // Keep in sync with FrameworkListener.cpp (500, "Command not recognized")
+    EXPECT_EQ(500, readResponseCode(fd));
+}
diff --git a/resolv/sethostent.cpp b/resolv/sethostent.cpp
new file mode 100644
index 0000000..2885fbc
--- /dev/null
+++ b/resolv/sethostent.cpp
@@ -0,0 +1,204 @@
+/*	$NetBSD: sethostent.c,v 1.20 2014/03/17 13:24:23 christos Exp $	*/
+
+/*
+ * Copyright (c) 1985, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include "hostent.h"
+#include "resolv_private.h"
+
+#define ALIGNBYTES (sizeof(uintptr_t) - 1)
+#define ALIGN(p) (((uintptr_t)(p) + ALIGNBYTES) & ~ALIGNBYTES)
+
+static void sethostent_r(FILE** hf) {
+    if (!*hf)
+        *hf = fopen(_PATH_HOSTS, "re");
+    else
+        rewind(*hf);
+}
+
+static void endhostent_r(FILE** hf) {
+    if (*hf) {
+        (void) fclose(*hf);
+        *hf = NULL;
+    }
+}
+
+// TODO: Consider returning a boolean result as files_getaddrinfo() does because the error code
+// does not currently return to netd.
+int _hf_gethtbyname2(const char* name, int af, getnamaddr* info) {
+    struct hostent *hp, hent;
+    char *buf, *ptr;
+    size_t len, num, i;
+    char* aliases[MAXALIASES];
+    char* addr_ptrs[MAXADDRS];
+
+    FILE* hf = NULL;
+    sethostent_r(&hf);
+    if (hf == NULL) {
+        // TODO: Consider converting to a private extended EAI_* error code.
+        // Currently, the EAI_* value has no corresponding error code for invalid argument socket
+        // length. In order to not rely on errno, convert the original error code pair, EAI_SYSTEM
+        // and EINVAL, to EAI_FAIL.
+        return EAI_FAIL;
+    }
+
+    if ((ptr = buf = (char*) malloc(len = info->buflen)) == NULL) {
+        return EAI_MEMORY;
+    }
+
+    hent.h_name = NULL;
+    hent.h_addrtype = 0;
+    hent.h_length = 0;
+
+    size_t anum = 0;
+    for (num = 0; num < MAXADDRS; /**/) {
+        info->hp->h_addrtype = af;
+        info->hp->h_length = 0;
+
+        int he;
+        hp = netbsd_gethostent_r(hf, info->hp, info->buf, info->buflen, &he);
+        if (hp == NULL) {
+            if (he == NETDB_INTERNAL && errno == ENOSPC) {
+                goto nospc;  // glibc compatibility.
+            }
+            break;
+        }
+
+        if (strcasecmp(hp->h_name, name) != 0) {
+            char** cp;
+            for (cp = hp->h_aliases; *cp != NULL; cp++)
+                if (strcasecmp(*cp, name) == 0) break;
+            // NOTE: does not increment num
+            if (*cp == NULL) continue;
+        }
+
+        if (num == 0) {
+            hent.h_addrtype = hp->h_addrtype;
+            hent.h_length = hp->h_length;
+
+            HENT_SCOPY(hent.h_name, hp->h_name, ptr, len);
+            for (anum = 0; hp->h_aliases[anum]; anum++) {
+                if (anum >= MAXALIASES) goto nospc;
+                HENT_SCOPY(aliases[anum], hp->h_aliases[anum], ptr, len);
+            }
+            ptr = (char*) ALIGN(ptr);
+            if ((size_t)(ptr - buf) >= info->buflen) goto nospc;
+        }
+
+        if (num >= MAXADDRS) goto nospc;
+        HENT_COPY(addr_ptrs[num], hp->h_addr_list[0], hp->h_length, ptr, len);
+
+        num++;
+    }
+    endhostent_r(&hf);
+
+    if (num == 0) {
+        free(buf);
+        // TODO: Perhaps convert HOST_NOT_FOUND to EAI_NONAME instead.
+        // The original return error number is h_errno HOST_NOT_FOUND which was converted to
+        // EAI_NODATA.
+        return EAI_NODATA;
+    }
+
+    hp = info->hp;
+    ptr = info->buf;
+    len = info->buflen;
+
+    hp->h_addrtype = hent.h_addrtype;
+    hp->h_length = hent.h_length;
+
+    HENT_ARRAY(hp->h_aliases, anum, ptr, len);
+    HENT_ARRAY(hp->h_addr_list, num, ptr, len);
+
+    for (i = 0; i < num; i++) {
+        HENT_COPY(hp->h_addr_list[i], addr_ptrs[i], hp->h_length, ptr, len);
+
+        // reserve space for mapping IPv4 address to IPv6 address in place
+        if (hp->h_addrtype == AF_INET) {
+            HENT_COPY(ptr, NAT64_PAD, sizeof(NAT64_PAD), ptr, len);
+        }
+    }
+    hp->h_addr_list[num] = NULL;
+
+    HENT_SCOPY(hp->h_name, hent.h_name, ptr, len);
+
+    for (i = 0; i < anum; i++) {
+        HENT_SCOPY(hp->h_aliases[i], aliases[i], ptr, len);
+    }
+    hp->h_aliases[anum] = NULL;
+
+    free(buf);
+    return 0;
+nospc:
+    free(buf);
+    return EAI_MEMORY;
+}
+
+// TODO: Consider returning a boolean result as files_getaddrinfo() does because the error code
+// does not currently return to netd.
+int _hf_gethtbyaddr(const unsigned char* uaddr, int len, int af, getnamaddr* info) {
+    info->hp->h_length = len;
+    info->hp->h_addrtype = af;
+
+    FILE* hf = NULL;
+    sethostent_r(&hf);
+    if (hf == NULL) {
+        // TODO: Consider converting to a private extended EAI_* error code.
+        // Currently, the EAI_* value has no corresponding error code for invalid argument socket
+        // length. In order to not rely on errno, convert the original error code pair, EAI_SYSTEM
+        // and EINVAL, to EAI_FAIL.
+        return EAI_FAIL;
+    }
+    struct hostent* hp;
+    int he;
+    while ((hp = netbsd_gethostent_r(hf, info->hp, info->buf, info->buflen, &he)) != NULL)
+        if (!memcmp(hp->h_addr_list[0], uaddr, (size_t) hp->h_length)) break;
+    endhostent_r(&hf);
+
+    if (hp == NULL) {
+        if (errno == ENOSPC) return EAI_MEMORY;  // glibc compatibility.
+        // TODO: Perhaps convert HOST_NOT_FOUND to EAI_NONAME instead.
+        // The original return error number is h_errno HOST_NOT_FOUND which was converted to
+        // EAI_NODATA.
+        return EAI_NODATA;
+    }
+    return 0;
+}
diff --git a/resolv/stats.proto b/resolv/stats.proto
new file mode 100644
index 0000000..dd9992c
--- /dev/null
+++ b/resolv/stats.proto
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+package android.net;
+
+enum EventType {
+    EVENT_UNKNOWN = 0;
+    EVENT_GETADDRINFO = 1;
+    EVENT_GETHOSTBYNAME = 2;
+    EVENT_GETHOSTBYADDR = 3;
+    EVENT_RES_NSEND = 4;
+}
+
+// The return value of the DNS resolver for each DNS lookups.
+// bionic/libc/include/netdb.h
+// system/netd/resolv/include/netd_resolv/resolv.h
+enum ReturnCode {
+    RC_EAI_NO_ERROR = 0;
+    RC_EAI_ADDRFAMILY = 1;
+    RC_EAI_AGAIN = 2;
+    RC_EAI_BADFLAGS = 3;
+    RC_EAI_FAIL = 4;
+    RC_EAI_FAMILY = 5;
+    RC_EAI_MEMORY = 6;
+    RC_EAI_NODATA = 7;
+    RC_EAI_NONAME = 8;
+    RC_EAI_SERVICE = 9;
+    RC_EAI_SOCKTYPE = 10;
+    RC_EAI_SYSTEM = 11;
+    RC_EAI_BADHINTS = 12;
+    RC_EAI_PROTOCOL = 13;
+    RC_EAI_OVERFLOW = 14;
+    RC_RESOLV_TIMEOUT = 255;
+    RC_EAI_MAX = 256;
+}
+
+enum NsRcode {
+    NS_R_NO_ERROR = 0;  // No error occurred.
+    NS_R_FORMERR = 1;   // Format error.
+    NS_R_SERVFAIL = 2;  // Server failure.
+    NS_R_NXDOMAIN = 3;  // Name error.
+    NS_R_NOTIMPL = 4;   // Unimplemented.
+    NS_R_REFUSED = 5;   // Operation refused.
+    // these are for BIND_UPDATE
+    NS_R_YXDOMAIN = 6;  // Name exists
+    NS_R_YXRRSET = 7;   // RRset exists
+    NS_R_NXRRSET = 8;   // RRset does not exist
+    NS_R_NOTAUTH = 9;   // Not authoritative for zone
+    NS_R_NOTZONE = 10;  // Zone of record different from zone section
+    NS_R_MAX = 11;
+    // The following are EDNS extended rcodes
+    NS_R_BADVERS = 16;
+    // The following are TSIG errors
+    // NS_R_BADSIG  = 16,
+    NS_R_BADKEY = 17;
+    NS_R_BADTIME = 18;
+}
+
+// Currently defined type values for resources and queries.
+enum NsType {
+    NS_T_INVALID = 0;      // Cookie.
+    NS_T_A = 1;            // Host address.
+    NS_T_NS = 2;           // Authoritative server.
+    NS_T_MD = 3;           // Mail destination.
+    NS_T_MF = 4;           // Mail forwarder.
+    NS_T_CNAME = 5;        // Canonical name.
+    NS_T_SOA = 6;          // Start of authority zone.
+    NS_T_MB = 7;           // Mailbox domain name.
+    NS_T_MG = 8;           // Mail group member.
+    NS_T_MR = 9;           // Mail rename name.
+    NS_T_NULL = 10;        // Null resource record.
+    NS_T_WKS = 11;         // Well known service.
+    NS_T_PTR = 12;         // Domain name pointer.
+    NS_T_HINFO = 13;       // Host information.
+    NS_T_MINFO = 14;       // Mailbox information.
+    NS_T_MX = 15;          // Mail routing information.
+    NS_T_TXT = 16;         // Text strings.
+    NS_T_RP = 17;          // Responsible person.
+    NS_T_AFSDB = 18;       // AFS cell database.
+    NS_T_X25 = 19;         // X_25 calling address.
+    NS_T_ISDN = 20;        // ISDN calling address.
+    NS_T_RT = 21;          // Router.
+    NS_T_NSAP = 22;        // NSAP address.
+    NS_T_NSAP_PTR = 23;    // Reverse NSAP lookup (deprecated).
+    NS_T_SIG = 24;         // Security signature.
+    NS_T_KEY = 25;         // Security key.
+    NS_T_PX = 26;          // X.400 mail mapping.
+    NS_T_GPOS = 27;        // Geographical position (withdrawn).
+    NS_T_AAAA = 28;        // IPv6 Address.
+    NS_T_LOC = 29;         // Location Information.
+    NS_T_NXT = 30;         // Next domain (security).
+    NS_T_EID = 31;         // Endpoint identifier.
+    NS_T_NIMLOC = 32;      // Nimrod Locator.
+    NS_T_SRV = 33;         // Server Selection.
+    NS_T_ATMA = 34;        // ATM Address
+    NS_T_NAPTR = 35;       // Naming Authority PoinTeR
+    NS_T_KX = 36;          // Key Exchange
+    NS_T_CERT = 37;        // Certification record
+    NS_T_A6 = 38;          // IPv6 address (experimental)
+    NS_T_DNAME = 39;       // Non-terminal DNAME
+    NS_T_SINK = 40;        // Kitchen sink (experimentatl)
+    NS_T_OPT = 41;         // EDNS0 option (meta-RR)
+    NS_T_APL = 42;         // Address prefix list (RFC 3123)
+    NS_T_DS = 43;          // Delegation Signer
+    NS_T_SSHFP = 44;       // SSH Fingerprint
+    NS_T_IPSECKEY = 45;    // IPSEC Key
+    NS_T_RRSIG = 46;       // RRset Signature
+    NS_T_NSEC = 47;        // Negative security
+    NS_T_DNSKEY = 48;      // DNS Key
+    NS_T_DHCID = 49;       // Dynamic host configuratin identifier
+    NS_T_NSEC3 = 50;       // Negative security type 3
+    NS_T_NSEC3PARAM = 51;  // Negative security type 3 parameters
+    NS_T_HIP = 55;         // Host Identity Protocol
+    NS_T_SPF = 99;         // Sender Policy Framework
+    NS_T_TKEY = 249;       // Transaction key
+    NS_T_TSIG = 250;       // Transaction signature.
+    NS_T_IXFR = 251;       // Incremental zone transfer.
+    NS_T_AXFR = 252;       // Transfer zone of authority.
+    NS_T_MAILB = 253;      // Transfer mailbox records.
+    NS_T_MAILA = 254;      // Transfer mail agent records.
+    NS_T_ANY = 255;        // Wildcard match.
+    NS_T_ZXFR = 256;       // BIND-specific, nonstandard.
+    NS_T_DLV = 32769;      // DNSSEC look-aside validatation.
+    NS_T_MAX = 65536;
+}
+
+enum IpVersion {
+    IV_UNKNOWN = 0;
+    IV_IPV4 = 1;
+    IV_IPV6 = 2;
+}
+
+enum TransportType {
+    TT_UNKNOWN = 0;
+    TT_UDP = 1;
+    TT_TCP = 2;
+    TT_DOT = 3;
+}
+
+enum PrivateDnsModes {
+    PDM_UNKNOWN = 0;
+    PDM_OFF = 1;
+    PDM_OPPORTUNISTIC = 2;
+    PDM_STRICT = 3;
+}
+
+enum Transport {
+    // Indicates this network uses a Cellular transport.
+    TRANSPORT_DEFAULT = 0;  // TRANSPORT_CELLULAR
+    // Indicates this network uses a Wi-Fi transport.
+    TRANSPORT_WIFI = 1;
+    // Indicates this network uses a Bluetooth transport.
+    TRANSPORT_BLUETOOTH = 2;
+    // Indicates this network uses an Ethernet transport.
+    TRANSPORT_ETHERNET = 3;
+    // Indicates this network uses a VPN transport.
+    TRANSPORT_VPN = 4;
+    // Indicates this network uses a Wi-Fi Aware transport.
+    TRANSPORT_WIFI_AWARE = 5;
+    // Indicates this network uses a LoWPAN transport.
+    TRANSPORT_LOWPAN = 6;
+}
+
+enum CacheStatus{
+    // the cache can't handle that kind of queries.
+    // or the answer buffer is too small.
+    CS_UNSUPPORTED = 0;
+    // the cache doesn't know about this query.
+    CS_NOTFOUND = 1;
+    // the cache found the answer.
+    CS_FOUND = 2;
+    // Don't do anything on cache.
+    CS_SKIP = 3;
+}
+
+message DnsQueryEvent {
+    optional NsRcode rcode = 1;
+
+    optional NsType type = 2;
+
+    optional CacheStatus cache_hit = 3;
+
+    optional IpVersion ip_version = 4;
+
+    optional TransportType transport = 5;
+
+    // Number of DNS query retry times
+    optional int32 retry_times = 6;
+
+    // Ordinal number of name server.
+    optional int32 dns_server_count = 7;
+
+    // Used only by TCP and DOT. True for new connections.
+    optional bool connected = 8;
+
+    optional int32 latency_micros = 9;
+}
+
+message DnsQueryEvents {
+    repeated DnsQueryEvent dns_query_event = 1;
+}
+
+/**
+ * Logs a DNS lookup operation initiated by the system resolver on behalf of an application
+ * invoking native APIs such as getaddrinfo() or Java APIs such as Network#getAllByName().
+ *
+ * The NetworkDnsEventReported message represents the entire lookup operation, which may
+ * result one or more queries to the recursive DNS resolvers. Those are individually logged
+ * in DnsQueryEvents to enable computing error rates and network latency and timeouts
+ * broken up by query type, transport, network interface, etc.
+ */
+message NetworkDnsEventReported {
+    optional EventType event_type = 1;
+
+    optional ReturnCode return_code = 2;
+
+    // The latency in microseconds of the entire DNS lookup operation.
+    optional int32 latency_micros = 3;
+
+    // Only valid for event_type = EVENT_GETADDRINFO.
+    optional int32 hints_ai_flags = 4;
+
+    // Flags passed to android_res_nsend() defined in multinetwork.h
+    // Only valid for event_type = EVENT_RESNSEND.
+    optional int32 res_nsend_flags = 5;
+
+    optional Transport network_type = 6;
+
+    // The DNS over TLS mode on a specific netId.
+    optional PrivateDnsModes private_dns_modes = 7;
+
+    // Additional pass-through fields opaque to statsd.
+    // The DNS resolver Mainline module can add new fields here without requiring an OS update.
+    optional DnsQueryEvents dns_query_events = 8;
+}
\ No newline at end of file
diff --git a/resolv/tests/BaseTestMetricsListener.cpp b/resolv/tests/BaseTestMetricsListener.cpp
new file mode 100644
index 0000000..414e383
--- /dev/null
+++ b/resolv/tests/BaseTestMetricsListener.cpp
@@ -0,0 +1,80 @@
+/**
+ * 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 "BaseTestMetricsListener.h"
+
+namespace android {
+namespace net {
+namespace metrics {
+
+void BaseTestMetricsListener::notify() {
+    std::lock_guard lock(mCvMutex);
+    mCv.notify_one();
+}
+
+void BaseTestMetricsListener::setVerified(EventFlag event) {
+    mVerified |= event;
+}
+
+android::binder::Status BaseTestMetricsListener::onDnsEvent(
+        int32_t /*netId*/, int32_t /*eventType*/, int32_t /*returnCode*/, int32_t /*latencyMs*/,
+        const std::string& /*hostname*/, const ::std::vector<std::string>& /*ipAddresses*/,
+        int32_t /*ipAddressesCount*/, int32_t /*uid*/) {
+    // default no-op
+    return android::binder::Status::ok();
+};
+
+android::binder::Status BaseTestMetricsListener::onPrivateDnsValidationEvent(
+        int32_t /*netId*/, const ::android::String16& /*ipAddress*/,
+        const ::android::String16& /*hostname*/, bool /*validated*/) {
+    // default no-op
+    return android::binder::Status::ok();
+};
+
+android::binder::Status BaseTestMetricsListener::onConnectEvent(
+        int32_t /*netId*/, int32_t /*error*/, int32_t /*latencyMs*/,
+        const ::android::String16& /*ipAddr*/, int32_t /*port*/, int32_t /*uid*/) {
+    // default no-op
+    return android::binder::Status::ok();
+};
+
+android::binder::Status BaseTestMetricsListener::onWakeupEvent(
+        const ::android::String16& /*prefix*/, int32_t /*uid*/, int32_t /*ethertype*/,
+        int32_t /*ipNextHeader*/, const ::std::vector<uint8_t>& /*dstHw*/,
+        const ::android::String16& /*srcIp*/, const ::android::String16& /*dstIp*/,
+        int32_t /*srcPort*/, int32_t /*dstPort*/, int64_t /*timestampNs*/) {
+    // default no-op
+    return android::binder::Status::ok();
+};
+
+android::binder::Status BaseTestMetricsListener::onTcpSocketStatsEvent(
+        const ::std::vector<int32_t>& /*networkIds*/, const ::std::vector<int32_t>& /*sentPackets*/,
+        const ::std::vector<int32_t>& /*lostPackets*/, const ::std::vector<int32_t>& /*rttUs*/,
+        const ::std::vector<int32_t>& /*sentAckDiffMs*/) {
+    // default no-op
+    return android::binder::Status::ok();
+};
+
+android::binder::Status BaseTestMetricsListener::onNat64PrefixEvent(
+        int32_t /*netId*/, bool /*added*/, const ::std::string& /*prefixString*/,
+        int32_t /*prefixLength*/) {
+    // default no-op
+    return android::binder::Status::ok();
+};
+
+}  // namespace metrics
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/resolv/tests/BaseTestMetricsListener.h b/resolv/tests/BaseTestMetricsListener.h
new file mode 100644
index 0000000..326647b
--- /dev/null
+++ b/resolv/tests/BaseTestMetricsListener.h
@@ -0,0 +1,104 @@
+/**
+ * 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 _BASE_TEST_METRICS_LISTENER_H_
+#define _BASE_TEST_METRICS_LISTENER_H_
+
+#include <string>
+#include <vector>
+
+#include <binder/BinderService.h>
+
+#include "android/net/metrics/BnNetdEventListener.h"
+
+enum EventFlag : uint32_t {
+    onDnsEvent = 1 << 0,
+    onPrivateDnsValidationEvent = 1 << 1,
+    onConnectEvent = 1 << 2,
+    onWakeupEvent = 1 << 3,
+    onTcpSocketStatsEvent = 1 << 4,
+    onNat64PrefixEvent = 1 << 5,
+};
+
+namespace android {
+namespace net {
+namespace metrics {
+
+class BaseTestMetricsListener : public BnNetdEventListener {
+  public:
+    BaseTestMetricsListener() = default;
+    ~BaseTestMetricsListener() = default;
+
+    // Returns TRUE if the verification was successful. Otherwise, returns FALSE.
+    virtual bool isVerified() = 0;
+
+    std::condition_variable& getCv() { return mCv; }
+    std::mutex& getCvMutex() { return mCvMutex; }
+
+    android::binder::Status onDnsEvent(int32_t /*netId*/, int32_t /*eventType*/,
+                                       int32_t /*returnCode*/, int32_t /*latencyMs*/,
+                                       const std::string& /*hostname*/,
+                                       const ::std::vector<std::string>& /*ipAddresses*/,
+                                       int32_t /*ipAddressesCount*/, int32_t /*uid*/) override;
+    android::binder::Status onPrivateDnsValidationEvent(int32_t /*netId*/,
+                                                        const ::android::String16& /*ipAddress*/,
+                                                        const ::android::String16& /*hostname*/,
+                                                        bool /*validated*/) override;
+    android::binder::Status onConnectEvent(int32_t /*netId*/, int32_t /*error*/,
+                                           int32_t /*latencyMs*/,
+                                           const ::android::String16& /*ipAddr*/, int32_t /*port*/,
+                                           int32_t /*uid*/) override;
+    android::binder::Status onWakeupEvent(const ::android::String16& /*prefix*/, int32_t /*uid*/,
+                                          int32_t /*ethertype*/, int32_t /*ipNextHeader*/,
+                                          const ::std::vector<uint8_t>& /*dstHw*/,
+                                          const ::android::String16& /*srcIp*/,
+                                          const ::android::String16& /*dstIp*/, int32_t /*srcPort*/,
+                                          int32_t /*dstPort*/, int64_t /*timestampNs*/) override;
+    android::binder::Status onTcpSocketStatsEvent(
+            const ::std::vector<int32_t>& /*networkIds*/,
+            const ::std::vector<int32_t>& /*sentPackets*/,
+            const ::std::vector<int32_t>& /*lostPackets*/, const ::std::vector<int32_t>& /*rttUs*/,
+            const ::std::vector<int32_t>& /*sentAckDiffMs*/) override;
+    android::binder::Status onNat64PrefixEvent(int32_t /*netId*/, bool /*added*/,
+                                               const ::std::string& /*prefixString*/,
+                                               int32_t /*prefixLength*/) override;
+
+  private:
+    // The verified event(s) as a bitwise-OR combination of enum EventFlag flags.
+    uint32_t mVerified{};
+
+    // This lock prevents racing condition between signaling thread(s) and waiting thread(s).
+    std::mutex mCvMutex;
+
+    // Condition variable signaled when notify() is called.
+    std::condition_variable mCv;
+
+  protected:
+    // Notify who is waiting for test results. See also mCvMutex and mCv.
+    void notify();
+
+    // Get current verified event(s).
+    uint32_t getVerified() const { return mVerified; }
+
+    // Set the specific event as verified if its verification was successful.
+    void setVerified(EventFlag event);
+};
+
+}  // namespace metrics
+}  // namespace net
+}  // namespace android
+
+#endif  // _BASE_TEST_METRICS_LISTENER_H_
diff --git a/resolv/tests/TestMetrics.cpp b/resolv/tests/TestMetrics.cpp
new file mode 100644
index 0000000..4c3d36a
--- /dev/null
+++ b/resolv/tests/TestMetrics.cpp
@@ -0,0 +1,74 @@
+/**
+ * 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 <netdb.h>
+
+#include "TestMetrics.h"
+
+namespace android {
+namespace net {
+namespace metrics {
+
+android::binder::Status TestOnDnsEvent::onDnsEvent(int32_t netId, int32_t eventType,
+                                                   int32_t returnCode, int32_t /*latencyMs*/,
+                                                   const std::string& hostname,
+                                                   const ::std::vector<std::string>& ipAddresses,
+                                                   int32_t ipAddressesCount, int32_t /*uid*/) {
+    // A bitwise-OR combination of all expected test cases.
+    // Currently, the maximum number of test case is 32 because a 32-bits bitwise-OR combination
+    // is used for checking and recording verified test cases.
+    static const uint32_t kExpectedTests = (1 << mResults.size()) - 1;
+
+    // A bitwise-OR combination for recording verified test cases.
+    static uint32_t verifiedTests = 0;
+
+    for (size_t i = 0; i < mResults.size(); ++i) {
+        // Generate bitwise flag to trace which testcase is running.
+        const uint32_t currentTestFlag = 1 << i;
+
+        // Ignore verified testcase.
+        if (verifiedTests & currentTestFlag) continue;
+
+        // Verify expected event content.
+        auto& result = mResults[i];
+        if (netId != result.netId) continue;
+        if (eventType != result.eventType) continue;
+        if (hostname != result.hostname) continue;
+        if (returnCode != result.returnCode) continue;
+        if (ipAddressesCount != result.ipAddressesCount) continue;
+        if (result.returnCode == 0 /*success*/) {
+            // Only verify first address.
+            // TODO: Check all addresses.
+            if (ipAddresses.empty() || ipAddresses.front() != result.ipAddress) continue;
+        }
+
+        // Record current testcase as verified.
+        verifiedTests |= currentTestFlag;
+        break;
+    }
+
+    // All testcases of onDnsEvent are verified. Notify who was waiting for test results.
+    if (verifiedTests == kExpectedTests) {
+        setVerified(EventFlag::onDnsEvent);
+        notify();
+    }
+
+    return android::binder::Status::ok();
+};
+
+}  // namespace metrics
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/resolv/tests/TestMetrics.h b/resolv/tests/TestMetrics.h
new file mode 100644
index 0000000..d3a4ad4
--- /dev/null
+++ b/resolv/tests/TestMetrics.h
@@ -0,0 +1,58 @@
+/**
+ * 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 _TEST_METRICS_H_
+#define _TEST_METRICS_H_
+
+#include "BaseTestMetricsListener.h"
+
+namespace android {
+namespace net {
+namespace metrics {
+
+class TestOnDnsEvent : public BaseTestMetricsListener {
+  public:
+    // Both latencyMs and uid are not verified. No special reason.
+    struct TestResult {
+        int netId;
+        int eventType;
+        int returnCode;
+        int ipAddressesCount;
+        std::string hostname;
+        std::string ipAddress;  // Check first address only.
+    };
+
+    TestOnDnsEvent() = delete;
+    TestOnDnsEvent(const std::vector<TestResult>& results) : mResults(results){};
+
+    // BaseTestMetricsListener::isVerified() override.
+    bool isVerified() override { return (getVerified() & EventFlag::onDnsEvent) != 0; }
+
+    // Override for testing verification.
+    android::binder::Status onDnsEvent(int32_t netId, int32_t eventType, int32_t returnCode,
+                                       int32_t /*latencyMs*/, const std::string& hostname,
+                                       const std::vector<std::string>& ipAddresses,
+                                       int32_t ipAddressesCount, int32_t /*uid*/) override;
+
+  private:
+    const std::vector<TestResult>& mResults;  // Expected results for test verification.
+};
+
+}  // namespace metrics
+}  // namespace net
+}  // namespace android
+
+#endif  // _TEST_METRICS_H_
diff --git a/server/Android.bp b/server/Android.bp
index 341f510..5129e44 100644
--- a/server/Android.bp
+++ b/server/Android.bp
@@ -1,11 +1,258 @@
-// AIDL interface between netd and framework.jar
-filegroup {
-    name: "netd_aidl",
-    srcs: ["binder/android/net/INetd.aidl"],
+// AIDL interface between netd and services.core
+aidl_interface {
+    name: "netd_event_listener_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/metrics/INetdEventListener.aidl",
+    ],
+    api_dir: "aidl/netdeventlistener",
+    versions: ["1"],
 }
 
-// AIDL interface between netd and services.core
+// These are used in netd_integration_test
+// TODO: fold these into a cc_library_static after converting netd/server to Android.bp
 filegroup {
-    name: "netd_metrics_aidl",
-    srcs: ["binder/android/net/metrics/INetdEventListener.aidl"],
+    name: "netd_integration_test_shared",
+    srcs: [
+        "NetdConstants.cpp",
+        "InterfaceController.cpp",
+        "NetlinkCommands.cpp",
+        "NetlinkListener.cpp",
+        "XfrmController.cpp",
+        "TrafficController.cpp",
+    ],
+}
+
+aidl_interface {
+    name: "netd_aidl_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/INetd.aidl",
+        // AIDL interface that callers can implement to receive networking events from netd.
+        "binder/android/net/INetdUnsolicitedEventListener.aidl",
+        "binder/android/net/InterfaceConfigurationParcel.aidl",
+        "binder/android/net/TetherStatsParcel.aidl",
+        "binder/android/net/UidRangeParcel.aidl",
+    ],
+    api_dir: "aidl/netd",
+    backend: {
+        cpp: {
+            gen_log: true,
+        },
+    },
+    versions: [
+        "1",
+        "2",
+    ],
+}
+
+aidl_interface {
+    // This interface is for OEM calls to netd and vice versa that do not exist in AOSP.
+    // Those calls cannot be part of INetd.aidl and INetdUnsolicitedEventListener.aidl
+    // because those interfaces are versioned.
+    // These interfaces must never be versioned or OEMs will not be able to change them.
+    name: "oemnetd_aidl_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/com/android/internal/net/IOemNetd.aidl",
+        "binder/com/android/internal/net/IOemNetdUnsolicitedEventListener.aidl",
+    ],
+}
+
+// Modules common to both netd and netd_unit_test
+cc_library_static {
+    name: "libnetd_server",
+    defaults: ["netd_defaults"],
+    include_dirs: [
+        "system/netd/include",
+        "system/netd/server/binder",
+    ],
+    srcs: [
+        "BandwidthController.cpp",
+        "ClatdController.cpp",
+        "ClatUtils.cpp",
+        "Controllers.cpp",
+        "NetdConstants.cpp",
+        "FirewallController.cpp",
+        "IdletimerController.cpp",
+        "InterfaceController.cpp",
+        "IptablesRestoreController.cpp",
+        "NFLogListener.cpp",
+        "NetlinkCommands.cpp",
+        "NetlinkListener.cpp",
+        "NetlinkManager.cpp",
+        "RouteController.cpp",
+        "SockDiag.cpp",
+        "StrictController.cpp",
+        "TcpSocketMonitor.cpp",
+        "TetherController.cpp",
+        "TrafficController.cpp",
+        "UidRanges.cpp",
+        "WakeupController.cpp",
+        "XfrmController.cpp",
+    ],
+    shared_libs: [
+        "libbpf_android",
+        "libbase",
+        "libbinder",
+        "liblogwrap",
+        "libnetdbpf",
+        "libnetutils",
+        "libnetdutils",
+        "libpcap",
+        "libqtaguid",
+        "libssl",
+        "netd_aidl_interface-V2-cpp",
+        "netd_event_listener_interface-V1-cpp",
+    ],
+    header_libs: [
+        "libnetd_resolv_headers",
+    ],
+    aidl: {
+        export_aidl_headers: true,
+        local_include_dirs: ["binder"],
+    },
+}
+
+cc_binary {
+    name: "netd",
+    defaults: ["netd_defaults"],
+    include_dirs: [
+        "external/mdnsresponder/mDNSShared",
+        "system/netd/include",
+    ],
+    init_rc: ["netd.rc"],
+    shared_libs: [
+        "android.system.net.netd@1.0",
+        "android.system.net.netd@1.1",
+        "libbase",
+        "libbinder",
+        "libbpf_android",
+        "libcutils",
+        "libdl",
+        "libhidlbase",
+        "libhidltransport",
+        "libjsoncpp",
+        "liblog",
+        "liblogwrap",
+        "libmdnssd",
+        "libnetdbpf",
+        "libnetdutils",
+        "libnetutils",
+        "libpcap",
+        "libprocessgroup",
+        "libqtaguid",
+        "libselinux",
+        "libsysutils",
+        "libutils",
+        "netd_aidl_interface-V2-cpp",
+        "netd_event_listener_interface-V1-cpp",
+        "oemnetd_aidl_interface-cpp",
+    ],
+    static_libs: [
+        "libnetd_server",
+    ],
+    header_libs: [
+        "libnetd_resolv_headers",
+    ],
+    srcs: [
+        "DummyNetwork.cpp",
+        "EventReporter.cpp",
+        "FwmarkServer.cpp",
+        "LocalNetwork.cpp",
+        "MDnsSdListener.cpp",
+        "NetdCommand.cpp",
+        "NetdHwService.cpp",
+        "NetdNativeService.cpp",
+        "NetlinkHandler.cpp",
+        "Network.cpp",
+        "NetworkController.cpp",
+        "OemNetdListener.cpp",
+        "PhysicalNetwork.cpp",
+        "PppController.cpp",
+        "Process.cpp",
+        "ResolvStub.cpp",
+        "VirtualNetwork.cpp",
+        "main.cpp",
+        "oem_iptables_hook.cpp",
+    ],
+}
+
+cc_binary {
+    name: "ndc",
+    defaults: ["netd_defaults"],
+    include_dirs: [
+        "system/netd/include",
+    ],
+    header_libs: [
+        "libnetd_client_headers",
+    ],
+    shared_libs: [
+        "libbase",
+        "libnetdutils",
+        "libnetutils",
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libbinder",
+        "dnsresolver_aidl_interface-V2-cpp",
+        "netd_aidl_interface-V2-cpp",
+    ],
+    srcs: [
+        "ndc.cpp",
+        "UidRanges.cpp",
+        "NdcDispatcher.cpp",
+    ],
+}
+
+cc_test {
+    name: "netd_unit_test",
+    defaults: ["netd_defaults"],
+    test_suites: ["device-tests"],
+    include_dirs: [
+        "system/netd/include",
+        "system/netd/server/binder",
+        "system/netd/tests",
+        "system/core/logwrapper/include",
+    ],
+    srcs: [
+        "BandwidthControllerTest.cpp",
+        "ClatdControllerTest.cpp",
+        "ClatUtilsTest.cpp",
+        "ControllersTest.cpp",
+        "FirewallControllerTest.cpp",
+        "IdletimerControllerTest.cpp",
+        "InterfaceControllerTest.cpp",
+        "IptablesBaseTest.cpp",
+        "IptablesRestoreControllerTest.cpp",
+        "NFLogListenerTest.cpp",
+        "RouteControllerTest.cpp",
+        "SockDiagTest.cpp",
+        "StrictControllerTest.cpp",
+        "TetherControllerTest.cpp",
+        "TrafficControllerTest.cpp",
+        "XfrmControllerTest.cpp",
+        "WakeupControllerTest.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+        "libnetd_server",
+        "libnetd_test_tun_interface",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libbpf_android",
+        "libcrypto",
+        "libcutils",
+        "liblog",
+        "libnetdbpf",
+        "libnetdutils",
+        "libnetutils",
+        "libqtaguid",
+        "libsysutils",
+        "libutils",
+        "netd_aidl_interface-V2-cpp",
+        "netd_event_listener_interface-V1-cpp",
+    ],
 }
diff --git a/server/Android.mk b/server/Android.mk
deleted file mode 100644
index 2487696..0000000
--- a/server/Android.mk
+++ /dev/null
@@ -1,234 +0,0 @@
-# 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.
-
-LOCAL_PATH := $(call my-dir)
-
-###
-### netd service AIDL interface.
-###
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS := -Wall -Werror -Wthread-safety
-LOCAL_MODULE := libnetdaidl_static
-LOCAL_SHARED_LIBRARIES := \
-        libbinder \
-        libutils
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/binder
-LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder frameworks/native/aidl/binder
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/binder
-LOCAL_SRC_FILES := \
-        binder/android/net/INetd.aidl \
-        binder/android/net/UidRange.cpp
-
-include $(BUILD_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS := -Wall -Werror -Wthread-safety
-LOCAL_MODULE := libnetdaidl
-LOCAL_SHARED_LIBRARIES := \
-        libbinder \
-        libutils
-LOCAL_WHOLE_STATIC_LIBRARIES := libnetdaidl_static
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/binder
-
-include $(BUILD_SHARED_LIBRARY)
-
-###
-### netd daemon.
-###
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES := \
-        $(call include-path-for, libhardware_legacy)/hardware_legacy \
-        bionic/libc/dns/include \
-        external/mdnsresponder/mDNSShared \
-        system/netd/include \
-
-LOCAL_CPPFLAGS := -Wall -Werror -Wthread-safety -Wnullable-to-nonnull-conversion
-LOCAL_MODULE := netd
-
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CPPFLAGS +=  -Wno-varargs \
-
-ifeq ($(TARGET_ARCH), x86)
-ifneq ($(TARGET_PRODUCT), gce_x86_phone)
-        LOCAL_CPPFLAGS += -D NETLINK_COMPAT32
-endif
-endif
-
-LOCAL_INIT_RC := netd.rc
-
-LOCAL_SHARED_LIBRARIES := \
-        android.system.net.netd@1.0 \
-        android.system.net.netd@1.1 \
-        libbinder \
-        libbpf    \
-        libcrypto \
-        libcutils \
-        libdl \
-        libhidlbase \
-        libhidltransport \
-        liblog \
-        liblogwrap \
-        libmdnssd \
-        libnetdaidl \
-        libnetutils \
-        libnetdutils \
-        libselinux \
-        libssl \
-        libsysutils \
-        libbase \
-        libutils \
-        libpcap \
-        libqtaguid \
-
-LOCAL_SRC_FILES := \
-        BandwidthController.cpp \
-        ClatdController.cpp \
-        CommandListener.cpp \
-        Controllers.cpp \
-        DnsProxyListener.cpp \
-        DummyNetwork.cpp \
-        DumpWriter.cpp \
-        EventReporter.cpp \
-        FirewallController.cpp \
-        FwmarkServer.cpp \
-        IdletimerController.cpp \
-        InterfaceController.cpp \
-        IptablesRestoreController.cpp \
-        LocalNetwork.cpp \
-        MDnsSdListener.cpp \
-        NetdCommand.cpp \
-        NetdConstants.cpp \
-        NetdHwService.cpp \
-        NetdNativeService.cpp \
-        NetlinkHandler.cpp \
-        NetlinkManager.cpp \
-        NetlinkCommands.cpp \
-        NetlinkListener.cpp \
-        Network.cpp \
-        NetworkController.cpp \
-        NFLogListener.cpp \
-        PhysicalNetwork.cpp \
-        PppController.cpp \
-        ResolverController.cpp \
-        RouteController.cpp \
-        SockDiag.cpp \
-        StrictController.cpp \
-        TetherController.cpp \
-        TrafficController.cpp \
-        UidRanges.cpp \
-        VirtualNetwork.cpp \
-        WakeupController.cpp \
-        XfrmController.cpp \
-        TcpSocketMonitor.cpp \
-        main.cpp \
-        oem_iptables_hook.cpp \
-        binder/android/net/UidRange.cpp \
-        binder/android/net/metrics/INetdEventListener.aidl \
-        dns/DnsTlsDispatcher.cpp \
-        dns/DnsTlsQueryMap.cpp \
-        dns/DnsTlsTransport.cpp \
-        dns/DnsTlsServer.cpp \
-        dns/DnsTlsSessionCache.cpp \
-        dns/DnsTlsSocket.cpp \
-
-LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder
-
-include $(BUILD_EXECUTABLE)
-
-
-###
-### ndc binary.
-###
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS := -Wall -Werror -Wthread-safety
-LOCAL_SANITIZE := unsigned-integer-overflow
-LOCAL_CLANG := true
-LOCAL_MODULE := ndc
-LOCAL_SHARED_LIBRARIES := libcutils
-LOCAL_SRC_FILES := ndc.cpp
-
-include $(BUILD_EXECUTABLE)
-
-###
-### netd unit tests.
-###
-include $(CLEAR_VARS)
-LOCAL_MODULE := netd_unit_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_SANITIZE := unsigned-integer-overflow
-LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter -Wthread-safety
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CFLAGS += -Wno-varargs
-
-LOCAL_C_INCLUDES := \
-        bionic/libc/dns/include \
-        system/netd/include \
-        system/netd/server \
-        system/netd/server/binder \
-        system/netd/tests \
-        system/core/logwrapper/include \
-
-LOCAL_SRC_FILES := \
-        InterfaceController.cpp InterfaceControllerTest.cpp \
-        Controllers.cpp ControllersTest.cpp \
-        NetdConstants.cpp IptablesBaseTest.cpp \
-        IptablesRestoreController.cpp IptablesRestoreControllerTest.cpp \
-        BandwidthController.cpp BandwidthControllerTest.cpp \
-        FirewallControllerTest.cpp FirewallController.cpp \
-        IdletimerController.cpp IdletimerControllerTest.cpp \
-        NetlinkCommands.cpp NetlinkManager.cpp \
-        RouteController.cpp RouteControllerTest.cpp \
-        SockDiagTest.cpp SockDiag.cpp \
-        StrictController.cpp StrictControllerTest.cpp \
-        TetherController.cpp TetherControllerTest.cpp \
-        TrafficController.cpp TrafficControllerTest.cpp \
-        XfrmController.cpp XfrmControllerTest.cpp \
-        TcpSocketMonitor.cpp \
-        UidRanges.cpp \
-        NetlinkListener.cpp \
-        WakeupController.cpp WakeupControllerTest.cpp \
-        NFLogListener.cpp NFLogListenerTest.cpp \
-        binder/android/net/UidRange.cpp \
-        binder/android/net/metrics/INetdEventListener.aidl \
-        ../tests/tun_interface.cpp \
-        dns/DnsTlsDispatcher.cpp \
-        dns/DnsTlsTransport.cpp \
-        dns/DnsTlsServer.cpp \
-        dns/DnsTlsSessionCache.cpp \
-        dns/DnsTlsSocket.cpp \
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_LIBRARIES := libgmock libpcap
-LOCAL_SHARED_LIBRARIES := \
-        libbpf    \
-        libnetdaidl \
-        libbase \
-        libbinder \
-        libcrypto \
-        libcutils \
-        liblog \
-        liblogwrap \
-        libnetutils \
-        libnetdutils \
-        libqtaguid \
-        libsysutils \
-        libutils \
-        libssl \
-
-include $(BUILD_NATIVE_TEST)
-
diff --git a/server/BandwidthController.cpp b/server/BandwidthController.cpp
index 28dcbc1..7e9bff7 100644
--- a/server/BandwidthController.cpp
+++ b/server/BandwidthController.cpp
@@ -46,8 +46,8 @@
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #define LOG_TAG "BandwidthController"
-#include <cutils/log.h>
 #include <cutils/properties.h>
+#include <log/log.h>
 #include <logwrap/logwrap.h>
 
 #include <netdutils/Syscalls.h>
@@ -56,6 +56,7 @@
 #include "FirewallController.h" /* For makeCriticalCommands */
 #include "Fwmark.h"
 #include "NetdConstants.h"
+#include "TrafficController.h"
 #include "bpf/BpfUtils.h"
 
 /* Alphabetical */
@@ -65,14 +66,16 @@
 const char BandwidthController::LOCAL_OUTPUT[] = "bw_OUTPUT";
 const char BandwidthController::LOCAL_RAW_PREROUTING[] = "bw_raw_PREROUTING";
 const char BandwidthController::LOCAL_MANGLE_POSTROUTING[] = "bw_mangle_POSTROUTING";
+const char BandwidthController::LOCAL_GLOBAL_ALERT[] = "bw_global_alert";
 
 auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput;
 
 using android::base::Join;
 using android::base::StringAppendF;
 using android::base::StringPrintf;
-using android::bpf::XT_BPF_EGRESS_PROG_PATH;
-using android::bpf::XT_BPF_INGRESS_PROG_PATH;
+using android::net::FirewallController;
+using android::net::gCtls;
+using android::netdutils::Status;
 using android::netdutils::StatusOr;
 using android::netdutils::UniqueFile;
 
@@ -147,31 +150,35 @@
  */
 
 const std::string COMMIT_AND_CLOSE = "COMMIT\n";
-const std::string HAPPY_BOX_WHITELIST_COMMAND = StringPrintf(
-    "-I bw_happy_box -m owner --uid-owner %d-%d --jump RETURN", 0, MAX_SYSTEM_UID);
+const std::string HAPPY_BOX_MATCH_WHITELIST_COMMAND =
+        StringPrintf("-I bw_happy_box -m owner --uid-owner %d-%d --jump RETURN", 0, MAX_SYSTEM_UID);
+const std::string BPF_HAPPY_BOX_MATCH_WHITELIST_COMMAND = StringPrintf(
+        "-I bw_happy_box -m bpf --object-pinned %s -j RETURN", XT_BPF_WHITELIST_PROG_PATH);
+const std::string BPF_PENALTY_BOX_MATCH_BLACKLIST_COMMAND = StringPrintf(
+        "-I bw_penalty_box -m bpf --object-pinned %s -j REJECT", XT_BPF_BLACKLIST_PROG_PATH);
 
 static const std::vector<std::string> IPT_FLUSH_COMMANDS = {
-    /*
-     * Cleanup rules.
-     * Should normally include bw_costly_<iface>, but we rely on the way they are setup
-     * to allow coexistance.
-     */
-    "*filter",
-    ":bw_INPUT -",
-    ":bw_OUTPUT -",
-    ":bw_FORWARD -",
-    ":bw_happy_box -",
-    ":bw_penalty_box -",
-    ":bw_data_saver -",
-    ":bw_costly_shared -",
-    "COMMIT",
-    "*raw",
-    ":bw_raw_PREROUTING -",
-    "COMMIT",
-    "*mangle",
-    ":bw_mangle_POSTROUTING -",
-    COMMIT_AND_CLOSE
-};
+        /*
+         * Cleanup rules.
+         * Should normally include bw_costly_<iface>, but we rely on the way they are setup
+         * to allow coexistance.
+         */
+        "*filter",
+        ":bw_INPUT -",
+        ":bw_OUTPUT -",
+        ":bw_FORWARD -",
+        ":bw_happy_box -",
+        ":bw_penalty_box -",
+        ":bw_data_saver -",
+        ":bw_costly_shared -",
+        ":bw_global_alert -",
+        "COMMIT",
+        "*raw",
+        ":bw_raw_PREROUTING -",
+        "COMMIT",
+        "*mangle",
+        ":bw_mangle_POSTROUTING -",
+        COMMIT_AND_CLOSE};
 
 static const uint32_t uidBillingMask = Fwmark::getUidBillingMask();
 
@@ -206,69 +213,66 @@
  * See go/ipsec-data-accounting for more information.
  */
 
-const std::vector<std::string> getBasicAccountingCommands() {
-    bool useBpf = BandwidthController::getBpfStatsStatus();
+const std::vector<std::string> getBasicAccountingCommands(const bool useBpf) {
     const std::vector<std::string> ipt_basic_accounting_commands = {
-        "*filter",
-        // Prevents IPSec double counting (ESP and UDP-encap-ESP respectively)
-        "-A bw_INPUT -p esp -j RETURN",
-        StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN",
-                     uidBillingMask, uidBillingMask),
-        "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */
-        StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x", uidBillingMask),
+            "*filter",
 
-        // Prevents IPSec double counting (Tunnel mode and Transport mode,
-        // respectively)
-        "-A bw_OUTPUT -o " IPSEC_IFACE_PREFIX "+ -j RETURN",
-        "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN",
-        "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
+            "-A bw_INPUT -j bw_global_alert",
+            // Prevents IPSec double counting (ESP and UDP-encap-ESP respectively)
+            "-A bw_INPUT -p esp -j RETURN",
+            StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN", uidBillingMask,
+                         uidBillingMask),
+            useBpf ? "" : "-A bw_INPUT -m owner --socket-exists",
+            StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x", uidBillingMask),
 
-        "-A bw_costly_shared --jump bw_penalty_box",
-        "-A bw_penalty_box --jump bw_happy_box",
-        "-A bw_happy_box --jump bw_data_saver",
-        "-A bw_data_saver -j RETURN",
-        HAPPY_BOX_WHITELIST_COMMAND,
-        "COMMIT",
+            "-A bw_OUTPUT -j bw_global_alert",
+            // Prevents IPSec double counting (Tunnel mode and Transport mode,
+            // respectively)
+            "-A bw_OUTPUT -o " IPSEC_IFACE_PREFIX "+ -j RETURN",
+            "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN",
+            useBpf ? "" : "-A bw_OUTPUT -m owner --socket-exists",
 
-        "*raw",
-        // Prevents IPSec double counting (Tunnel mode and Transport mode,
-        // respectively)
-        "-A bw_raw_PREROUTING -i " IPSEC_IFACE_PREFIX "+ -j RETURN",
-        "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN",
-        "-A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */
-        useBpf ? StringPrintf("-A bw_raw_PREROUTING -m bpf --object-pinned %s",
-                              XT_BPF_INGRESS_PROG_PATH):"",
-        "COMMIT",
+            "-A bw_costly_shared --jump bw_penalty_box",
+            useBpf ? BPF_PENALTY_BOX_MATCH_BLACKLIST_COMMAND : "",
+            "-A bw_penalty_box --jump bw_happy_box", "-A bw_happy_box --jump bw_data_saver",
+            "-A bw_data_saver -j RETURN",
+            useBpf ? BPF_HAPPY_BOX_MATCH_WHITELIST_COMMAND : HAPPY_BOX_MATCH_WHITELIST_COMMAND,
+            "COMMIT",
 
-        "*mangle",
-        // Prevents IPSec double counting (Tunnel mode and Transport mode,
-        // respectively)
-        "-A bw_mangle_POSTROUTING -o " IPSEC_IFACE_PREFIX "+ -j RETURN",
-        "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN",
-        "-A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */
-        StringPrintf("-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x%x",
-                     uidBillingMask), // Clear the mark before sending this packet
-        useBpf ? StringPrintf("-A bw_mangle_POSTROUTING -m bpf --object-pinned %s",
-                              XT_BPF_EGRESS_PROG_PATH):"",
-        COMMIT_AND_CLOSE
-    };
+            "*raw",
+            // Prevents IPSec double counting (Tunnel mode and Transport mode,
+            // respectively)
+            "-A bw_raw_PREROUTING -i " IPSEC_IFACE_PREFIX "+ -j RETURN",
+            "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN",
+            useBpf ? StringPrintf("-A bw_raw_PREROUTING -m bpf --object-pinned %s",
+                                  XT_BPF_INGRESS_PROG_PATH)
+                   : "-A bw_raw_PREROUTING -m owner --socket-exists",
+            "COMMIT",
+
+            "*mangle",
+            // Prevents IPSec double counting (Tunnel mode and Transport mode,
+            // respectively)
+            "-A bw_mangle_POSTROUTING -o " IPSEC_IFACE_PREFIX "+ -j RETURN",
+            "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN",
+            useBpf ? "" : "-A bw_mangle_POSTROUTING -m owner --socket-exists",
+            StringPrintf("-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x%x",
+                         uidBillingMask),  // Clear the mark before sending this packet
+            useBpf ? StringPrintf("-A bw_mangle_POSTROUTING -m bpf --object-pinned %s",
+                                  XT_BPF_EGRESS_PROG_PATH)
+                   : "",
+            COMMIT_AND_CLOSE};
     return ipt_basic_accounting_commands;
 }
 
 
-std::vector<std::string> toStrVec(int num, char* strs[]) {
-    std::vector<std::string> tmp;
-    for (int i = 0; i < num; ++i) {
-        tmp.emplace_back(strs[i]);
-    }
-    return tmp;
+std::vector<std::string> toStrVec(int num, const char* const strs[]) {
+    return std::vector<std::string>(strs, strs + num);
 }
 
 }  // namespace
 
-bool BandwidthController::getBpfStatsStatus() {
-    return (access(XT_BPF_INGRESS_PROG_PATH, F_OK) != -1) &&
-           (access(XT_BPF_EGRESS_PROG_PATH, F_OK) != -1);
+void BandwidthController::setBpfEnabled(bool isEnabled) {
+    mBpfSupported = isEnabled;
 }
 
 BandwidthController::BandwidthController() {
@@ -288,25 +292,16 @@
     return 0;
 }
 
-int BandwidthController::enableBandwidthControl(bool force) {
-    char value[PROPERTY_VALUE_MAX];
-
-    if (!force) {
-            property_get("persist.bandwidth.enable", value, "1");
-            if (!strcmp(value, "0"))
-                    return 0;
-    }
-
+int BandwidthController::enableBandwidthControl() {
     /* Let's pretend we started from scratch ... */
     mSharedQuotaIfaces.clear();
     mQuotaIfaces.clear();
     mGlobalAlertBytes = 0;
-    mGlobalAlertTetherCount = 0;
     mSharedQuotaBytes = mSharedAlertBytes = 0;
 
     flushCleanTables(false);
 
-    std::string commands = Join(getBasicAccountingCommands(), '\n');
+    std::string commands = Join(getBasicAccountingCommands(mBpfSupported), '\n');
     return iptablesRestoreFunction(V4V6, commands, nullptr);
 }
 
@@ -337,29 +332,56 @@
     return ret;
 }
 
-int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::addNaughtyApps(int numUids, const char* const appUids[]) {
     return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
                                  IptJumpReject, IptOpInsert);
 }
 
-int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::removeNaughtyApps(int numUids, const char* const appUids[]) {
     return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
                                  IptJumpReject, IptOpDelete);
 }
 
-int BandwidthController::addNiceApps(int numUids, char *appUids[]) {
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::addNiceApps(int numUids, const char* const appUids[]) {
     return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
                                  IptJumpReturn, IptOpInsert);
 }
 
-int BandwidthController::removeNiceApps(int numUids, char *appUids[]) {
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::removeNiceApps(int numUids, const char* const appUids[]) {
     return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
                                  IptJumpReturn, IptOpDelete);
 }
 
+int BandwidthController::addNaughtyApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NAUGHTY_CHAIN, IptJumpReject, IptOpInsert);
+}
+
+int BandwidthController::removeNaughtyApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NAUGHTY_CHAIN, IptJumpReject, IptOpDelete);
+}
+
+int BandwidthController::addNiceApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NICE_CHAIN, IptJumpReturn, IptOpInsert);
+}
+
+int BandwidthController::removeNiceApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NICE_CHAIN, IptJumpReturn, IptOpDelete);
+}
+
 int BandwidthController::manipulateSpecialApps(const std::vector<std::string>& appStrUids,
                                                const std::string& chain, IptJumpOp jumpHandling,
                                                IptOp op) {
+    if (mBpfSupported) {
+        Status status = gCtls->trafficCtrl.updateUidOwnerMap(appStrUids, jumpHandling, op);
+        if (!isOk(status)) {
+            ALOGE("unable to update the Bandwidth Uid Map: %s", toString(status).c_str());
+      }
+      return status.code();
+    }
     std::string cmd = "*filter\n";
     for (const auto& appStrUid : appStrUids) {
         StringAppendF(&cmd, "%s %s -m owner --uid-owner %s%s\n", opToString(op), chain.c_str(),
@@ -480,13 +502,11 @@
 int BandwidthController::setInterfaceQuota(const std::string& iface, int64_t maxBytes) {
     const std::string& cost = iface;
 
-    if (!isIfaceName(iface))
-        return -1;
+    if (!isIfaceName(iface)) return -EINVAL;
 
     if (!maxBytes) {
-        /* Don't talk about -1, deprecate it. */
         ALOGE("Invalid bytes value. 1..max_int64.");
-        return -1;
+        return -ERANGE;
     }
     if (maxBytes == -1) {
         return removeInterfaceQuota(iface);
@@ -496,10 +516,10 @@
     auto it = mQuotaIfaces.find(iface);
 
     if (it != mQuotaIfaces.end()) {
-        if (updateQuota(cost, maxBytes) != 0) {
+        if (int res = updateQuota(cost, maxBytes)) {
             ALOGE("Failed update quota for %s", iface.c_str());
             removeInterfaceQuota(iface);
-            return -1;
+            return res;
         }
         it->second.quota = maxBytes;
         return 0;
@@ -521,11 +541,10 @@
                      chain.c_str(), maxBytes, cost.c_str()),
         "COMMIT\n",
     };
-
     if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) {
         ALOGE("Failed set quota rule");
         removeInterfaceQuota(iface);
-        return -1;
+        return -EREMOTEIO;
     }
 
     mQuotaIfaces[iface] = QuotaInfo{maxBytes, 0};
@@ -557,14 +576,13 @@
 }
 
 int BandwidthController::removeInterfaceQuota(const std::string& iface) {
-    if (!isIfaceName(iface))
-        return -1;
+    if (!isIfaceName(iface)) return -EINVAL;
 
     auto it = mQuotaIfaces.find(iface);
 
     if (it == mQuotaIfaces.end()) {
         ALOGE("No such iface %s to delete", iface.c_str());
-        return -1;
+        return -ENODEV;
     }
 
     const std::string chain = "bw_costly_" + iface;
@@ -585,7 +603,7 @@
         mQuotaIfaces.erase(it);
     }
 
-    return res;
+    return res ? -EREMOTEIO : 0;
 }
 
 int BandwidthController::updateQuota(const std::string& quotaName, int64_t bytes) {
@@ -594,15 +612,17 @@
 
     if (!isIfaceName(quotaName)) {
         ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName.c_str());
-        return -1;
+        return -EINVAL;
     }
 
     StatusOr<UniqueFile> file = sys.fopen(fname, "we");
     if (!isOk(file)) {
+        int res = errno;
         ALOGE("Updating quota %s failed (%s)", quotaName.c_str(), toString(file).c_str());
-        return -1;
+        return -res;
     }
-    sys.fprintf(file.value().get(), "%" PRId64 "\n", bytes);
+    // TODO: should we propagate this error?
+    sys.fprintf(file.value().get(), "%" PRId64 "\n", bytes).ignoreError();
     return 0;
 }
 
@@ -613,20 +633,13 @@
 
     // TODO: consider using an alternate template for the delete that does not include the --quota
     // value. This code works because the --quota value is ignored by deletes
-    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_INPUT", bytes,
-                  alertName.c_str());
-    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_OUTPUT", bytes,
-                  alertName.c_str());
-    StringAppendF(&alertQuotaCmd, "COMMIT\n");
 
-    return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr);
-}
-
-int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const std::string& alertName,
-                                                int64_t bytes) {
-    const char *opFlag = opToString(op);
-    std::string alertQuotaCmd = "*filter\n";
-    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_FORWARD", bytes,
+    /*
+     * Add alert rule in bw_global_alert chain, 3 chains might reference bw_global_alert.
+     * bw_INPUT, bw_OUTPUT (added by BandwidthController in enableBandwidthControl)
+     * bw_FORWARD (added by TetherController in setTetherGlobalAlertRule if nat enable/disable)
+     */
+    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, LOCAL_GLOBAL_ALERT, bytes,
                   alertName.c_str());
     StringAppendF(&alertQuotaCmd, "COMMIT\n");
 
@@ -635,84 +648,37 @@
 
 int BandwidthController::setGlobalAlert(int64_t bytes) {
     const char *alertName = ALERT_GLOBAL_NAME;
-    int res = 0;
 
     if (!bytes) {
         ALOGE("Invalid bytes value. 1..max_int64.");
-        return -1;
+        return -ERANGE;
     }
+
+    int res = 0;
     if (mGlobalAlertBytes) {
         res = updateQuota(alertName, bytes);
     } else {
         res = runIptablesAlertCmd(IptOpInsert, alertName, bytes);
-        if (mGlobalAlertTetherCount) {
-            ALOGV("setGlobalAlert for %d tether", mGlobalAlertTetherCount);
-            res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes);
+        if (res) {
+            res = -EREMOTEIO;
         }
     }
     mGlobalAlertBytes = bytes;
     return res;
 }
 
-int BandwidthController::setGlobalAlertInForwardChain() {
-    const char *alertName = ALERT_GLOBAL_NAME;
-    int res = 0;
-
-    mGlobalAlertTetherCount++;
-    ALOGV("setGlobalAlertInForwardChain(): %d tether", mGlobalAlertTetherCount);
-
-    /*
-     * If there is no globalAlert active we are done.
-     * If there is an active globalAlert but this is not the 1st
-     * tether, we are also done.
-     */
-    if (!mGlobalAlertBytes || mGlobalAlertTetherCount != 1) {
-        return 0;
-    }
-
-    /* We only add the rule if this was the 1st tether added. */
-    res = runIptablesAlertFwdCmd(IptOpInsert, alertName, mGlobalAlertBytes);
-    return res;
-}
-
 int BandwidthController::removeGlobalAlert() {
 
     const char *alertName = ALERT_GLOBAL_NAME;
-    int res = 0;
 
     if (!mGlobalAlertBytes) {
         ALOGE("No prior alert set");
         return -1;
     }
-    res = runIptablesAlertCmd(IptOpDelete, alertName, mGlobalAlertBytes);
-    if (mGlobalAlertTetherCount) {
-        res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, mGlobalAlertBytes);
-    }
-    mGlobalAlertBytes = 0;
-    return res;
-}
 
-int BandwidthController::removeGlobalAlertInForwardChain() {
     int res = 0;
-    const char *alertName = ALERT_GLOBAL_NAME;
-
-    if (!mGlobalAlertTetherCount) {
-        ALOGE("No prior alert set");
-        return -1;
-    }
-
-    mGlobalAlertTetherCount--;
-    /*
-     * If there is no globalAlert active we are done.
-     * If there is an active globalAlert but there are more
-     * tethers, we are also done.
-     */
-    if (!mGlobalAlertBytes || mGlobalAlertTetherCount >= 1) {
-        return 0;
-    }
-
-    /* We only detete the rule if this was the last tether removed. */
-    res = runIptablesAlertFwdCmd(IptOpDelete, alertName, mGlobalAlertBytes);
+    res = runIptablesAlertCmd(IptOpDelete, alertName, mGlobalAlertBytes);
+    mGlobalAlertBytes = 0;
     return res;
 }
 
@@ -735,18 +701,18 @@
 int BandwidthController::setInterfaceAlert(const std::string& iface, int64_t bytes) {
     if (!isIfaceName(iface)) {
         ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface.c_str());
-        return -1;
+        return -EINVAL;
     }
 
     if (!bytes) {
         ALOGE("Invalid bytes value. 1..max_int64.");
-        return -1;
+        return -ERANGE;
     }
     auto it = mQuotaIfaces.find(iface);
 
     if (it == mQuotaIfaces.end()) {
         ALOGE("Need to have a prior interface quota set to set an alert");
-        return -1;
+        return -ENOENT;
     }
 
     return setCostlyAlert(iface, bytes, &it->second.alert);
@@ -755,14 +721,14 @@
 int BandwidthController::removeInterfaceAlert(const std::string& iface) {
     if (!isIfaceName(iface)) {
         ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface.c_str());
-        return -1;
+        return -EINVAL;
     }
 
     auto it = mQuotaIfaces.find(iface);
 
     if (it == mQuotaIfaces.end()) {
         ALOGE("No prior alert set for interface %s", iface.c_str());
-        return -1;
+        return -ENOENT;
     }
 
     return removeCostlyAlert(iface, &it->second.alert);
@@ -774,12 +740,12 @@
 
     if (!isIfaceName(costName)) {
         ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName.c_str());
-        return -1;
+        return -EINVAL;
     }
 
     if (!bytes) {
         ALOGE("Invalid bytes value. 1..max_int64.");
-        return -1;
+        return -ERANGE;
     }
 
     std::string alertName = costName + "Alert";
@@ -795,6 +761,7 @@
         res = iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr);
         if (res) {
             ALOGE("Failed to set costly alert for %s", costName.c_str());
+            res = -EREMOTEIO;
         }
     }
     if (res == 0) {
@@ -806,12 +773,12 @@
 int BandwidthController::removeCostlyAlert(const std::string& costName, int64_t* alertBytes) {
     if (!isIfaceName(costName)) {
         ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName.c_str());
-        return -1;
+        return -EINVAL;
     }
 
     if (!*alertBytes) {
         ALOGE("No prior alert set for %s alert", costName.c_str());
-        return -1;
+        return -ENOENT;
     }
 
     std::string alertName = costName + "Alert";
@@ -823,7 +790,7 @@
     };
     if (iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr) != 0) {
         ALOGE("Failed to remove costly alert %s", costName.c_str());
-        return -1;
+        return -EREMOTEIO;
     }
 
     *alertBytes = 0;
diff --git a/server/BandwidthController.h b/server/BandwidthController.h
index efacdce..b8691dc 100644
--- a/server/BandwidthController.h
+++ b/server/BandwidthController.h
@@ -21,21 +21,20 @@
 #include <string>
 #include <utility>
 #include <vector>
-
-#include <utils/RWLock.h>
+#include <mutex>
 
 #include "NetdConstants.h"
 
 class BandwidthController {
 public:
-    android::RWLock lock;
+    std::mutex lock;
 
     BandwidthController();
 
     int setupIptablesHooks();
-    static bool getBpfStatsStatus();
+    void setBpfEnabled(bool isEnabled);
 
-    int enableBandwidthControl(bool force);
+    int enableBandwidthControl();
     int disableBandwidthControl();
     int enableDataSaver(bool enable);
 
@@ -47,10 +46,16 @@
     int getInterfaceQuota(const std::string& iface, int64_t* bytes);
     int removeInterfaceQuota(const std::string& iface);
 
-    int addNaughtyApps(int numUids, char *appUids[]);
-    int removeNaughtyApps(int numUids, char *appUids[]);
-    int addNiceApps(int numUids, char *appUids[]);
-    int removeNiceApps(int numUids, char *appUids[]);
+    // TODO: Remove after removing these commands in CommandListener
+    int addNaughtyApps(int numUids, const char* const appUids[]);
+    int removeNaughtyApps(int numUids, const char* const appUids[]);
+    int addNiceApps(int numUids, const char* const appUids[]);
+    int removeNiceApps(int numUids, const char* const appUids[]);
+
+    int addNaughtyApps(const std::vector<std::string>& appStrUid);
+    int removeNaughtyApps(const std::vector<std::string>& appStrUid);
+    int addNiceApps(const std::vector<std::string>& appStrUid);
+    int removeNiceApps(const std::vector<std::string>& appStrUid);
 
     int setGlobalAlert(int64_t bytes);
     int removeGlobalAlert();
@@ -68,6 +73,10 @@
     static const char LOCAL_OUTPUT[];
     static const char LOCAL_RAW_PREROUTING[];
     static const char LOCAL_MANGLE_POSTROUTING[];
+    static const char LOCAL_GLOBAL_ALERT[];
+
+    enum IptJumpOp { IptJumpReject, IptJumpReturn, IptJumpNoAdd };
+    enum IptOp { IptOpInsert, IptOpDelete };
 
   private:
     struct QuotaInfo {
@@ -77,8 +86,6 @@
 
     enum IptIpVer { IptIpV4, IptIpV6 };
     enum IptFullOp { IptFullOpInsert, IptFullOpDelete, IptFullOpAppend };
-    enum IptJumpOp { IptJumpReject, IptJumpReturn, IptJumpNoAdd };
-    enum IptOp { IptOpInsert, IptOpDelete };
     enum QuotaType { QuotaUnique, QuotaShared };
     enum RunCmdErrHandling { RunCmdFailureBad, RunCmdFailureOk };
 #if LOG_NDEBUG
@@ -125,18 +132,11 @@
     static const char *opToString(IptOp op);
     static const char *jumpToString(IptJumpOp jumpHandling);
 
+    bool mBpfSupported = false;
+
     int64_t mSharedQuotaBytes = 0;
     int64_t mSharedAlertBytes = 0;
     int64_t mGlobalAlertBytes = 0;
-    /*
-     * This tracks the number of tethers setup.
-     * The FORWARD chain is updated in the following cases:
-     *  - The 1st time a globalAlert is setup and there are tethers setup.
-     *  - Anytime a globalAlert is removed and there are tethers setup.
-     *  - The 1st tether is setup and there is a globalAlert active.
-     *  - The last tether is removed and there is a globalAlert active.
-     */
-    int mGlobalAlertTetherCount = 0;
 
     std::map<std::string, QuotaInfo> mQuotaIfaces;
     std::set<std::string> mSharedQuotaIfaces;
diff --git a/server/BandwidthControllerTest.cpp b/server/BandwidthControllerTest.cpp
index 938cea0..9337ac5 100644
--- a/server/BandwidthControllerTest.cpp
+++ b/server/BandwidthControllerTest.cpp
@@ -35,22 +35,85 @@
 #include "Fwmark.h"
 #include "IptablesBaseTest.h"
 #include "bpf/BpfUtils.h"
+#include "netdbpf/bpf_shared.h"
 #include "tun_interface.h"
 
+using ::testing::_;
 using ::testing::ByMove;
 using ::testing::Invoke;
 using ::testing::Return;
 using ::testing::StrictMock;
-using ::testing::Test;
-using ::testing::_;
 
 using android::base::Join;
 using android::base::StringPrintf;
-using android::bpf::XT_BPF_EGRESS_PROG_PATH;
-using android::bpf::XT_BPF_INGRESS_PROG_PATH;
 using android::net::TunInterface;
-using android::netdutils::status::ok;
 using android::netdutils::UniqueFile;
+using android::netdutils::status::ok;
+
+const std::string ACCOUNT_RULES_WITHOUT_BPF =
+        "*filter\n"
+        "-A bw_INPUT -j bw_global_alert\n"
+        "-A bw_INPUT -p esp -j RETURN\n"
+        "-A bw_INPUT -m mark --mark 0x100000/0x100000 -j RETURN\n"
+        "-A bw_INPUT -m owner --socket-exists\n"
+        "-A bw_INPUT -j MARK --or-mark 0x100000\n"
+        "-A bw_OUTPUT -j bw_global_alert\n"
+        "-A bw_OUTPUT -o ipsec+ -j RETURN\n"
+        "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN\n"
+        "-A bw_OUTPUT -m owner --socket-exists\n"
+        "-A bw_costly_shared --jump bw_penalty_box\n"
+        "\n"
+        "-A bw_penalty_box --jump bw_happy_box\n"
+        "-A bw_happy_box --jump bw_data_saver\n"
+        "-A bw_data_saver -j RETURN\n"
+        "-I bw_happy_box -m owner --uid-owner 0-9999 --jump RETURN\n"
+        "COMMIT\n"
+        "*raw\n"
+        "-A bw_raw_PREROUTING -i ipsec+ -j RETURN\n"
+        "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n"
+        "-A bw_raw_PREROUTING -m owner --socket-exists\n"
+        "COMMIT\n"
+        "*mangle\n"
+        "-A bw_mangle_POSTROUTING -o ipsec+ -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -m owner --socket-exists\n"
+        "-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x100000\n"
+        "\n"
+        "COMMIT\n";
+
+const std::string ACCOUNT_RULES_WITH_BPF =
+        "*filter\n"
+        "-A bw_INPUT -j bw_global_alert\n"
+        "-A bw_INPUT -p esp -j RETURN\n"
+        "-A bw_INPUT -m mark --mark 0x100000/0x100000 -j RETURN\n"
+        "\n"
+        "-A bw_INPUT -j MARK --or-mark 0x100000\n"
+        "-A bw_OUTPUT -j bw_global_alert\n"
+        "-A bw_OUTPUT -o ipsec+ -j RETURN\n"
+        "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN\n"
+        "\n"
+        "-A bw_costly_shared --jump bw_penalty_box\n" +
+        StringPrintf("-I bw_penalty_box -m bpf --object-pinned %s -j REJECT\n",
+                     XT_BPF_BLACKLIST_PROG_PATH) +
+        "-A bw_penalty_box --jump bw_happy_box\n"
+        "-A bw_happy_box --jump bw_data_saver\n"
+        "-A bw_data_saver -j RETURN\n" +
+        StringPrintf("-I bw_happy_box -m bpf --object-pinned %s -j RETURN\n",
+                     XT_BPF_WHITELIST_PROG_PATH) +
+        "COMMIT\n"
+        "*raw\n"
+        "-A bw_raw_PREROUTING -i ipsec+ -j RETURN\n"
+        "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n" +
+        StringPrintf("-A bw_raw_PREROUTING -m bpf --object-pinned %s\n", XT_BPF_INGRESS_PROG_PATH) +
+        "COMMIT\n"
+        "*mangle\n"
+        "-A bw_mangle_POSTROUTING -o ipsec+ -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
+        "\n"
+        "-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x100000\n" +
+        StringPrintf("-A bw_mangle_POSTROUTING -m bpf --object-pinned %s\n",
+                     XT_BPF_EGRESS_PROG_PATH) +
+        "COMMIT\n";
 
 class BandwidthControllerTest : public IptablesBaseTest {
 protected:
@@ -68,28 +131,30 @@
         mTun.destroy();
     }
 
-    void expectSetupCommands(const std::string& expectedClean, std::string expectedAccounting) {
+    void expectSetupCommands(const std::string& expectedClean,
+                             const std::string& expectedAccounting) {
         std::string expectedList =
             "*filter\n"
             "-S\n"
             "COMMIT\n";
 
         std::string expectedFlush =
-            "*filter\n"
-            ":bw_INPUT -\n"
-            ":bw_OUTPUT -\n"
-            ":bw_FORWARD -\n"
-            ":bw_happy_box -\n"
-            ":bw_penalty_box -\n"
-            ":bw_data_saver -\n"
-            ":bw_costly_shared -\n"
-            "COMMIT\n"
-            "*raw\n"
-            ":bw_raw_PREROUTING -\n"
-            "COMMIT\n"
-            "*mangle\n"
-            ":bw_mangle_POSTROUTING -\n"
-            "COMMIT\n";
+                "*filter\n"
+                ":bw_INPUT -\n"
+                ":bw_OUTPUT -\n"
+                ":bw_FORWARD -\n"
+                ":bw_happy_box -\n"
+                ":bw_penalty_box -\n"
+                ":bw_data_saver -\n"
+                ":bw_costly_shared -\n"
+                ":bw_global_alert -\n"
+                "COMMIT\n"
+                "*raw\n"
+                ":bw_raw_PREROUTING -\n"
+                "COMMIT\n"
+                "*mangle\n"
+                ":bw_mangle_POSTROUTING -\n"
+                "COMMIT\n";
 
         ExpectedIptablesCommands expected = {{ V4, expectedList }};
         if (expectedClean.size()) {
@@ -105,21 +170,15 @@
 
     using IptOp = BandwidthController::IptOp;
 
-    int runIptablesAlertCmd(IptOp a, const char *b, int64_t c) {
+    int runIptablesAlertCmd(IptOp a, const char* b, int64_t c) {
         return mBw.runIptablesAlertCmd(a, b, c);
     }
 
-    int runIptablesAlertFwdCmd(IptOp a, const char *b, int64_t c) {
-        return mBw.runIptablesAlertFwdCmd(a, b, c);
-    }
-
-    int setCostlyAlert(const std::string a, int64_t b, int64_t *c) {
+    int setCostlyAlert(const std::string& a, int64_t b, int64_t* c) {
         return mBw.setCostlyAlert(a, b, c);
     }
 
-    int removeCostlyAlert(const std::string a, int64_t *b) {
-        return mBw.removeCostlyAlert(a, b);
-    }
+    int removeCostlyAlert(const std::string& a, int64_t* b) { return mBw.removeCostlyAlert(a, b); }
 
     void expectUpdateQuota(uint64_t quota) {
         uintptr_t dummy;
@@ -134,6 +193,23 @@
         EXPECT_CALL(mSyscalls, fclose(dummyFile)).WillOnce(Return(ok));
     }
 
+    void checkBandwithControl(bool useBpf) {
+        // Pretend no bw_costly_shared_<iface> rules already exist...
+        addIptablesRestoreOutput(
+                "-P OUTPUT ACCEPT\n"
+                "-N bw_costly_shared\n"
+                "-N unrelated\n");
+
+        // ... so none are flushed or deleted.
+        std::string expectedClean = "";
+
+        std::string expectedAccounting =
+                useBpf ? ACCOUNT_RULES_WITH_BPF : ACCOUNT_RULES_WITHOUT_BPF;
+        mBw.setBpfEnabled(useBpf);
+        mBw.enableBandwidthControl();
+        expectSetupCommands(expectedClean, expectedAccounting);
+    }
+
     StrictMock<android::netdutils::ScopedMockSyscalls> mSyscalls;
 };
 
@@ -169,60 +245,12 @@
     EXPECT_TRUE(isPowerOfTwo);
 }
 
-TEST_F(BandwidthControllerTest, TestEnableBandwidthControl) {
-    // Pretend no bw_costly_shared_<iface> rules already exist...
-    addIptablesRestoreOutput(
-        "-P OUTPUT ACCEPT\n"
-        "-N bw_costly_shared\n"
-        "-N unrelated\n");
+TEST_F(BandwidthControllerTest, TestEnableBandwidthControlWithBpf) {
+    checkBandwithControl(true);
+}
 
-    // ... so none are flushed or deleted.
-    std::string expectedClean = "";
-
-    uint32_t uidBillingMask = Fwmark::getUidBillingMask();
-    bool useBpf = BandwidthController::getBpfStatsStatus();
-    std::string expectedAccounting =
-        "*filter\n"
-        "-A bw_INPUT -p esp -j RETURN\n" +
-        StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN\n",
-                    uidBillingMask, uidBillingMask) +
-        "-A bw_INPUT -m owner --socket-exists\n" +
-        StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x\n", uidBillingMask) +
-        "-A bw_OUTPUT -o " IPSEC_IFACE_PREFIX "+ -j RETURN\n"
-        "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN\n"
-        "-A bw_OUTPUT -m owner --socket-exists\n"
-        "-A bw_costly_shared --jump bw_penalty_box\n"
-        "-A bw_penalty_box --jump bw_happy_box\n"
-        "-A bw_happy_box --jump bw_data_saver\n"
-        "-A bw_data_saver -j RETURN\n"
-        "-I bw_happy_box -m owner --uid-owner 0-9999 --jump RETURN\n"
-        "COMMIT\n"
-        "*raw\n"
-        "-A bw_raw_PREROUTING -i " IPSEC_IFACE_PREFIX "+ -j RETURN\n"
-        "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n"
-        "-A bw_raw_PREROUTING -m owner --socket-exists\n";
-    if (useBpf) {
-        expectedAccounting += StringPrintf("-A bw_raw_PREROUTING -m bpf --object-pinned %s\n",
-                                           XT_BPF_INGRESS_PROG_PATH);
-    } else {
-        expectedAccounting += "\n";
-    }
-    expectedAccounting +=
-        "COMMIT\n"
-        "*mangle\n"
-        "-A bw_mangle_POSTROUTING -o " IPSEC_IFACE_PREFIX "+ -j RETURN\n"
-        "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
-        "-A bw_mangle_POSTROUTING -m owner --socket-exists\n" +
-        StringPrintf("-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x%x\n", uidBillingMask);
-    if (useBpf) {
-        expectedAccounting += StringPrintf("-A bw_mangle_POSTROUTING -m bpf --object-pinned %s\n",
-                                           XT_BPF_EGRESS_PROG_PATH);
-    } else {
-        expectedAccounting += "\n";
-    }
-    expectedAccounting += "COMMIT\n";
-    mBw.enableBandwidthControl(false);
-    expectSetupCommands(expectedClean, expectedAccounting);
+TEST_F(BandwidthControllerTest, TestEnableBandwidthControlWithoutBpf) {
+    checkBandwithControl(false);
 }
 
 TEST_F(BandwidthControllerTest, TestDisableBandwidthControl) {
@@ -440,42 +468,20 @@
 
 TEST_F(BandwidthControllerTest, IptablesAlertCmd) {
     std::vector<std::string> expected = {
-        "*filter\n"
-        "-I bw_INPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
-        "-I bw_OUTPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
-        "COMMIT\n"
-    };
+            "*filter\n"
+            "-I bw_global_alert -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+            "COMMIT\n"};
     EXPECT_EQ(0, runIptablesAlertCmd(IptOp::IptOpInsert, "MyWonderfulAlert", 123456));
     expectIptablesRestoreCommands(expected);
 
     expected = {
-        "*filter\n"
-        "-D bw_INPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
-        "-D bw_OUTPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
-        "COMMIT\n"
-    };
+            "*filter\n"
+            "-D bw_global_alert -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+            "COMMIT\n"};
     EXPECT_EQ(0, runIptablesAlertCmd(IptOp::IptOpDelete, "MyWonderfulAlert", 123456));
     expectIptablesRestoreCommands(expected);
 }
 
-TEST_F(BandwidthControllerTest, IptablesAlertFwdCmd) {
-    std::vector<std::string> expected = {
-        "*filter\n"
-        "-I bw_FORWARD -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
-        "COMMIT\n"
-    };
-    EXPECT_EQ(0, runIptablesAlertFwdCmd(IptOp::IptOpInsert, "MyWonderfulAlert", 123456));
-    expectIptablesRestoreCommands(expected);
-
-    expected = {
-        "*filter\n"
-        "-D bw_FORWARD -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
-        "COMMIT\n"
-    };
-    EXPECT_EQ(0, runIptablesAlertFwdCmd(IptOp::IptOpDelete, "MyWonderfulAlert", 123456));
-    expectIptablesRestoreCommands(expected);
-}
-
 TEST_F(BandwidthControllerTest, CostlyAlert) {
     const int64_t kQuota = 123456;
     int64_t alertBytes = 0;
diff --git a/server/ClatUtils.cpp b/server/ClatUtils.cpp
new file mode 100644
index 0000000..ed7287a
--- /dev/null
+++ b/server/ClatUtils.cpp
@@ -0,0 +1,357 @@
+/*
+ * 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 "ClatUtils.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/if.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <linux/rtnetlink.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define LOG_TAG "ClatUtils"
+#include <log/log.h>
+
+#include "NetlinkCommands.h"
+#include "android-base/unique_fd.h"
+#include "bpf/BpfUtils.h"
+#include "netdbpf/bpf_shared.h"
+
+namespace android {
+namespace net {
+
+int hardwareAddressType(const std::string& interface) {
+    base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+
+    if (ufd < 0) {
+        const int err = errno;
+        ALOGE("socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)");
+        return -err;
+    };
+
+    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.c_str(), sizeof(ifr.ifr_name));
+
+    if (ioctl(ufd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) return -errno;
+
+    return ifr.ifr_hwaddr.sa_family;
+}
+
+int getClatIngressMapFd(void) {
+    const int fd = bpf::bpfFdGet(CLAT_INGRESS_MAP_PATH, 0);
+    return (fd == -1) ? -errno : fd;
+}
+
+int getClatIngressProgFd(bool with_ethernet_header) {
+    const int fd = bpf::bpfFdGet(
+            with_ethernet_header ? CLAT_INGRESS_PROG_ETHER_PATH : CLAT_INGRESS_PROG_RAWIP_PATH, 0);
+    return (fd == -1) ? -errno : fd;
+}
+
+// TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
+int openNetlinkSocket(void) {
+    base::unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+    if (fd == -1) {
+        const int err = errno;
+        ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)");
+        return -err;
+    }
+
+    int rv;
+
+    const int on = 1;
+    rv = setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
+    if (rv) ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
+
+    // this is needed to get sane strace netlink parsing, it allocates the pid
+    rv = bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+    if (rv) {
+        const int err = errno;
+        ALOGE("bind(fd, {AF_NETLINK, 0, 0})");
+        return -err;
+    }
+
+    // we do not want to receive messages from anyone besides the kernel
+    rv = connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+    if (rv) {
+        const int err = errno;
+        ALOGE("connect(fd, {AF_NETLINK, 0, 0})");
+        return -err;
+    }
+
+    return fd.release();
+}
+
+// TODO: merge with //system/netd/server/SockDiag.cpp:checkError(fd)
+int processNetlinkResponse(int fd) {
+    struct {
+        nlmsghdr h;
+        nlmsgerr e;
+        char buf[256];
+    } resp = {};
+
+    const int rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+    if (rv == -1) {
+        const int err = errno;
+        ALOGE("recv() failed");
+        return -err;
+    }
+
+    if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+        ALOGE("recv() returned short packet: %d", rv);
+        return -EMSGSIZE;
+    }
+
+    if (resp.h.nlmsg_len != (unsigned)rv) {
+        ALOGE("recv() returned invalid header length: %d != %d", resp.h.nlmsg_len, rv);
+        return -EBADMSG;
+    }
+
+    if (resp.h.nlmsg_type != NLMSG_ERROR) {
+        ALOGE("recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
+        return -EBADMSG;
+    }
+
+    return resp.e.error;  // returns 0 on success
+}
+
+// ADD:     nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
+// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
+// DEL:     nlMsgType=RTM_DELQDISC nlMsgFlags=0
+int doTcQdiscClsact(int fd, int ifIndex, __u16 nlMsgType, __u16 nlMsgFlags) {
+    // This is the name of the qdisc we are attaching.
+    // Some hoop jumping to make this compile time constant with known size,
+    // so that the structure declaration is well defined at compile time.
+#define CLSACT "clsact"
+    static const char clsact[] = CLSACT;
+    // sizeof() includes the terminating NULL
+#define ASCIIZ_LEN_CLSACT sizeof(clsact)
+
+    const struct {
+        nlmsghdr n;
+        tcmsg t;
+        struct {
+            nlattr attr;
+            char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
+        } kind;
+    } req = {
+            .n =
+                    {
+                            .nlmsg_len = sizeof(req),
+                            .nlmsg_type = nlMsgType,
+                            .nlmsg_flags = static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
+                    },
+            .t =
+                    {
+                            .tcm_family = AF_UNSPEC,
+                            .tcm_ifindex = ifIndex,
+                            .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
+                            .tcm_parent = TC_H_CLSACT,
+                    },
+            .kind =
+                    {
+                            .attr =
+                                    {
+                                            .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
+                                            .nla_type = TCA_KIND,
+                                    },
+                            .str = CLSACT,
+                    },
+    };
+#undef ASCIIZ_LEN_CLSACT
+#undef CLSACT
+
+    const int rv = send(fd, &req, sizeof(req), 0);
+    if (rv == -1) return -errno;
+    if (rv != sizeof(req)) return -EMSGSIZE;
+
+    return processNetlinkResponse(fd);
+}
+
+int tcQdiscAddDevClsact(int fd, int ifIndex) {
+    return doTcQdiscClsact(fd, ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+int tcQdiscReplaceDevClsact(int fd, int ifIndex) {
+    return doTcQdiscClsact(fd, ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+int tcQdiscDelDevClsact(int fd, int ifIndex) {
+    return doTcQdiscClsact(fd, ifIndex, RTM_DELQDISC, 0);
+}
+
+// tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+int tcFilterAddDevBpf(int fd, int ifIndex, int bpfFd, bool ethernet) {
+    // The priority doesn't matter until we actually start attaching multiple
+    // things to the same interface's ingress point.
+    const int prio = 1;
+
+    // This is the name of the filter we're attaching (ie. this is the 'bpf'
+    // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
+    //
+    // We go through some hoops in order to make this compile time constants
+    // so that we can define the struct further down the function with the
+    // field for this sized correctly already during the build.
+#define BPF "bpf"
+    const char bpf[] = BPF;
+    // sizeof() includes the terminating NULL
+#define ASCIIZ_LEN_BPF sizeof(bpf)
+
+    // This is to replicate program name suffix used by 'tc' Linux cli
+    // when it attaches programs.
+#define FSOBJ_SUFFIX ":[*fsobj]"
+
+    // This macro expands (from header files) to:
+    //   prog_clatd_schedcls_ingress_clat_rawip:[*fsobj]
+    // and is the name of the pinned ebpf program for ARPHRD_RAWIP interfaces.
+    // (also compatible with anything that has 0 size L2 header)
+#define NAME_RAWIP CLAT_INGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX
+    const char name_rawip[] = NAME_RAWIP;
+
+    // This macro expands (from header files) to:
+    //   prog_clatd_schedcls_ingress_clat_ether:[*fsobj]
+    // and is the name of the pinned ebpf program for ARPHRD_ETHER interfaces.
+    // (also compatible with anything that has standard ethernet header)
+#define NAME_ETHER CLAT_INGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX
+    const char name_ether[] = NAME_ETHER;
+
+    // The actual name we'll use is determined at run time via 'ethernet'
+    // boolean.  We need to compile time allocate enough space in the struct
+    // hence this macro magic to make sure we have enough space for either
+    // possibility.  In practice both are actually the same size.
+#define ASCIIZ_MAXLEN_NAME \
+    ((sizeof(name_rawip) > sizeof(name_ether)) ? sizeof(name_rawip) : sizeof(name_ether))
+
+    // This is not a compile time constant and is used in strcpy below
+#define NAME (ethernet ? NAME_ETHER : NAME_RAWIP)
+
+    struct {
+        nlmsghdr n;
+        tcmsg t;
+        struct {
+            nlattr attr;
+            char str[NLMSG_ALIGN(ASCIIZ_LEN_BPF)];
+        } kind;
+        struct {
+            nlattr attr;
+            struct {
+                nlattr attr;
+                __u32 u32;
+            } fd;
+            struct {
+                nlattr attr;
+                char str[NLMSG_ALIGN(ASCIIZ_MAXLEN_NAME)];
+            } 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, TC_H_MIN_INGRESS),
+                            .tcm_info = (prio << 16) | htons(ETH_P_IPV6),
+                    },
+            .kind =
+                    {
+                            .attr =
+                                    {
+                                            .nla_len = sizeof(req.kind),
+                                            .nla_type = TCA_KIND,
+                                    },
+                            .str = BPF,
+                    },
+            .options =
+                    {
+                            .attr =
+                                    {
+                                            .nla_len = sizeof(req.options),
+                                            .nla_type = 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 strcpy below
+                                            .str = "placeholder",
+                                    },
+                            .flags =
+                                    {
+                                            .attr =
+                                                    {
+                                                            .nla_len = sizeof(req.options.flags),
+                                                            .nla_type = TCA_BPF_FLAGS,
+                                                    },
+                                            .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+                                    },
+                    },
+    };
+
+    strncpy(req.options.name.str, NAME, sizeof(req.options.name.str));
+
+#undef NAME
+#undef ASCIIZ_MAXLEN_NAME
+#undef NAME_ETHER
+#undef NAME_RAWIP
+#undef NAME
+#undef ASCIIZ_LEN_BPF
+#undef BPF
+
+    const int rv = send(fd, &req, sizeof(req), 0);
+    if (rv == -1) return -errno;
+    if (rv != sizeof(req)) return -EMSGSIZE;
+
+    return processNetlinkResponse(fd);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/ClatUtils.h b/server/ClatUtils.h
new file mode 100644
index 0000000..9f2a28e
--- /dev/null
+++ b/server/ClatUtils.h
@@ -0,0 +1,44 @@
+/*
+ * 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 _CLAT_UTILS_H
+#define _CLAT_UTILS_H
+
+#include <string>
+
+namespace android {
+namespace net {
+
+int hardwareAddressType(const std::string& interface);
+
+int getClatIngressMapFd(void);
+
+int getClatIngressProgFd(bool with_ethernet_header);
+
+int openNetlinkSocket(void);
+
+int processNetlinkResponse(int fd);
+
+int tcQdiscAddDevClsact(int fd, int ifIndex);
+int tcQdiscReplaceDevClsact(int fd, int ifIndex);
+int tcQdiscDelDevClsact(int fd, int ifIndex);
+
+int tcFilterAddDevBpf(int fd, int ifIndex, int bpfFd, bool ethernet);
+
+}  // namespace net
+}  // namespace android
+
+#endif
diff --git a/server/ClatUtilsTest.cpp b/server/ClatUtilsTest.cpp
new file mode 100644
index 0000000..2a869ba
--- /dev/null
+++ b/server/ClatUtilsTest.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright 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.
+ *
+ * ClatUtilsTest.cpp - unit tests for ClatUtils.cpp
+ */
+
+#include <gtest/gtest.h>
+
+#include "ClatUtils.h"
+
+#include <linux/if_arp.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "bpf/BpfUtils.h"
+#include "netdbpf/bpf_shared.h"
+
+namespace android {
+namespace net {
+
+class ClatUtilsTest : public ::testing::Test {
+  public:
+    void SetUp() {}
+};
+
+TEST_F(ClatUtilsTest, HardwareAddressTypeOfNonExistingIf) {
+    ASSERT_EQ(-ENODEV, hardwareAddressType("not_existing_if"));
+}
+
+TEST_F(ClatUtilsTest, HardwareAddressTypeOfLoopback) {
+    ASSERT_EQ(ARPHRD_LOOPBACK, hardwareAddressType("lo"));
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+TEST_F(ClatUtilsTest, HardwareAddressTypeOfWireless) {
+    int type = hardwareAddressType("wlan0");
+    if (type == -ENODEV) return;
+
+    ASSERT_EQ(ARPHRD_ETHER, type);
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+TEST_F(ClatUtilsTest, HardwareAddressTypeOfCellular) {
+    int type = hardwareAddressType("rmnet_data0");
+    if (type == -ENODEV) return;
+
+    ASSERT_NE(ARPHRD_ETHER, type);
+
+    // ARPHRD_RAWIP is 530 on some pre-4.14 Qualcomm devices.
+    if (type == 530) return;
+
+    ASSERT_EQ(ARPHRD_RAWIP, type);
+}
+
+TEST_F(ClatUtilsTest, GetClatMapFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatIngressMapFd();
+    ASSERT_LE(3, fd);  // 0,1,2 - stdin/out/err, thus 3 <= fd
+    close(fd);
+}
+
+TEST_F(ClatUtilsTest, GetClatRawIpProgFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatIngressProgFd(false);
+    ASSERT_LE(3, fd);
+    close(fd);
+}
+
+TEST_F(ClatUtilsTest, GetClatEtherProgFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatIngressProgFd(true);
+    ASSERT_LE(3, fd);
+    close(fd);
+}
+
+TEST_F(ClatUtilsTest, TryOpeningNetlinkSocket) {
+    int fd = openNetlinkSocket();
+    ASSERT_LE(3, fd);
+    close(fd);
+}
+
+// The SKIP_IF_BPF_NOT_SUPPORTED macro is effectively a check for 4.9+ kernel
+// combined with a launched on P device.  Ie. it's a test for 4.9-P or better.
+
+// NET_SCH_INGRESS is only enabled starting with 4.9-Q and as such we need
+// a separate way to test for this...
+int doKernelSupportsNetSchIngress(void) {
+    // NOLINTNEXTLINE(cert-env33-c)
+    return system("zcat /proc/config.gz | egrep -q '^CONFIG_NET_SCH_INGRESS=[my]$'");
+}
+
+// NET_CLS_BPF is only enabled starting with 4.9-Q...
+int doKernelSupportsNetClsBpf(void) {
+    // NOLINTNEXTLINE(cert-env33-c)
+    return system("zcat /proc/config.gz | egrep -q '^CONFIG_NET_CLS_BPF=[my]$'");
+}
+
+// Make sure the above functions actually execute correctly rather than failing
+// due to missing binary or execution failure...
+TEST_F(ClatUtilsTest, KernelSupportsNetFuncs) {
+    // Make sure the file is present and readable and decompressable.
+    // NOLINTNEXTLINE(cert-env33-c)
+    ASSERT_EQ(W_EXITCODE(0, 0), system("zcat /proc/config.gz > /dev/null"));
+
+    int v = doKernelSupportsNetSchIngress();
+    int w = doKernelSupportsNetClsBpf();
+
+    // They should always either return 0 (match) or 1 (no match),
+    // anything else is some sort of exec/environment/etc failure.
+    if (v != W_EXITCODE(1, 0)) ASSERT_EQ(v, W_EXITCODE(0, 0));
+    if (w != W_EXITCODE(1, 0)) ASSERT_EQ(w, W_EXITCODE(0, 0));
+}
+
+// True iff CONFIG_NET_SCH_INGRESS is enabled in /proc/config.gz
+bool kernelSupportsNetSchIngress(void) {
+    return doKernelSupportsNetSchIngress() == W_EXITCODE(0, 0);
+}
+
+// True iff CONFIG_NET_CLS_BPF is enabled in /proc/config.gz
+bool kernelSupportsNetClsBpf(void) {
+    return doKernelSupportsNetClsBpf() == W_EXITCODE(0, 0);
+}
+
+// See Linux kernel source in include/net/flow.h
+#define LOOPBACK_IFINDEX 1
+
+TEST_F(ClatUtilsTest, AttachReplaceDetachClsactLo) {
+    // Technically does not depend on ebpf, but does depend on clsact,
+    // and we do not really care if it works on pre-4.9-Q anyway.
+    SKIP_IF_BPF_NOT_SUPPORTED;
+    if (!kernelSupportsNetSchIngress()) return;
+
+    int fd = openNetlinkSocket();
+    ASSERT_LE(3, fd);
+
+    // This attaches and detaches a configuration-less and thus no-op clsact
+    // qdisc to loopback interface (and it takes fractions of a second)
+    EXPECT_EQ(0, tcQdiscAddDevClsact(fd, LOOPBACK_IFINDEX));
+    EXPECT_EQ(0, tcQdiscReplaceDevClsact(fd, LOOPBACK_IFINDEX));
+    EXPECT_EQ(0, tcQdiscDelDevClsact(fd, LOOPBACK_IFINDEX));
+    close(fd);
+}
+
+void checkAttachBpfFilterClsactLo(const bool ethernet) {
+    // This test requires kernel 4.9-Q or better
+    SKIP_IF_BPF_NOT_SUPPORTED;
+    if (!kernelSupportsNetSchIngress()) return;
+    if (!kernelSupportsNetClsBpf()) return;
+
+    int bpf_fd = getClatIngressProgFd(false);
+    ASSERT_LE(3, bpf_fd);
+
+    int fd = openNetlinkSocket();
+    EXPECT_LE(3, fd);
+    if (fd >= 0) {
+        // This attaches and detaches a clsact plus ebpf program to loopback
+        // interface, but it should not affect traffic by virtue of us not
+        // actually populating the ebpf control map.
+        // Furthermore: it only takes fractions of a second.
+        EXPECT_EQ(0, tcQdiscAddDevClsact(fd, LOOPBACK_IFINDEX));
+        EXPECT_EQ(0, tcFilterAddDevBpf(fd, LOOPBACK_IFINDEX, bpf_fd, ethernet));
+        EXPECT_EQ(0, tcQdiscDelDevClsact(fd, LOOPBACK_IFINDEX));
+        close(fd);
+    }
+
+    close(bpf_fd);
+}
+
+TEST_F(ClatUtilsTest, CheckAttachBpfFilterRawIpClsactLo) {
+    checkAttachBpfFilterClsactLo(false);
+}
+
+TEST_F(ClatUtilsTest, CheckAttachBpfFilterEthernetClsactLo) {
+    checkAttachBpfFilterClsactLo(true);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/ClatdController.cpp b/server/ClatdController.cpp
index 85c1b02..5a5566b 100644
--- a/server/ClatdController.cpp
+++ b/server/ClatdController.cpp
@@ -13,140 +13,606 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include <map>
 #include <string>
 
-#include <unistd.h>
+#include <arpa/inet.h>
 #include <errno.h>
+#include <linux/if_arp.h>
+#include <linux/if_tun.h>
+#include <linux/ioctl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <spawn.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 #define LOG_TAG "ClatdController"
-#include <cutils/log.h>
+#include <log/log.h>
 
-#include <resolv_netid.h>
-
-#include "NetdConstants.h"
 #include "ClatdController.h"
+
+#include "android-base/properties.h"
+#include "android-base/scopeguard.h"
+#include "android-base/stringprintf.h"
+#include "android-base/unique_fd.h"
+#include "bpf/BpfMap.h"
+#include "netdbpf/bpf_shared.h"
+#include "netdutils/DumpWriter.h"
+
+extern "C" {
+#include "netutils/checksum.h"
+}
+
+#include "ClatUtils.h"
 #include "Fwmark.h"
 #include "NetdConstants.h"
 #include "NetworkController.h"
+#include "netid_client.h"
 
 static const char* kClatdPath = "/system/bin/clatd";
 
+// For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses in
+// 192.0.0.0/29 (RFC 7335).
+static const char* kV4AddrString = "192.0.0.4";
+static const in_addr kV4Addr = {inet_addr(kV4AddrString)};
+static const int kV4AddrLen = 29;
+
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::bpf::BpfMap;
+using android::netdutils::DumpWriter;
+using android::netdutils::ScopedIndent;
+
 namespace android {
 namespace net {
 
-ClatdController::ClatdController(NetworkController* controller)
-        : mNetCtrl(controller) {
-}
+void ClatdController::init(void) {
+    std::lock_guard guard(mutex);
 
-ClatdController::~ClatdController() {
-}
-
-// Returns the PID of the clatd running on interface |interface|, or 0 if clatd is not running on
-// |interface|.
-pid_t ClatdController::getClatdPid(char* interface) {
-    auto it = mClatdPids.find(interface);
-    return (it == mClatdPids.end() ? 0 : it->second);
-}
-
-int ClatdController::startClatd(char* interface) {
-    pid_t pid = getClatdPid(interface);
-
-    if (pid != 0) {
-        ALOGE("clatd pid=%d already started on %s", pid, interface);
-        errno = EBUSY;
-        return -1;
+    // TODO: should refactor into separate function for testability
+    if (bpf::getBpfSupportLevel() == bpf::BpfLevel::NONE) {
+        ALOGI("Pre-4.9 kernel or pre-P api shipping level - disabling clat ebpf.");
+        mClatEbpfMode = ClatEbpfDisabled;
+        return;
     }
 
-    // Pass in the interface, a netid to use for DNS lookups, and a fwmark for outgoing packets.
-    unsigned netId = mNetCtrl->getNetworkForInterface(interface);
-    if (netId == NETID_UNSET) {
-        ALOGE("interface %s not assigned to any netId", interface);
-        errno = ENODEV;
-        return -1;
+    // We know the device initially shipped with at least P...,
+    // but did it ship with at least Q?
+
+    uint64_t api_level = base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
+    if (api_level == 0) {
+        ALOGE("Cannot determine initial API level of the device.");
+        api_level = base::GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
     }
 
-    char netIdString[UINT32_STRLEN];
-    snprintf(netIdString, sizeof(netIdString), "%u", netId);
+    // Note: MINIMUM_API_REQUIRED is for eBPF as a whole and is thus P
+    if (api_level > bpf::MINIMUM_API_REQUIRED) {
+        ALOGI("4.9+ kernel and device shipped with Q+ - clat ebpf should work.");
+        mClatEbpfMode = ClatEbpfEnabled;
+    } else {
+        // We cannot guarantee that 4.9-P kernels will include NET_CLS_BPF support.
+        ALOGI("4.9+ kernel and device shipped with P - clat ebpf might work.");
+        mClatEbpfMode = ClatEbpfMaybe;
+    }
 
-    Fwmark fwmark;
+    int rv = openNetlinkSocket();
+    if (rv < 0) {
+        ALOGE("openNetlinkSocket() failure: %s", strerror(-rv));
+        mClatEbpfMode = ClatEbpfDisabled;
+        return;
+    }
+    mNetlinkFd.reset(rv);
+
+    rv = getClatIngressMapFd();
+    if (rv < 0) {
+        ALOGE("getClatIngressMapFd() failure: %s", strerror(-rv));
+        mClatEbpfMode = ClatEbpfDisabled;
+        mNetlinkFd.reset(-1);
+        return;
+    }
+    mClatIngressMap.reset(rv);
+
+    int netlinkFd = mNetlinkFd.get();
+
+    // TODO: perhaps this initial cleanup should be in its own function?
+    const auto del = [&netlinkFd](const ClatIngressKey& key,
+                                  const BpfMap<ClatIngressKey, ClatIngressValue>&) {
+        ALOGW("Removing stale clat config on interface %d.", key.iif);
+        int rv = tcQdiscDelDevClsact(netlinkFd, key.iif);
+        if (rv < 0) ALOGE("tcQdiscDelDevClsact() failure: %s", strerror(-rv));
+        return netdutils::status::ok;  // keep on going regardless
+    };
+    auto ret = mClatIngressMap.iterate(del);
+    if (!isOk(ret)) ALOGE("mClatIngressMap.iterate() failure: %s", strerror(ret.code()));
+    ret = mClatIngressMap.clear();
+    if (!isOk(ret)) ALOGE("mClatIngressMap.clear() failure: %s", strerror(ret.code()));
+}
+
+bool ClatdController::isIpv4AddressFree(in_addr_t addr) {
+    int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s == -1) {
+        return 0;
+    }
+
+    // Attempt to connect to the address. If the connection succeeds and getsockname returns the
+    // same then the address is already assigned to the system and we can't use it.
+    struct sockaddr_in sin = {.sin_family = AF_INET, .sin_addr = {addr}, .sin_port = 53};
+    socklen_t len = sizeof(sin);
+    bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
+                 getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
+                 sin.sin_addr.s_addr == addr;
+
+    close(s);
+    return !inuse;
+}
+
+// Picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order.
+//   ip        - the IP address from the configuration file
+//   prefixlen - the length of the prefix from which addresses may be selected.
+//   returns: the IPv4 address, or INADDR_NONE if no addresses were available
+in_addr_t ClatdController::selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+    // Don't accept prefixes that are too large because we scan addresses one by one.
+    if (prefixlen < 16 || prefixlen > 32) {
+        return INADDR_NONE;
+    }
+
+    // All these are in host byte order.
+    in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+    in_addr_t ipv4 = ntohl(ip.s_addr);
+    in_addr_t first_ipv4 = ipv4;
+    in_addr_t prefix = ipv4 & mask;
+
+    // Pick the first IPv4 address in the pool, wrapping around if necessary.
+    // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
+    do {
+        if (isIpv4AddressFreeFunc(htonl(ipv4))) {
+            return htonl(ipv4);
+        }
+        ipv4 = prefix | ((ipv4 + 1) & ~mask);
+    } while (ipv4 != first_ipv4);
+
+    return INADDR_NONE;
+}
+
+// Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
+void ClatdController::makeChecksumNeutral(in6_addr* v6, const in_addr v4,
+                                          const in6_addr& nat64Prefix) {
+    // Fill last 8 bytes of IPv6 address with random bits.
+    arc4random_buf(&v6->s6_addr[8], 8);
+
+    // Make the IID checksum-neutral. That is, make it so that:
+    //   checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+    // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+    //   checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+    // Do this by adjusting the two bytes in the middle of the IID.
+
+    uint16_t middlebytes = (v6->s6_addr[11] << 8) + v6->s6_addr[12];
+
+    uint32_t c1 = ip_checksum_add(0, &v4, sizeof(v4));
+    uint32_t c2 = ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+                  ip_checksum_add(0, v6, sizeof(*v6));
+
+    uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2);
+    v6->s6_addr[11] = delta >> 8;
+    v6->s6_addr[12] = delta & 0xff;
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+int ClatdController::generateIpv6Address(const char* iface, const in_addr v4,
+                                         const in6_addr& nat64Prefix, in6_addr* v6) {
+    unique_fd s(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+    if (s == -1) return -errno;
+
+    if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
+        return -errno;
+    }
+
+    sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
+    if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+        return -errno;
+    }
+
+    socklen_t len = sizeof(sin6);
+    if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+        return -errno;
+    }
+
+    *v6 = sin6.sin6_addr;
+
+    if (IN6_IS_ADDR_UNSPECIFIED(v6) || IN6_IS_ADDR_LOOPBACK(v6) || IN6_IS_ADDR_LINKLOCAL(v6) ||
+        IN6_IS_ADDR_SITELOCAL(v6) || IN6_IS_ADDR_ULA(v6)) {
+        return -ENETUNREACH;
+    }
+
+    makeChecksumNeutral(v6, v4, nat64Prefix);
+
+    return 0;
+}
+
+void ClatdController::maybeStartBpf(const ClatdTracker& tracker) {
+    if (mClatEbpfMode == ClatEbpfDisabled) return;
+
+    int rv = hardwareAddressType(tracker.iface);
+    if (rv < 0) {
+        ALOGE("hardwareAddressType(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
+              strerror(-rv));
+        return;
+    }
+
+    bool isEthernet;
+    switch (rv) {
+        case ARPHRD_ETHER:
+            isEthernet = true;
+            break;
+        case ARPHRD_RAWIP:  // in Linux 4.14+ rmnet support was upstreamed and this is 519
+        case 530:           // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+            isEthernet = false;
+            break;
+        default:
+            ALOGE("hardwareAddressType(%s[%d]) returned unknown type %d.", tracker.iface,
+                  tracker.ifIndex, rv);
+            return;
+    }
+
+    rv = getClatIngressProgFd(isEthernet);
+    if (rv < 0) {
+        ALOGE("getClatIngressProgFd(%d) failure: %s", isEthernet, strerror(-rv));
+        return;
+    }
+    unique_fd progFd(rv);
+
+    ClatIngressKey key = {
+            .iif = tracker.ifIndex,
+            .pfx96 = tracker.pfx96,
+            .local6 = tracker.v6,
+    };
+    ClatIngressValue value = {
+            // TODO: move all the clat code to eBPF and remove the tun interface entirely.
+            .oif = tracker.v4ifIndex,
+            .local4 = tracker.v4,
+    };
+
+    auto ret = mClatIngressMap.writeValue(key, value, BPF_ANY);
+    if (!isOk(ret)) {
+        ALOGE("mClatIngress.Map.writeValue failure: %s", strerror(ret.code()));
+        return;
+    }
+
+    // We do tc setup *after* populating map, so scanning through map
+    // can always be used to tell us what needs cleanup.
+
+    rv = tcQdiscAddDevClsact(mNetlinkFd, tracker.ifIndex);
+    if (rv) {
+        ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
+              strerror(-rv));
+        ret = mClatIngressMap.deleteValue(key);
+        if (!isOk(ret)) ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.code()));
+        return;
+    }
+
+    rv = tcFilterAddDevBpf(mNetlinkFd, tracker.ifIndex, progFd, isEthernet);
+    if (rv) {
+        if ((rv == -ENOENT) && (mClatEbpfMode == ClatEbpfMaybe)) {
+            ALOGI("tcFilterAddDevBpf(%d[%s], %d): %s", tracker.ifIndex, tracker.iface, isEthernet,
+                  strerror(-rv));
+        } else {
+            ALOGE("tcFilterAddDevBpf(%d[%s], %d) failure: %s", tracker.ifIndex, tracker.iface,
+                  isEthernet, strerror(-rv));
+        }
+        rv = tcQdiscDelDevClsact(mNetlinkFd, tracker.ifIndex);
+        if (rv)
+            ALOGE("tcQdiscDelDevClsact(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
+                  strerror(-rv));
+        ret = mClatIngressMap.deleteValue(key);
+        if (!isOk(ret)) ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.code()));
+        return;
+    }
+
+    // success
+}
+
+void ClatdController::maybeSetIptablesDropRule(bool add, const char* pfx96Str, const char* v6Str) {
+    if (mClatEbpfMode == ClatEbpfDisabled) return;
+
+    std::string cmd = StringPrintf(
+            "*raw\n"
+            "%s %s -s %s/96 -d %s -j DROP\n"
+            "COMMIT\n",
+            (add ? "-A" : "-D"), LOCAL_RAW_PREROUTING, pfx96Str, v6Str);
+
+    iptablesRestoreFunction(V6, cmd);
+}
+
+void ClatdController::maybeStopBpf(const ClatdTracker& tracker) {
+    if (mClatEbpfMode == ClatEbpfDisabled) return;
+
+    // No need to remove filter, since we remove qdisc it is attached to,
+    // which automatically removes everything attached to the qdisc.
+    int rv = tcQdiscDelDevClsact(mNetlinkFd, tracker.ifIndex);
+    if (rv < 0)
+        ALOGE("tcQdiscDelDevClsact(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
+              strerror(-rv));
+
+    // We cleanup map last, so scanning through map can be used to
+    // determine what still needs cleanup.
+
+    ClatIngressKey key = {
+            .iif = tracker.ifIndex,
+            .pfx96 = tracker.pfx96,
+            .local6 = tracker.v6,
+    };
+
+    auto ret = mClatIngressMap.deleteValue(key);
+    if (!isOk(ret)) ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.code()));
+}
+
+// Finds the tracker of the clatd running on interface |interface|, or nullptr if clatd has not been
+// started  on |interface|.
+ClatdController::ClatdTracker* ClatdController::getClatdTracker(const std::string& interface) {
+    auto it = mClatdTrackers.find(interface);
+    return (it == mClatdTrackers.end() ? nullptr : &it->second);
+}
+
+// Initializes a ClatdTracker for the specified interface.
+int ClatdController::ClatdTracker::init(unsigned networkId, const std::string& interface,
+                                        const std::string& v4interface,
+                                        const std::string& nat64Prefix) {
+    netId = networkId;
+
     fwmark.netId = netId;
     fwmark.explicitlySelected = true;
     fwmark.protectedFromVpn = true;
     fwmark.permission = PERMISSION_SYSTEM;
 
-    char fwmarkString[UINT32_HEX_STRLEN];
     snprintf(fwmarkString, sizeof(fwmarkString), "0x%x", fwmark.intValue);
+    snprintf(netIdString, sizeof(netIdString), "%u", netId);
+    strlcpy(iface, interface.c_str(), sizeof(iface));
+    ifIndex = if_nametoindex(iface);
+    strlcpy(v4iface, v4interface.c_str(), sizeof(v4iface));
+    v4ifIndex = if_nametoindex(v4iface);
 
-    ALOGD("starting clatd on %s", interface);
+    // Pass in everything that clatd needs: interface, a netid to use for DNS lookups, a fwmark for
+    // outgoing packets, the NAT64 prefix, and the IPv4 and IPv6 addresses.
+    // Validate the prefix and strip off the prefix length.
+    uint8_t family;
+    uint8_t prefixLen;
+    int res = parsePrefix(nat64Prefix.c_str(), &family, &pfx96, sizeof(pfx96), &prefixLen);
+    // clatd only supports /96 prefixes.
+    if (res != sizeof(pfx96)) return res;
+    if (family != AF_INET6) return -EAFNOSUPPORT;
+    if (prefixLen != 96) return -EINVAL;
+    if (!inet_ntop(AF_INET6, &pfx96, pfx96String, sizeof(pfx96String))) return -errno;
 
+    // Pick an IPv4 address.
+    // TODO: this picks the address based on other addresses that are assigned to interfaces, but
+    // the address is only actually assigned to an interface once clatd starts up. So we could end
+    // up with two clatd instances with the same IPv4 address.
+    // Stop doing this and instead pick a free one from the kV4Addr pool.
+    v4 = {selectIpv4Address(kV4Addr, kV4AddrLen)};
+    if (v4.s_addr == INADDR_NONE) {
+        ALOGE("No free IPv4 address in %s/%d", kV4AddrString, kV4AddrLen);
+        return -EADDRNOTAVAIL;
+    }
+    if (!inet_ntop(AF_INET, &v4, v4Str, sizeof(v4Str))) return -errno;
+
+    // Generate a checksum-neutral IID.
+    if (generateIpv6Address(iface, v4, pfx96, &v6)) {
+        ALOGE("Unable to find global source address on %s for %s", iface, pfx96String);
+        return -EADDRNOTAVAIL;
+    }
+    if (!inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str))) return -errno;
+
+    ALOGD("starting clatd on %s v4=%s v6=%s pfx96=%s", iface, v4Str, v6Str, pfx96String);
+    return 0;
+}
+
+int ClatdController::startClatd(const std::string& interface, const std::string& nat64Prefix,
+                                std::string* v6Str) {
+    std::lock_guard guard(mutex);
+
+    // 1. fail if pre-existing tracker already exists
+    ClatdTracker* existing = getClatdTracker(interface);
+    if (existing != nullptr) {
+        ALOGE("clatd pid=%d already started on %s", existing->pid, interface.c_str());
+        return -EBUSY;
+    }
+
+    // 2. get network id associated with this external interface
+    unsigned networkId = mNetCtrl->getNetworkForInterface(interface.c_str());
+    if (networkId == NETID_UNSET) {
+        ALOGE("Interface %s not assigned to any netId", interface.c_str());
+        return -ENODEV;
+    }
+
+    // 3. open the tun device in non blocking mode as required by clatd
+    int res = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+    if (res == -1) {
+        res = errno;
+        ALOGE("open of tun device failed (%s)", strerror(res));
+        return -res;
+    }
+    unique_fd tmpTunFd(res);
+
+    // 4. create the v4-... tun interface
+    std::string v4interface("v4-");
+    v4interface += interface;
+
+    struct ifreq ifr = {
+            .ifr_flags = IFF_TUN,
+    };
+    strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name));
+
+    res = ioctl(tmpTunFd, TUNSETIFF, &ifr, sizeof(ifr));
+    if (res == -1) {
+        res = errno;
+        ALOGE("ioctl(TUNSETIFF) failed (%s)", strerror(res));
+        return -res;
+    }
+
+    // 5. initialize tracker object
+    ClatdTracker tracker;
+    int ret = tracker.init(networkId, interface, v4interface, nat64Prefix);
+    if (ret) return ret;
+
+    // 6. create a throwaway socket to reserve a file descriptor number
+    res = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (res == -1) {
+        res = errno;
+        ALOGE("socket(ipv6/udp) failed (%s)", strerror(res));
+        return -res;
+    }
+    unique_fd passedTunFd(res);
+
+    // 7. this is the FD we'll pass to clatd on the cli, so need it as a string
+    char passedTunFdStr[INT32_STRLEN];
+    snprintf(passedTunFdStr, sizeof(passedTunFdStr), "%d", passedTunFd.get());
+
+    // 8. we're going to use this as argv[0] to clatd to make ps output more useful
     std::string progname("clatd-");
-    progname += interface;
+    progname += tracker.iface;
 
-    if ((pid = fork()) < 0) {
-        ALOGE("fork failed (%s)", strerror(errno));
-        return -1;
+    // clang-format off
+    const char* args[] = {progname.c_str(),
+                          "-i", tracker.iface,
+                          "-n", tracker.netIdString,
+                          "-m", tracker.fwmarkString,
+                          "-p", tracker.pfx96String,
+                          "-4", tracker.v4Str,
+                          "-6", tracker.v6Str,
+                          "-t", passedTunFdStr,
+                          nullptr};
+    // clang-format on
+
+    // 9. register vfork requirement
+    posix_spawnattr_t attr;
+    res = posix_spawnattr_init(&attr);
+    if (res) {
+        ALOGE("posix_spawnattr_init failed (%s)", strerror(res));
+        return -res;
+    }
+    const android::base::ScopeGuard attrGuard = [&] { posix_spawnattr_destroy(&attr); };
+    res = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
+    if (res) {
+        ALOGE("posix_spawnattr_setflags failed (%s)", strerror(res));
+        return -res;
     }
 
-    if (!pid) {
-        char *args[] = {
-            (char *) progname.c_str(),
-            (char *) "-i",
-            interface,
-            (char *) "-n",
-            netIdString,
-            (char *) "-m",
-            fwmarkString,
-            NULL
-        };
-
-        if (execv(kClatdPath, args)) {
-            ALOGE("execv failed (%s)", strerror(errno));
-            _exit(1);
-        }
-        ALOGE("Should never get here!");
-        _exit(1);
-    } else {
-        mClatdPids[interface] = pid;
-        ALOGD("clatd started on %s", interface);
+    // 10. register dup2() action: this is what 'clears' the CLOEXEC flag
+    // on the tun fd that we want the child clatd process to inherit
+    // (this will happen after the vfork, and before the execve)
+    posix_spawn_file_actions_t fa;
+    res = posix_spawn_file_actions_init(&fa);
+    if (res) {
+        ALOGE("posix_spawn_file_actions_init failed (%s)", strerror(res));
+        return -res;
+    }
+    const android::base::ScopeGuard faGuard = [&] { posix_spawn_file_actions_destroy(&fa); };
+    res = posix_spawn_file_actions_adddup2(&fa, tmpTunFd, passedTunFd);
+    if (res) {
+        ALOGE("posix_spawn_file_actions_adddup2 failed (%s)", strerror(res));
+        return -res;
     }
 
+    // 11. If necessary, add the drop rule for iptables.
+    maybeSetIptablesDropRule(true, tracker.pfx96String, tracker.v6Str);
+
+    // 12. actually perform vfork/dup2/execve
+    res = posix_spawn(&tracker.pid, kClatdPath, &fa, &attr, (char* const*)args, nullptr);
+    if (res) {
+        ALOGE("posix_spawn failed (%s)", strerror(res));
+        return -res;
+    }
+
+    // 13. configure eBPF offload - if possible
+    maybeStartBpf(tracker);
+
+    mClatdTrackers[interface] = tracker;
+    ALOGD("clatd started on %s", interface.c_str());
+
+    *v6Str = tracker.v6Str;
     return 0;
 }
 
-int ClatdController::stopClatd(char* interface) {
-    pid_t pid = getClatdPid(interface);
+int ClatdController::stopClatd(const std::string& interface) {
+    std::lock_guard guard(mutex);
+    ClatdTracker* tracker = getClatdTracker(interface);
 
-    if (pid == 0) {
+    if (tracker == nullptr) {
         ALOGE("clatd already stopped");
-        return -1;
+        return -ENODEV;
     }
 
-    ALOGD("Stopping clatd pid=%d on %s", pid, interface);
+    ALOGD("Stopping clatd pid=%d on %s", tracker->pid, interface.c_str());
 
-    kill(pid, SIGTERM);
-    waitpid(pid, NULL, 0);
-    mClatdPids.erase(interface);
+    maybeStopBpf(*tracker);
 
-    ALOGD("clatd on %s stopped", interface);
+    kill(tracker->pid, SIGTERM);
+    waitpid(tracker->pid, nullptr, 0);
+
+    maybeSetIptablesDropRule(false, tracker->pfx96String, tracker->v6Str);
+    mClatdTrackers.erase(interface);
+
+    ALOGD("clatd on %s stopped", interface.c_str());
 
     return 0;
 }
 
-bool ClatdController::isClatdStarted(char* interface) {
-    pid_t waitpid_status;
-    pid_t pid = getClatdPid(interface);
-    if (pid == 0) {
-        return false;
+void ClatdController::dump(DumpWriter& dw) {
+    std::lock_guard guard(mutex);
+
+    ScopedIndent clatdIndent(dw);
+    dw.println("ClatdController");
+
+    {
+        ScopedIndent trackerIndent(dw);
+        dw.println("Trackers: iif[iface] nat64Prefix v6Addr -> v4Addr v4iif[v4iface] [netId]");
+
+        ScopedIndent trackerDetailIndent(dw);
+        for (const auto& pair : mClatdTrackers) {
+            const ClatdTracker& tracker = pair.second;
+            dw.println("%u[%s] %s/96 %s -> %s %u[%s] [%u]", tracker.ifIndex, tracker.iface,
+                       tracker.pfx96String, tracker.v6Str, tracker.v4Str, tracker.v4ifIndex,
+                       tracker.v4iface, tracker.netId);
+        }
     }
-    waitpid_status = waitpid(pid, NULL, WNOHANG);
-    if (waitpid_status != 0) {
-        mClatdPids.erase(interface);  // child exited, don't call waitpid on it again
+
+    int mapFd = getClatIngressMapFd();
+    if (mapFd < 0) return;  // if unsupported just don't dump anything
+    BpfMap<ClatIngressKey, ClatIngressValue> configMap(mapFd);
+
+    ScopedIndent bpfIndent(dw);
+    dw.println("BPF ingress map: iif(iface) nat64Prefix v6Addr -> v4Addr oif(iface)");
+
+    ScopedIndent bpfDetailIndent(dw);
+    const auto printClatMap = [&dw](const ClatIngressKey& key, const ClatIngressValue& value,
+                                    const BpfMap<ClatIngressKey, ClatIngressValue>&) {
+        char iifStr[IFNAMSIZ] = "?";
+        char pfx96Str[INET6_ADDRSTRLEN] = "?";
+        char local6Str[INET6_ADDRSTRLEN] = "?";
+        char local4Str[INET_ADDRSTRLEN] = "?";
+        char oifStr[IFNAMSIZ] = "?";
+
+        if_indextoname(key.iif, iifStr);
+        inet_ntop(AF_INET6, &key.pfx96, pfx96Str, sizeof(pfx96Str));
+        inet_ntop(AF_INET6, &key.local6, local6Str, sizeof(local6Str));
+        inet_ntop(AF_INET, &value.local4, local4Str, sizeof(local4Str));
+        if_indextoname(value.oif, oifStr);
+
+        dw.println("%u(%s) %s/96 %s -> %s %u(%s)", key.iif, iifStr, pfx96Str, local6Str, local4Str,
+                   value.oif, oifStr);
+        return netdutils::status::ok;
+    };
+    auto res = configMap.iterateWithValue(printClatMap);
+    if (!isOk(res)) {
+        dw.println("Error printing BPF map: %s", res.msg().c_str());
     }
-    return waitpid_status == 0; // 0 while child is running
 }
 
+auto ClatdController::isIpv4AddressFreeFunc = isIpv4AddressFree;
+auto ClatdController::iptablesRestoreFunction = execIptablesRestore;
+
 }  // namespace net
 }  // namespace android
diff --git a/server/ClatdController.h b/server/ClatdController.h
index 21d4dfe..8648f17 100644
--- a/server/ClatdController.h
+++ b/server/ClatdController.h
@@ -18,6 +18,19 @@
 #define _CLATD_CONTROLLER_H
 
 #include <map>
+#include <mutex>
+#include <string>
+
+#include <linux/if.h>
+#include <netinet/in.h>
+
+#include <android-base/thread_annotations.h>
+
+#include "Fwmark.h"
+#include "NetdConstants.h"
+#include "bpf/BpfMap.h"
+#include "netdbpf/bpf_shared.h"
+#include "netdutils/DumpWriter.h"
 
 namespace android {
 namespace net {
@@ -25,18 +38,80 @@
 class NetworkController;
 
 class ClatdController {
-public:
-    explicit ClatdController(NetworkController* controller);
-    virtual ~ClatdController();
+  public:
+    explicit ClatdController(NetworkController* controller) EXCLUDES(mutex)
+        : mNetCtrl(controller){};
+    virtual ~ClatdController() EXCLUDES(mutex){};
 
-    int startClatd(char *interface);
-    int stopClatd(char* interface);
-    bool isClatdStarted(char* interface);
+    /* First thing init/startClatd/stopClatd/dump do is grab the mutex. */
+    void init(void) EXCLUDES(mutex);
 
-private:
-    NetworkController* const mNetCtrl;
-    std::map<std::string, pid_t> mClatdPids;
-    pid_t getClatdPid(char* interface);
+    int startClatd(const std::string& interface, const std::string& nat64Prefix,
+                   std::string* v6Addr) EXCLUDES(mutex);
+    int stopClatd(const std::string& interface) EXCLUDES(mutex);
+
+    void dump(netdutils::DumpWriter& dw) EXCLUDES(mutex);
+
+    static constexpr const char LOCAL_RAW_PREROUTING[] = "clat_raw_PREROUTING";
+
+  private:
+    struct ClatdTracker {
+        pid_t pid = -1;
+        unsigned ifIndex;
+        char iface[IFNAMSIZ];
+        unsigned v4ifIndex;
+        char v4iface[IFNAMSIZ];
+        Fwmark fwmark;
+        char fwmarkString[UINT32_STRLEN];
+        unsigned netId;
+        char netIdString[UINT32_STRLEN];
+        in_addr v4;
+        char v4Str[INET_ADDRSTRLEN];
+        in6_addr v6;
+        char v6Str[INET6_ADDRSTRLEN];
+        in6_addr pfx96;
+        char pfx96String[INET6_ADDRSTRLEN];
+
+        int init(unsigned networkId, const std::string& interface, const std::string& v4interface,
+                 const std::string& nat64Prefix);
+    };
+
+    std::mutex mutex;
+
+    const NetworkController* mNetCtrl GUARDED_BY(mutex);
+    std::map<std::string, ClatdTracker> mClatdTrackers GUARDED_BY(mutex);
+    ClatdTracker* getClatdTracker(const std::string& interface) REQUIRES(mutex);
+
+    static in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
+    static int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+                                   in6_addr* v6);
+    static void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
+
+    enum eClatEbpfMode {
+        ClatEbpfDisabled,  //  <4.9 kernel ||  <P api shipping level -- will not work
+        ClatEbpfMaybe,     // >=4.9 kernel &&   P api shipping level -- might work
+        ClatEbpfEnabled,   // >=4.9 kernel && >=Q api shipping level -- must work
+    };
+    eClatEbpfMode mClatEbpfMode GUARDED_BY(mutex);
+    eClatEbpfMode getEbpfMode() EXCLUDES(mutex) {
+        std::lock_guard guard(mutex);
+        return mClatEbpfMode;
+    }
+
+    base::unique_fd mNetlinkFd GUARDED_BY(mutex);
+    bpf::BpfMap<ClatIngressKey, ClatIngressValue> mClatIngressMap GUARDED_BY(mutex);
+
+    void maybeStartBpf(const ClatdTracker& tracker) REQUIRES(mutex);
+    void maybeStopBpf(const ClatdTracker& tracker) REQUIRES(mutex);
+    void maybeSetIptablesDropRule(bool add, const char* pfx96Str, const char* v6Str)
+            REQUIRES(mutex);
+
+    // For testing.
+    friend class ClatdControllerTest;
+
+    static bool (*isIpv4AddressFreeFunc)(in_addr_t);
+    static bool isIpv4AddressFree(in_addr_t addr);
+    static int (*iptablesRestoreFunction)(IptablesTarget target, const std::string& commands);
 };
 
 }  // namespace net
diff --git a/server/ClatdControllerTest.cpp b/server/ClatdControllerTest.cpp
new file mode 100644
index 0000000..e90dd1a
--- /dev/null
+++ b/server/ClatdControllerTest.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright 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.
+ *
+ * ClatdControllerTest.cpp - unit tests for ClatdController.cpp
+ */
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <netutils/ifc.h>
+
+extern "C" {
+#include <netutils/checksum.h>
+}
+
+#include "ClatdController.h"
+#include "IptablesBaseTest.h"
+#include "NetworkController.h"
+#include "tun_interface.h"
+
+static const char kIPv4LocalAddr[] = "192.0.0.4";
+
+namespace android {
+namespace net {
+
+using android::base::StringPrintf;
+
+// Mock functions for isIpv4AddressFree.
+bool neverFree(in_addr_t /* addr */) {
+    return 0;
+}
+bool alwaysFree(in_addr_t /* addr */) {
+    return 1;
+}
+bool only2Free(in_addr_t addr) {
+    return (ntohl(addr) & 0xff) == 2;
+}
+bool over6Free(in_addr_t addr) {
+    return (ntohl(addr) & 0xff) >= 6;
+}
+bool only10Free(in_addr_t addr) {
+    return (ntohl(addr) & 0xff) == 10;
+}
+
+class ClatdControllerTest : public IptablesBaseTest {
+  public:
+    ClatdControllerTest() : mClatdCtrl(nullptr) {
+        ClatdController::iptablesRestoreFunction = fakeExecIptablesRestore;
+    }
+
+    void SetUp() { resetIpv4AddressFreeFunc(); }
+
+  protected:
+    ClatdController mClatdCtrl;
+    bool isEbpfDisabled() { return mClatdCtrl.getEbpfMode() == ClatdController::ClatEbpfDisabled; }
+    void maybeSetIptablesDropRule(bool a, const char* b, const char* c) {
+        std::lock_guard guard(mClatdCtrl.mutex);
+        return mClatdCtrl.maybeSetIptablesDropRule(a, b, c);
+    }
+    void setIpv4AddressFreeFunc(bool (*func)(in_addr_t)) {
+        ClatdController::isIpv4AddressFreeFunc = func;
+    }
+    void resetIpv4AddressFreeFunc() {
+        ClatdController::isIpv4AddressFreeFunc = ClatdController::isIpv4AddressFree;
+    }
+    in_addr_t selectIpv4Address(const in_addr a, int16_t b) {
+        return ClatdController::selectIpv4Address(a, b);
+    }
+    void makeChecksumNeutral(in6_addr* a, const in_addr b, const in6_addr& c) {
+        ClatdController::makeChecksumNeutral(a, b, c);
+    }
+};
+
+TEST_F(ClatdControllerTest, SelectIpv4Address) {
+    struct in_addr addr;
+
+    inet_pton(AF_INET, kIPv4LocalAddr, &addr);
+
+    // If no addresses are free, return INADDR_NONE.
+    setIpv4AddressFreeFunc(neverFree);
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 16));
+
+    // If the configured address is free, pick that. But a prefix that's too big is invalid.
+    setIpv4AddressFreeFunc(alwaysFree);
+    EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 29));
+    EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 20));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 15));
+
+    // A prefix length of 32 works, but anything above it is invalid.
+    EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 32));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 33));
+
+    // If another address is free, pick it.
+    setIpv4AddressFreeFunc(over6Free);
+    EXPECT_EQ(inet_addr("192.0.0.6"), selectIpv4Address(addr, 29));
+
+    // Check that we wrap around to addresses that are lower than the first address.
+    setIpv4AddressFreeFunc(only2Free);
+    EXPECT_EQ(inet_addr("192.0.0.2"), selectIpv4Address(addr, 29));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 30));
+
+    // If a free address exists outside the prefix, we don't pick it.
+    setIpv4AddressFreeFunc(only10Free);
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29));
+    EXPECT_EQ(inet_addr("192.0.0.10"), selectIpv4Address(addr, 24));
+
+    // Now try using the real function which sees if IP addresses are free using bind().
+    // Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8.
+    resetIpv4AddressFreeFunc();
+    addr.s_addr = inet_addr("8.8.8.8");
+    EXPECT_EQ(inet_addr("8.8.8.8"), selectIpv4Address(addr, 29));
+
+    addr.s_addr = inet_addr("127.0.0.1");
+    EXPECT_EQ(inet_addr("127.0.0.2"), selectIpv4Address(addr, 29));
+}
+
+TEST_F(ClatdControllerTest, MakeChecksumNeutral) {
+    // We can't test generateIPv6Address here since it requires manipulating routing, which we can't
+    // do without talking to the real netd on the system.
+    uint32_t rand = arc4random_uniform(0xffffffff);
+    uint16_t rand1 = rand & 0xffff;
+    uint16_t rand2 = (rand >> 16) & 0xffff;
+    std::string v6PrefixStr = StringPrintf("2001:db8:%x:%x", rand1, rand2);
+    std::string v6InterfaceAddrStr = StringPrintf("%s::%x:%x", v6PrefixStr.c_str(), rand2, rand1);
+    std::string nat64PrefixStr = StringPrintf("2001:db8:%x:%x::", rand2, rand1);
+
+    in_addr v4 = {inet_addr(kIPv4LocalAddr)};
+    in6_addr v6InterfaceAddr;
+    ASSERT_TRUE(inet_pton(AF_INET6, v6InterfaceAddrStr.c_str(), &v6InterfaceAddr));
+    in6_addr nat64Prefix;
+    ASSERT_TRUE(inet_pton(AF_INET6, nat64PrefixStr.c_str(), &nat64Prefix));
+
+    // Generate a boatload of random IIDs.
+    int onebits = 0;
+    uint64_t prev_iid = 0;
+    for (int i = 0; i < 100000; i++) {
+        in6_addr v6 = v6InterfaceAddr;
+        makeChecksumNeutral(&v6, v4, nat64Prefix);
+
+        // Check the generated IP address is in the same prefix as the interface IPv6 address.
+        EXPECT_EQ(0, memcmp(&v6, &v6InterfaceAddr, 8));
+
+        // Check that consecutive IIDs are not the same.
+        uint64_t iid = *(uint64_t*)(&v6.s6_addr[8]);
+        ASSERT_TRUE(iid != prev_iid)
+                << "Two consecutive random IIDs are the same: " << std::showbase << std::hex << iid
+                << "\n";
+        prev_iid = iid;
+
+        // Check that the IID is checksum-neutral with the NAT64 prefix and the
+        // local prefix.
+        uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, &v4, sizeof(v4)));
+        uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+                                         ip_checksum_add(0, &v6, sizeof(v6)));
+
+        if (c1 != c2) {
+            char v6Str[INET6_ADDRSTRLEN];
+            inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str));
+            FAIL() << "Bad IID: " << v6Str << " not checksum-neutral with " << kIPv4LocalAddr
+                   << " and " << nat64PrefixStr.c_str() << std::showbase << std::hex
+                   << "\n  IPv4 checksum: " << c1 << "\n  IPv6 checksum: " << c2 << "\n";
+        }
+
+        // Check that IIDs are roughly random and use all the bits by counting the
+        // total number of bits set to 1 in a random sample of 100000 generated IIDs.
+        onebits += __builtin_popcountll(*(uint64_t*)&iid);
+    }
+    EXPECT_LE(3190000, onebits);
+    EXPECT_GE(3210000, onebits);
+}
+
+TEST_F(ClatdControllerTest, AddRemoveIptablesRule) {
+    if (isEbpfDisabled()) return;
+
+    ExpectedIptablesCommands expected = {
+            {V6,
+             "*raw\n"
+             "-A clat_raw_PREROUTING -s 64:ff9b::/96 -d 2001:db8::1:2:3:4 -j DROP\n"
+             "COMMIT\n"},
+    };
+    maybeSetIptablesDropRule(true, "64:ff9b::", "2001:db8::1:2:3:4");
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+            {V6,
+             "*raw\n"
+             "-D clat_raw_PREROUTING -s 64:ff9b::/96 -d 2001:db8::a:b:c:d -j DROP\n"
+             "COMMIT\n"},
+    };
+    maybeSetIptablesDropRule(false, "64:ff9b::", "2001:db8::a:b:c:d");
+    expectIptablesRestoreCommands(expected);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/CleanSpec.mk b/server/CleanSpec.mk
deleted file mode 100644
index b84e1b6..0000000
--- a/server/CleanSpec.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2007 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.
-#
-
-# If you don't need to do a full clean build but would like to touch
-# a file or delete some intermediate files, add a clean step to the end
-# of the list.  These steps will only be run once, if they haven't been
-# run before.
-#
-# E.g.:
-#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
-#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
-#
-# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
-# files that are missing or have been moved.
-#
-# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
-# Use $(OUT_DIR) to refer to the "out" directory.
-#
-# If you need to re-do something that's already mentioned, just copy
-# the command and add it to the bottom of the list.  E.g., if a change
-# that you made last week required touching a file and a change you
-# made today requires touching the same file, just copy the old
-# touch step and add it to the end of the list.
-#
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
-
-# For example:
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
-#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
-#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp
deleted file mode 100644
index b2e988e..0000000
--- a/server/CommandListener.cpp
+++ /dev/null
@@ -1,1545 +0,0 @@
-/*
- * 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.
- */
-
-// #define LOG_NDEBUG 0
-
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <dirent.h>
-#include <errno.h>
-#include <string.h>
-#include <linux/if.h>
-#include <resolv_netid.h>
-#include <resolv_params.h>
-
-#define __STDC_FORMAT_MACROS 1
-#include <inttypes.h>
-
-#define LOG_TAG "CommandListener"
-
-#include <cutils/log.h>
-#include <netdutils/Status.h>
-#include <netdutils/StatusOr.h>
-#include <netutils/ifc.h>
-#include <sysutils/SocketClient.h>
-
-#include "Controllers.h"
-#include "CommandListener.h"
-#include "ResponseCode.h"
-#include "BandwidthController.h"
-#include "IdletimerController.h"
-#include "InterfaceController.h"
-#include "NetdConstants.h"
-#include "FirewallController.h"
-#include "RouteController.h"
-#include "UidRanges.h"
-
-#include <string>
-#include <vector>
-
-namespace android {
-namespace net {
-
-namespace {
-
-const unsigned NUM_OEM_IDS = NetworkController::MAX_OEM_ID - NetworkController::MIN_OEM_ID + 1;
-
-unsigned stringToNetId(const char* arg) {
-    if (!strcmp(arg, "local")) {
-        return NetworkController::LOCAL_NET_ID;
-    }
-    // OEM NetIds are "oem1", "oem2", .., "oem50".
-    if (!strncmp(arg, "oem", 3)) {
-        unsigned n = strtoul(arg + 3, NULL, 0);
-        if (1 <= n && n <= NUM_OEM_IDS) {
-            return NetworkController::MIN_OEM_ID + n;
-        }
-        return NETID_UNSET;
-    } else if (!strncmp(arg, "handle", 6)) {
-        unsigned n = netHandleToNetId((net_handle_t)strtoull(arg + 6, NULL, 10));
-        if (NetworkController::MIN_OEM_ID <= n && n <= NetworkController::MAX_OEM_ID) {
-            return n;
-        }
-        return NETID_UNSET;
-    }
-    // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
-    return strtoul(arg, NULL, 0);
-}
-
-class LockingFrameworkCommand : public FrameworkCommand {
-public:
-    LockingFrameworkCommand(FrameworkCommand *wrappedCmd, android::RWLock& lock) :
-            FrameworkCommand(wrappedCmd->getCommand()),
-            mWrappedCmd(wrappedCmd),
-            mLock(lock) {}
-
-    int runCommand(SocketClient *c, int argc, char **argv) {
-        android::RWLock::AutoWLock lock(mLock);
-        return mWrappedCmd->runCommand(c, argc, argv);
-    }
-
-private:
-    FrameworkCommand *mWrappedCmd;
-    android::RWLock& mLock;
-};
-
-
-}  // namespace
-
-void CommandListener::registerLockingCmd(FrameworkCommand *cmd, android::RWLock& lock) {
-    registerCmd(new LockingFrameworkCommand(cmd, lock));
-}
-
-CommandListener::CommandListener() : FrameworkListener(SOCKET_NAME, true) {
-    registerLockingCmd(new InterfaceCmd());
-    registerLockingCmd(new IpFwdCmd(), gCtls->tetherCtrl.lock);
-    registerLockingCmd(new TetherCmd(), gCtls->tetherCtrl.lock);
-    registerLockingCmd(new NatCmd(), gCtls->tetherCtrl.lock);
-    registerLockingCmd(new ListTtysCmd());
-    registerLockingCmd(new PppdCmd());
-    registerLockingCmd(new BandwidthControlCmd(), gCtls->bandwidthCtrl.lock);
-    registerLockingCmd(new IdletimerControlCmd());
-    registerLockingCmd(new ResolverCmd());
-    registerLockingCmd(new FirewallCmd(), gCtls->firewallCtrl.lock);
-    registerLockingCmd(new ClatdCmd());
-    registerLockingCmd(new NetworkCommand());
-    registerLockingCmd(new StrictCmd());
-}
-
-CommandListener::InterfaceCmd::InterfaceCmd() :
-                 NetdCommand("interface") {
-}
-
-int CommandListener::InterfaceCmd::runCommand(SocketClient *cli,
-                                                      int argc, char **argv) {
-    if (argc < 2) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-        return 0;
-    }
-
-    if (!strcmp(argv[1], "list")) {
-        const auto ifacePairs =
-            InterfaceController::getIfaceList();
-        if (ifacePairs.status() != netdutils::status::ok) {
-            cli->sendMsg(ResponseCode::OperationFailed, "Failed to get interface list", true);
-            return 0;
-        }
-        for (const auto& ifacePair : ifacePairs.value()) {
-            cli->sendMsg(ResponseCode::InterfaceListResult, ifacePair.first.c_str(), false);
-        }
-
-        cli->sendMsg(ResponseCode::CommandOkay, "Interface list completed", false);
-        return 0;
-    } else {
-        /*
-         * These commands take a minimum of 3 arguments
-         */
-        if (argc < 3) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-            return 0;
-        }
-
-        if (!strcmp(argv[1], "getcfg")) {
-            struct in_addr addr;
-            int prefixLength;
-            unsigned char hwaddr[6];
-            unsigned flags = 0;
-
-            ifc_init();
-            memset(hwaddr, 0, sizeof(hwaddr));
-
-            if (ifc_get_info(argv[2], &addr.s_addr, &prefixLength, &flags)) {
-                cli->sendMsg(ResponseCode::OperationFailed, "Interface not found", true);
-                ifc_close();
-                return 0;
-            }
-
-            if (ifc_get_hwaddr(argv[2], (void *) hwaddr)) {
-                ALOGW("Failed to retrieve HW addr for %s (%s)", argv[2], strerror(errno));
-            }
-
-            char *addr_s = strdup(inet_ntoa(addr));
-            const char *updown, *brdcst, *loopbk, *ppp, *running, *multi;
-
-            updown =  (flags & IFF_UP)           ? "up" : "down";
-            brdcst =  (flags & IFF_BROADCAST)    ? " broadcast" : "";
-            loopbk =  (flags & IFF_LOOPBACK)     ? " loopback" : "";
-            ppp =     (flags & IFF_POINTOPOINT)  ? " point-to-point" : "";
-            running = (flags & IFF_RUNNING)      ? " running" : "";
-            multi =   (flags & IFF_MULTICAST)    ? " multicast" : "";
-
-            char *flag_s;
-
-            asprintf(&flag_s, "%s%s%s%s%s%s", updown, brdcst, loopbk, ppp, running, multi);
-
-            char *msg = NULL;
-            asprintf(&msg, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x %s %d %s",
-                     hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5],
-                     addr_s, prefixLength, flag_s);
-
-            cli->sendMsg(ResponseCode::InterfaceGetCfgResult, msg, false);
-
-            free(addr_s);
-            free(flag_s);
-            free(msg);
-
-            ifc_close();
-            return 0;
-        } else if (!strcmp(argv[1], "setcfg")) {
-            // arglist: iface [addr prefixLength] flags
-            if (argc < 4) {
-                cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-                return 0;
-            }
-            ALOGD("Setting iface cfg");
-
-            struct in_addr addr;
-            int index = 5;
-
-            ifc_init();
-
-            if (!inet_aton(argv[3], &addr)) {
-                // Handle flags only case
-                index = 3;
-            } else {
-                if (ifc_set_addr(argv[2], 0)) {
-                    cli->sendMsg(ResponseCode::OperationFailed, "Failed to clear address", true);
-                    ifc_close();
-                    return 0;
-                }
-                if (addr.s_addr != 0) {
-                    if (ifc_add_address(argv[2], argv[3], atoi(argv[4]))) {
-                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to set address", true);
-                        ifc_close();
-                        return 0;
-                    }
-                }
-            }
-
-            /* Process flags */
-            for (int i = index; i < argc; i++) {
-                char *flag = argv[i];
-                if (!strcmp(flag, "up")) {
-                    ALOGD("Trying to bring up %s", argv[2]);
-                    if (ifc_up(argv[2])) {
-                        ALOGE("Error upping interface");
-                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to up interface", true);
-                        ifc_close();
-                        return 0;
-                    }
-                } else if (!strcmp(flag, "down")) {
-                    ALOGD("Trying to bring down %s", argv[2]);
-                    if (ifc_down(argv[2])) {
-                        ALOGE("Error downing interface");
-                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to down interface", true);
-                        ifc_close();
-                        return 0;
-                    }
-                } else if (!strcmp(flag, "broadcast")) {
-                    // currently ignored
-                } else if (!strcmp(flag, "multicast")) {
-                    // currently ignored
-                } else if (!strcmp(flag, "running")) {
-                    // currently ignored
-                } else if (!strcmp(flag, "loopback")) {
-                    // currently ignored
-                } else if (!strcmp(flag, "point-to-point")) {
-                    // currently ignored
-                } else {
-                    cli->sendMsg(ResponseCode::CommandParameterError, "Flag unsupported", false);
-                    ifc_close();
-                    return 0;
-                }
-            }
-
-            cli->sendMsg(ResponseCode::CommandOkay, "Interface configuration set", false);
-            ifc_close();
-            return 0;
-        } else if (!strcmp(argv[1], "clearaddrs")) {
-            // arglist: iface
-            ALOGD("Clearing all IP addresses on %s", argv[2]);
-
-            ifc_clear_addresses(argv[2]);
-
-            cli->sendMsg(ResponseCode::CommandOkay, "Interface IP addresses cleared", false);
-            return 0;
-        } else if (!strcmp(argv[1], "ipv6privacyextensions")) {
-            if (argc != 4) {
-                cli->sendMsg(ResponseCode::CommandSyntaxError,
-                        "Usage: interface ipv6privacyextensions <interface> <enable|disable>",
-                        false);
-                return 0;
-            }
-            int enable = !strncmp(argv[3], "enable", 7);
-            if (InterfaceController::setIPv6PrivacyExtensions(argv[2], enable) == 0) {
-                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 privacy extensions changed", false);
-            } else {
-                cli->sendMsg(ResponseCode::OperationFailed,
-                        "Failed to set ipv6 privacy extensions", true);
-            }
-            return 0;
-        } else if (!strcmp(argv[1], "ipv6")) {
-            if (argc != 4) {
-                cli->sendMsg(ResponseCode::CommandSyntaxError,
-                        "Usage: interface ipv6 <interface> <enable|disable>",
-                        false);
-                return 0;
-            }
-
-            int enable = !strncmp(argv[3], "enable", 7);
-            if (InterfaceController::setEnableIPv6(argv[2], enable) == 0) {
-                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 state changed", false);
-            } else {
-                cli->sendMsg(ResponseCode::OperationFailed,
-                        "Failed to change IPv6 state", true);
-            }
-            return 0;
-        } else if (!strcmp(argv[1], "setmtu")) {
-            if (argc != 4) {
-                cli->sendMsg(ResponseCode::CommandSyntaxError,
-                        "Usage: interface setmtu <interface> <val>", false);
-                return 0;
-            }
-            if (InterfaceController::setMtu(argv[2], argv[3]) == 0) {
-                cli->sendMsg(ResponseCode::CommandOkay, "MTU changed", false);
-            } else {
-                cli->sendMsg(ResponseCode::OperationFailed,
-                        "Failed to set MTU", true);
-            }
-            return 0;
-        } else {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown interface cmd", false);
-            return 0;
-        }
-    }
-    return 0;
-}
-
-
-CommandListener::ListTtysCmd::ListTtysCmd() :
-                 NetdCommand("list_ttys") {
-}
-
-int CommandListener::ListTtysCmd::runCommand(SocketClient *cli,
-                                             int /* argc */, char ** /* argv */) {
-    TtyCollection *tlist = gCtls->pppCtrl.getTtyList();
-    TtyCollection::iterator it;
-
-    for (it = tlist->begin(); it != tlist->end(); ++it) {
-        cli->sendMsg(ResponseCode::TtyListResult, *it, false);
-    }
-
-    cli->sendMsg(ResponseCode::CommandOkay, "Ttys listed.", false);
-    return 0;
-}
-
-CommandListener::IpFwdCmd::IpFwdCmd() :
-                 NetdCommand("ipfwd") {
-}
-
-int CommandListener::IpFwdCmd::runCommand(SocketClient *cli, int argc, char **argv) {
-    bool matched = false;
-    bool success;
-
-    if (argc == 2) {
-        //   0     1
-        // ipfwd status
-        if (!strcmp(argv[1], "status")) {
-            char *tmp = NULL;
-
-            asprintf(&tmp, "Forwarding %s",
-                     ((gCtls->tetherCtrl.forwardingRequestCount() > 0) ? "enabled" : "disabled"));
-            cli->sendMsg(ResponseCode::IpFwdStatusResult, tmp, false);
-            free(tmp);
-            return 0;
-        }
-    } else if (argc == 3) {
-        //  0      1         2
-        // ipfwd enable  <requester>
-        // ipfwd disable <requester>
-        if (!strcmp(argv[1], "enable")) {
-            matched = true;
-            success = gCtls->tetherCtrl.enableForwarding(argv[2]);
-        } else if (!strcmp(argv[1], "disable")) {
-            matched = true;
-            success = gCtls->tetherCtrl.disableForwarding(argv[2]);
-        }
-    } else if (argc == 4) {
-        //  0      1      2     3
-        // ipfwd  add   wlan0 dummy0
-        // ipfwd remove wlan0 dummy0
-        int ret = 0;
-        if (!strcmp(argv[1], "add")) {
-            matched = true;
-            ret = RouteController::enableTethering(argv[2], argv[3]);
-        } else if (!strcmp(argv[1], "remove")) {
-            matched = true;
-            ret = RouteController::disableTethering(argv[2], argv[3]);
-        }
-        success = (ret == 0);
-        errno = -ret;
-    }
-
-    if (!matched) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown ipfwd cmd", false);
-        return 0;
-    }
-
-    if (success) {
-        cli->sendMsg(ResponseCode::CommandOkay, "ipfwd operation succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "ipfwd operation failed", true);
-    }
-    return 0;
-}
-
-CommandListener::TetherCmd::TetherCmd() :
-                 NetdCommand("tether") {
-}
-
-int CommandListener::TetherCmd::runCommand(SocketClient *cli,
-                                                      int argc, char **argv) {
-    int rc = 0;
-
-    if (argc < 2) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-        return 0;
-    }
-
-    if (!strcmp(argv[1], "stop")) {
-        rc = gCtls->tetherCtrl.stopTethering();
-    } else if (!strcmp(argv[1], "status")) {
-        char *tmp = NULL;
-
-        asprintf(&tmp, "Tethering services %s",
-                 (gCtls->tetherCtrl.isTetheringStarted() ? "started" : "stopped"));
-        cli->sendMsg(ResponseCode::TetherStatusResult, tmp, false);
-        free(tmp);
-        return 0;
-    } else if (argc == 3) {
-        if (!strcmp(argv[1], "interface") && !strcmp(argv[2], "list")) {
-            for (const auto &ifname : gCtls->tetherCtrl.getTetheredInterfaceList()) {
-                cli->sendMsg(ResponseCode::TetherInterfaceListResult, ifname.c_str(), false);
-            }
-        } else if (!strcmp(argv[1], "dns") && !strcmp(argv[2], "list")) {
-            char netIdStr[UINT32_STRLEN];
-            snprintf(netIdStr, sizeof(netIdStr), "%u", gCtls->tetherCtrl.getDnsNetId());
-            cli->sendMsg(ResponseCode::TetherDnsFwdNetIdResult, netIdStr, false);
-
-            for (const auto &fwdr : gCtls->tetherCtrl.getDnsForwarders()) {
-                cli->sendMsg(ResponseCode::TetherDnsFwdTgtListResult, fwdr.c_str(), false);
-            }
-        }
-    } else {
-        /*
-         * These commands take a minimum of 4 arguments
-         */
-        if (argc < 4) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-            return 0;
-        }
-
-        if (!strcmp(argv[1], "start")) {
-            if (argc % 2 == 1) {
-                cli->sendMsg(ResponseCode::CommandSyntaxError, "Bad number of arguments", false);
-                return 0;
-            }
-
-            const int num_addrs = argc - 2;
-            // TODO: consider moving this validation into TetherController.
-            struct in_addr tmp_addr;
-            for (int arg_index = 2; arg_index < argc; arg_index++) {
-                if (!inet_aton(argv[arg_index], &tmp_addr)) {
-                    cli->sendMsg(ResponseCode::CommandParameterError, "Invalid address", false);
-                    return 0;
-                }
-            }
-
-            rc = gCtls->tetherCtrl.startTethering(num_addrs, &(argv[2]));
-        } else if (!strcmp(argv[1], "interface")) {
-            if (!strcmp(argv[2], "add")) {
-                rc = gCtls->tetherCtrl.tetherInterface(argv[3]);
-            } else if (!strcmp(argv[2], "remove")) {
-                rc = gCtls->tetherCtrl.untetherInterface(argv[3]);
-            /* else if (!strcmp(argv[2], "list")) handled above */
-            } else {
-                cli->sendMsg(ResponseCode::CommandParameterError,
-                             "Unknown tether interface operation", false);
-                return 0;
-            }
-        } else if (!strcmp(argv[1], "dns")) {
-            if (!strcmp(argv[2], "set")) {
-                if (argc < 5) {
-                    cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-                    return 0;
-                }
-                unsigned netId = stringToNetId(argv[3]);
-                rc = gCtls->tetherCtrl.setDnsForwarders(netId, &argv[4], argc - 4);
-            /* else if (!strcmp(argv[2], "list")) handled above */
-            } else {
-                cli->sendMsg(ResponseCode::CommandParameterError,
-                             "Unknown tether interface operation", false);
-                return 0;
-            }
-        } else {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown tether cmd", false);
-            return 0;
-        }
-    }
-
-    if (!rc) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Tether operation succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Tether operation failed", true);
-    }
-
-    return 0;
-}
-
-CommandListener::NatCmd::NatCmd() :
-                 NetdCommand("nat") {
-}
-
-int CommandListener::NatCmd::runCommand(SocketClient *cli,
-                                                      int argc, char **argv) {
-    int rc = 0;
-
-    if (argc < 5) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-        return 0;
-    }
-
-    //  0     1       2        3
-    // nat  enable intiface extiface
-    // nat disable intiface extiface
-    if (!strcmp(argv[1], "enable") && argc >= 4) {
-        rc = gCtls->tetherCtrl.enableNat(argv[2], argv[3]);
-        if(!rc) {
-            /* Ignore ifaces for now. */
-            rc = gCtls->bandwidthCtrl.setGlobalAlertInForwardChain();
-        }
-    } else if (!strcmp(argv[1], "disable") && argc >= 4) {
-        /* Ignore ifaces for now. */
-        rc = gCtls->bandwidthCtrl.removeGlobalAlertInForwardChain();
-        rc |= gCtls->tetherCtrl.disableNat(argv[2], argv[3]);
-    } else {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown nat cmd", false);
-        return 0;
-    }
-
-    if (!rc) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Nat operation succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Nat operation failed", true);
-    }
-
-    return 0;
-}
-
-CommandListener::PppdCmd::PppdCmd() :
-                 NetdCommand("pppd") {
-}
-
-int CommandListener::PppdCmd::runCommand(SocketClient *cli,
-                                                      int argc, char **argv) {
-    int rc = 0;
-
-    if (argc < 3) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-        return 0;
-    }
-
-    if (!strcmp(argv[1], "attach")) {
-        struct in_addr l, r, dns1, dns2;
-
-        memset(&dns1, 0, sizeof(struct in_addr));
-        memset(&dns2, 0, sizeof(struct in_addr));
-
-        if (!inet_aton(argv[3], &l)) {
-            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid local address", false);
-            return 0;
-        }
-        if (!inet_aton(argv[4], &r)) {
-            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid remote address", false);
-            return 0;
-        }
-        if ((argc > 3) && (!inet_aton(argv[5], &dns1))) {
-            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid dns1 address", false);
-            return 0;
-        }
-        if ((argc > 4) && (!inet_aton(argv[6], &dns2))) {
-            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid dns2 address", false);
-            return 0;
-        }
-        rc = gCtls->pppCtrl.attachPppd(argv[2], l, r, dns1, dns2);
-    } else if (!strcmp(argv[1], "detach")) {
-        rc = gCtls->pppCtrl.detachPppd(argv[2]);
-    } else {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown pppd cmd", false);
-        return 0;
-    }
-
-    if (!rc) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Pppd operation succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Pppd operation failed", true);
-    }
-
-    return 0;
-}
-
-CommandListener::ResolverCmd::ResolverCmd() :
-        NetdCommand("resolver") {
-}
-
-int CommandListener::ResolverCmd::runCommand(SocketClient *cli, int argc, char **margv) {
-    int rc = 0;
-    const char **argv = const_cast<const char **>(margv);
-
-    if (argc < 3) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Resolver missing arguments", false);
-        return 0;
-    }
-
-    unsigned netId = stringToNetId(argv[2]);
-    // TODO: Consider making NetworkController.isValidNetwork() public
-    // and making that check here.
-
-    if (!strcmp(argv[1], "setnetdns")) {
-        if (!parseAndExecuteSetNetDns(netId, argc, argv)) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                    "Wrong number of or invalid arguments to resolver setnetdns", false);
-            return 0;
-        }
-    } else if (!strcmp(argv[1], "clearnetdns")) { // "resolver clearnetdns <netId>"
-        if (argc == 3) {
-            rc = gCtls->resolverCtrl.clearDnsServers(netId);
-        } else {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                    "Wrong number of arguments to resolver clearnetdns", false);
-            return 0;
-        }
-    } else {
-        cli->sendMsg(ResponseCode::CommandSyntaxError,"Resolver unknown command", false);
-        return 0;
-    }
-
-    if (!rc) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Resolver command succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Resolver command failed", true);
-    }
-
-    return 0;
-}
-
-bool CommandListener::ResolverCmd::parseAndExecuteSetNetDns(int netId, int argc,
-        const char** argv) {
-    // "resolver setnetdns <netId> <domains> <dns1> [<dns2> ...] [--params <params>]"
-    // TODO: This code has to be replaced by a Binder call ASAP
-    if (argc < 5) {
-        return false;
-    }
-    int end = argc;
-    __res_params params;
-    const __res_params* paramsPtr = nullptr;
-    if (end > 6 && !strcmp(argv[end - 2], "--params")) {
-        const char* paramsStr = argv[end - 1];
-        end -= 2;
-        if (sscanf(paramsStr, "%hu %hhu %hhu %hhu", &params.sample_validity,
-                &params.success_threshold, &params.min_samples, &params.max_samples) != 4) {
-            return false;
-        }
-        paramsPtr = &params;
-    }
-    return gCtls->resolverCtrl.setDnsServers(netId, argv[3], &argv[4], end - 4, paramsPtr) == 0;
-}
-
-CommandListener::BandwidthControlCmd::BandwidthControlCmd() :
-    NetdCommand("bandwidth") {
-}
-
-void CommandListener::BandwidthControlCmd::sendGenericSyntaxError(SocketClient *cli, const char *usageMsg) {
-    char *msg;
-    asprintf(&msg, "Usage: bandwidth %s", usageMsg);
-    cli->sendMsg(ResponseCode::CommandSyntaxError, msg, false);
-    free(msg);
-}
-
-void CommandListener::BandwidthControlCmd::sendGenericOkFail(SocketClient *cli, int cond) {
-    if (!cond) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Bandwidth command succeeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Bandwidth command failed", false);
-    }
-}
-
-void CommandListener::BandwidthControlCmd::sendGenericOpFailed(SocketClient *cli, const char *errMsg) {
-    cli->sendMsg(ResponseCode::OperationFailed, errMsg, false);
-}
-
-int CommandListener::BandwidthControlCmd::runCommand(SocketClient *cli, int argc, char **argv) {
-    if (argc < 2) {
-        sendGenericSyntaxError(cli, "<cmds> <args...>");
-        return 0;
-    }
-
-    ALOGV("bwctrlcmd: argc=%d %s %s ...", argc, argv[0], argv[1]);
-
-    if (!strcmp(argv[1], "enable")) {
-        int rc = gCtls->bandwidthCtrl.enableBandwidthControl(true);
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "disable")) {
-        int rc = gCtls->bandwidthCtrl.disableBandwidthControl();
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "removequota") || !strcmp(argv[1], "rq")) {
-        if (argc != 3) {
-            sendGenericSyntaxError(cli, "removequota <interface>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeInterfaceSharedQuota(argv[2]);
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "getquota") || !strcmp(argv[1], "gq")) {
-        int64_t bytes;
-        if (argc != 2) {
-            sendGenericSyntaxError(cli, "getquota");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.getInterfaceSharedQuota(&bytes);
-        if (rc) {
-            sendGenericOpFailed(cli, "Failed to get quota");
-            return 0;
-        }
-
-        char *msg;
-        asprintf(&msg, "%" PRId64, bytes);
-        cli->sendMsg(ResponseCode::QuotaCounterResult, msg, false);
-        free(msg);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "getiquota") || !strcmp(argv[1], "giq")) {
-        int64_t bytes;
-        if (argc != 3) {
-            sendGenericSyntaxError(cli, "getiquota <iface>");
-            return 0;
-        }
-
-        int rc = gCtls->bandwidthCtrl.getInterfaceQuota(argv[2], &bytes);
-        if (rc) {
-            sendGenericOpFailed(cli, "Failed to get quota");
-            return 0;
-        }
-        char *msg;
-        asprintf(&msg, "%" PRId64, bytes);
-        cli->sendMsg(ResponseCode::QuotaCounterResult, msg, false);
-        free(msg);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "setquota") || !strcmp(argv[1], "sq")) {
-        if (argc != 4) {
-            sendGenericSyntaxError(cli, "setquota <interface> <bytes>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.setInterfaceSharedQuota(argv[2], atoll(argv[3]));
-        sendGenericOkFail(cli, rc);
-        return 0;
-    }
-    if (!strcmp(argv[1], "setquotas") || !strcmp(argv[1], "sqs")) {
-        int rc;
-        if (argc < 4) {
-            sendGenericSyntaxError(cli, "setquotas <bytes> <interface> ...");
-            return 0;
-        }
-
-        for (int q = 3; argc >= 4; q++, argc--) {
-            rc = gCtls->bandwidthCtrl.setInterfaceSharedQuota(argv[q], atoll(argv[2]));
-            if (rc) {
-                char *msg;
-                asprintf(&msg, "bandwidth setquotas %s %s failed", argv[2], argv[q]);
-                cli->sendMsg(ResponseCode::OperationFailed,
-                             msg, false);
-                free(msg);
-                return 0;
-            }
-        }
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "removequotas") || !strcmp(argv[1], "rqs")) {
-        int rc;
-        if (argc < 3) {
-            sendGenericSyntaxError(cli, "removequotas <interface> ...");
-            return 0;
-        }
-
-        for (int q = 2; argc >= 3; q++, argc--) {
-            rc = gCtls->bandwidthCtrl.removeInterfaceSharedQuota(argv[q]);
-            if (rc) {
-                char *msg;
-                asprintf(&msg, "bandwidth removequotas %s failed", argv[q]);
-                cli->sendMsg(ResponseCode::OperationFailed,
-                             msg, false);
-                free(msg);
-                return 0;
-            }
-        }
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "removeiquota") || !strcmp(argv[1], "riq")) {
-        if (argc != 3) {
-            sendGenericSyntaxError(cli, "removeiquota <interface>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeInterfaceQuota(argv[2]);
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "setiquota") || !strcmp(argv[1], "siq")) {
-        if (argc != 4) {
-            sendGenericSyntaxError(cli, "setiquota <interface> <bytes>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.setInterfaceQuota(argv[2], atoll(argv[3]));
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "addnaughtyapps") || !strcmp(argv[1], "ana")) {
-        if (argc < 3) {
-            sendGenericSyntaxError(cli, "addnaughtyapps <appUid> ...");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.addNaughtyApps(argc - 2, argv + 2);
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-
-    }
-    if (!strcmp(argv[1], "removenaughtyapps") || !strcmp(argv[1], "rna")) {
-        if (argc < 3) {
-            sendGenericSyntaxError(cli, "removenaughtyapps <appUid> ...");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeNaughtyApps(argc - 2, argv + 2);
-        sendGenericOkFail(cli, rc);
-        return 0;
-    }
-    if (!strcmp(argv[1], "addniceapps") || !strcmp(argv[1], "aha")) {
-        if (argc < 3) {
-            sendGenericSyntaxError(cli, "addniceapps <appUid> ...");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.addNiceApps(argc - 2, argv + 2);
-        sendGenericOkFail(cli, rc);
-        return 0;
-    }
-    if (!strcmp(argv[1], "removeniceapps") || !strcmp(argv[1], "rha")) {
-        if (argc < 3) {
-            sendGenericSyntaxError(cli, "removeniceapps <appUid> ...");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeNiceApps(argc - 2, argv + 2);
-        sendGenericOkFail(cli, rc);
-        return 0;
-    }
-    if (!strcmp(argv[1], "setglobalalert") || !strcmp(argv[1], "sga")) {
-        if (argc != 3) {
-            sendGenericSyntaxError(cli, "setglobalalert <bytes>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.setGlobalAlert(atoll(argv[2]));
-        sendGenericOkFail(cli, rc);
-        return 0;
-    }
-    if (!strcmp(argv[1], "debugsettetherglobalalert") || !strcmp(argv[1], "dstga")) {
-        if (argc != 4) {
-            sendGenericSyntaxError(cli, "debugsettetherglobalalert <interface0> <interface1>");
-            return 0;
-        }
-        /* We ignore the interfaces for now. */
-        int rc = gCtls->bandwidthCtrl.setGlobalAlertInForwardChain();
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "removeglobalalert") || !strcmp(argv[1], "rga")) {
-        if (argc != 2) {
-            sendGenericSyntaxError(cli, "removeglobalalert");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeGlobalAlert();
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "debugremovetetherglobalalert") || !strcmp(argv[1], "drtga")) {
-        if (argc != 4) {
-            sendGenericSyntaxError(cli, "debugremovetetherglobalalert <interface0> <interface1>");
-            return 0;
-        }
-        /* We ignore the interfaces for now. */
-        int rc = gCtls->bandwidthCtrl.removeGlobalAlertInForwardChain();
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "setsharedalert") || !strcmp(argv[1], "ssa")) {
-        if (argc != 3) {
-            sendGenericSyntaxError(cli, "setsharedalert <bytes>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.setSharedAlert(atoll(argv[2]));
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "removesharedalert") || !strcmp(argv[1], "rsa")) {
-        if (argc != 2) {
-            sendGenericSyntaxError(cli, "removesharedalert");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeSharedAlert();
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "setinterfacealert") || !strcmp(argv[1], "sia")) {
-        if (argc != 4) {
-            sendGenericSyntaxError(cli, "setinterfacealert <interface> <bytes>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.setInterfaceAlert(argv[2], atoll(argv[3]));
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-    if (!strcmp(argv[1], "removeinterfacealert") || !strcmp(argv[1], "ria")) {
-        if (argc != 3) {
-            sendGenericSyntaxError(cli, "removeinterfacealert <interface>");
-            return 0;
-        }
-        int rc = gCtls->bandwidthCtrl.removeInterfaceAlert(argv[2]);
-        sendGenericOkFail(cli, rc);
-        return 0;
-
-    }
-
-    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown bandwidth cmd", false);
-    return 0;
-}
-
-CommandListener::IdletimerControlCmd::IdletimerControlCmd() :
-    NetdCommand("idletimer") {
-}
-
-int CommandListener::IdletimerControlCmd::runCommand(SocketClient *cli, int argc, char **argv) {
-  // TODO(ashish): Change the error statements
-    if (argc < 2) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-        return 0;
-    }
-
-    ALOGV("idletimerctrlcmd: argc=%d %s %s ...", argc, argv[0], argv[1]);
-
-    if (!strcmp(argv[1], "enable")) {
-      if (0 != gCtls->idletimerCtrl.enableIdletimerControl()) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-      } else {
-        cli->sendMsg(ResponseCode::CommandOkay, "Enable success", false);
-      }
-      return 0;
-
-    }
-    if (!strcmp(argv[1], "disable")) {
-      if (0 != gCtls->idletimerCtrl.disableIdletimerControl()) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-      } else {
-        cli->sendMsg(ResponseCode::CommandOkay, "Disable success", false);
-      }
-      return 0;
-    }
-    if (!strcmp(argv[1], "add")) {
-        if (argc != 5) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-            return 0;
-        }
-        if(0 != gCtls->idletimerCtrl.addInterfaceIdletimer(
-                                        argv[2], atoi(argv[3]), argv[4])) {
-          cli->sendMsg(ResponseCode::OperationFailed, "Failed to add interface", false);
-        } else {
-          cli->sendMsg(ResponseCode::CommandOkay,  "Add success", false);
-        }
-        return 0;
-    }
-    if (!strcmp(argv[1], "remove")) {
-        if (argc != 5) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-            return 0;
-        }
-        // ashish: fixme timeout
-        if (0 != gCtls->idletimerCtrl.removeInterfaceIdletimer(
-                                        argv[2], atoi(argv[3]), argv[4])) {
-          cli->sendMsg(ResponseCode::OperationFailed, "Failed to remove interface", false);
-        } else {
-          cli->sendMsg(ResponseCode::CommandOkay, "Remove success", false);
-        }
-        return 0;
-    }
-
-    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown idletimer cmd", false);
-    return 0;
-}
-
-CommandListener::FirewallCmd::FirewallCmd() :
-    NetdCommand("firewall") {
-}
-
-int CommandListener::FirewallCmd::sendGenericOkFail(SocketClient *cli, int cond) {
-    if (!cond) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Firewall command succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Firewall command failed", false);
-    }
-    return 0;
-}
-
-FirewallRule CommandListener::FirewallCmd::parseRule(const char* arg) {
-    if (!strcmp(arg, "allow")) {
-        return ALLOW;
-    } else if (!strcmp(arg, "deny")) {
-        return DENY;
-    } else {
-        ALOGE("failed to parse uid rule (%s)", arg);
-        return ALLOW;
-    }
-}
-
-FirewallType CommandListener::FirewallCmd::parseFirewallType(const char* arg) {
-    if (!strcmp(arg, "whitelist")) {
-        return WHITELIST;
-    } else if (!strcmp(arg, "blacklist")) {
-        return BLACKLIST;
-    } else {
-        ALOGE("failed to parse firewall type (%s)", arg);
-        return BLACKLIST;
-    }
-}
-
-ChildChain CommandListener::FirewallCmd::parseChildChain(const char* arg) {
-    if (!strcmp(arg, "dozable")) {
-        return DOZABLE;
-    } else if (!strcmp(arg, "standby")) {
-        return STANDBY;
-    } else if (!strcmp(arg, "powersave")) {
-        return POWERSAVE;
-    } else if (!strcmp(arg, "none")) {
-        return NONE;
-    } else {
-        ALOGE("failed to parse child firewall chain (%s)", arg);
-        return INVALID_CHAIN;
-    }
-}
-
-int CommandListener::FirewallCmd::runCommand(SocketClient *cli, int argc,
-        char **argv) {
-    if (argc < 2) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
-        return 0;
-    }
-
-    if (!strcmp(argv[1], "enable")) {
-        if (argc != 3) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                        "Usage: firewall enable <whitelist|blacklist>", false);
-            return 0;
-        }
-        FirewallType firewallType = parseFirewallType(argv[2]);
-
-        int res = gCtls->firewallCtrl.enableFirewall(firewallType);
-        return sendGenericOkFail(cli, res);
-    }
-    if (!strcmp(argv[1], "disable")) {
-        int res = gCtls->firewallCtrl.disableFirewall();
-        return sendGenericOkFail(cli, res);
-    }
-    if (!strcmp(argv[1], "is_enabled")) {
-        int res = gCtls->firewallCtrl.isFirewallEnabled();
-        return sendGenericOkFail(cli, res);
-    }
-
-    if (!strcmp(argv[1], "set_interface_rule")) {
-        if (argc != 4) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall set_interface_rule <rmnet0> <allow|deny>", false);
-            return 0;
-        }
-
-        const char* iface = argv[2];
-        FirewallRule rule = parseRule(argv[3]);
-
-        int res = gCtls->firewallCtrl.setInterfaceRule(iface, rule);
-        return sendGenericOkFail(cli, res);
-    }
-
-    if (!strcmp(argv[1], "set_uid_rule")) {
-        if (argc != 5) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall set_uid_rule <dozable|standby|none> <1000> <allow|deny>",
-                         false);
-            return 0;
-        }
-
-        ChildChain childChain = parseChildChain(argv[2]);
-        if (childChain == INVALID_CHAIN) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Invalid chain name. Valid names are: <dozable|standby|none>",
-                         false);
-            return 0;
-        }
-        int uid = atoi(argv[3]);
-        FirewallRule rule = parseRule(argv[4]);
-        int res = gCtls->firewallCtrl.setUidRule(childChain, uid, rule);
-        return sendGenericOkFail(cli, res);
-    }
-
-    if (!strcmp(argv[1], "enable_chain")) {
-        if (argc != 3) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall enable_chain <dozable|standby>",
-                         false);
-            return 0;
-        }
-
-        ChildChain childChain = parseChildChain(argv[2]);
-        int res = gCtls->firewallCtrl.enableChildChains(childChain, true);
-        return sendGenericOkFail(cli, res);
-    }
-
-    if (!strcmp(argv[1], "disable_chain")) {
-        if (argc != 3) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall disable_chain <dozable|standby>",
-                         false);
-            return 0;
-        }
-
-        ChildChain childChain = parseChildChain(argv[2]);
-        int res = gCtls->firewallCtrl.enableChildChains(childChain, false);
-        return sendGenericOkFail(cli, res);
-    }
-
-    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
-    return 0;
-}
-
-CommandListener::ClatdCmd::ClatdCmd() : NetdCommand("clatd") {
-}
-
-int CommandListener::ClatdCmd::runCommand(SocketClient *cli, int argc,
-                                                            char **argv) {
-    int rc = 0;
-    if (argc < 3) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
-        return 0;
-    }
-
-    if (!strcmp(argv[1], "stop")) {
-        rc = gCtls->clatdCtrl.stopClatd(argv[2]);
-    } else if (!strcmp(argv[1], "status")) {
-        char *tmp = NULL;
-        asprintf(&tmp, "Clatd status: %s", (gCtls->clatdCtrl.isClatdStarted(argv[2]) ?
-                                            "started" : "stopped"));
-        cli->sendMsg(ResponseCode::ClatdStatusResult, tmp, false);
-        free(tmp);
-        return 0;
-    } else if (!strcmp(argv[1], "start")) {
-        rc = gCtls->clatdCtrl.startClatd(argv[2]);
-    } else {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown clatd cmd", false);
-        return 0;
-    }
-
-    if (!rc) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Clatd operation succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Clatd operation failed", false);
-    }
-
-    return 0;
-}
-
-CommandListener::StrictCmd::StrictCmd() :
-    NetdCommand("strict") {
-}
-
-int CommandListener::StrictCmd::sendGenericOkFail(SocketClient *cli, int cond) {
-    if (!cond) {
-        cli->sendMsg(ResponseCode::CommandOkay, "Strict command succeeded", false);
-    } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Strict command failed", false);
-    }
-    return 0;
-}
-
-StrictPenalty CommandListener::StrictCmd::parsePenalty(const char* arg) {
-    if (!strcmp(arg, "reject")) {
-        return REJECT;
-    } else if (!strcmp(arg, "log")) {
-        return LOG;
-    } else if (!strcmp(arg, "accept")) {
-        return ACCEPT;
-    } else {
-        return INVALID;
-    }
-}
-
-int CommandListener::StrictCmd::runCommand(SocketClient *cli, int argc,
-        char **argv) {
-    if (argc < 2) {
-        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
-        return 0;
-    }
-
-    if (!strcmp(argv[1], "enable")) {
-        int res = gCtls->strictCtrl.enableStrict();
-        return sendGenericOkFail(cli, res);
-    }
-    if (!strcmp(argv[1], "disable")) {
-        int res = gCtls->strictCtrl.disableStrict();
-        return sendGenericOkFail(cli, res);
-    }
-
-    if (!strcmp(argv[1], "set_uid_cleartext_policy")) {
-        if (argc != 4) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: strict set_uid_cleartext_policy <uid> <accept|log|reject>",
-                         false);
-            return 0;
-        }
-
-        errno = 0;
-        unsigned long int uid = strtoul(argv[2], NULL, 0);
-        if (errno || uid > UID_MAX) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid UID", false);
-            return 0;
-        }
-
-        StrictPenalty penalty = parsePenalty(argv[3]);
-        if (penalty == INVALID) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid penalty argument", false);
-            return 0;
-        }
-
-        int res = gCtls->strictCtrl.setUidCleartextPenalty((uid_t) uid, penalty);
-        return sendGenericOkFail(cli, res);
-    }
-
-    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
-    return 0;
-}
-
-CommandListener::NetworkCommand::NetworkCommand() : NetdCommand("network") {
-}
-
-int CommandListener::NetworkCommand::syntaxError(SocketClient* client, const char* message) {
-    client->sendMsg(ResponseCode::CommandSyntaxError, message, false);
-    return 0;
-}
-
-int CommandListener::NetworkCommand::operationError(SocketClient* client, const char* message,
-                                                    int ret) {
-    errno = -ret;
-    client->sendMsg(ResponseCode::OperationFailed, message, true);
-    return 0;
-}
-
-int CommandListener::NetworkCommand::success(SocketClient* client) {
-    client->sendMsg(ResponseCode::CommandOkay, "success", false);
-    return 0;
-}
-
-int CommandListener::NetworkCommand::runCommand(SocketClient* client, int argc, char** argv) {
-    if (argc < 2) {
-        return syntaxError(client, "Missing argument");
-    }
-
-    //    0      1      2      3      4       5         6            7           8
-    // network route [legacy <uid>]  add   <netId> <interface> <destination> [nexthop]
-    // network route [legacy <uid>] remove <netId> <interface> <destination> [nexthop]
-    //
-    // nexthop may be either an IPv4/IPv6 address or one of "unreachable" or "throw".
-    if (!strcmp(argv[1], "route")) {
-        if (argc < 6 || argc > 9) {
-            return syntaxError(client, "Incorrect number of arguments");
-        }
-
-        int nextArg = 2;
-        bool legacy = false;
-        uid_t uid = 0;
-        if (!strcmp(argv[nextArg], "legacy")) {
-            ++nextArg;
-            legacy = true;
-            uid = strtoul(argv[nextArg++], NULL, 0);
-        }
-
-        bool add = false;
-        if (!strcmp(argv[nextArg], "add")) {
-            add = true;
-        } else if (strcmp(argv[nextArg], "remove")) {
-            return syntaxError(client, "Unknown argument");
-        }
-        ++nextArg;
-
-        if (argc < nextArg + 3 || argc > nextArg + 4) {
-            return syntaxError(client, "Incorrect number of arguments");
-        }
-
-        unsigned netId = stringToNetId(argv[nextArg++]);
-        const char* interface = argv[nextArg++];
-        const char* destination = argv[nextArg++];
-        const char* nexthop = argc > nextArg ? argv[nextArg] : NULL;
-
-        int ret;
-        if (add) {
-            ret = gCtls->netCtrl.addRoute(netId, interface, destination, nexthop, legacy, uid);
-        } else {
-            ret = gCtls->netCtrl.removeRoute(netId, interface, destination, nexthop, legacy, uid);
-        }
-        if (ret) {
-            return operationError(client, add ? "addRoute() failed" : "removeRoute() failed", ret);
-        }
-
-        return success(client);
-    }
-
-    //    0        1       2       3         4
-    // network interface  add   <netId> <interface>
-    // network interface remove <netId> <interface>
-    if (!strcmp(argv[1], "interface")) {
-        if (argc != 5) {
-            return syntaxError(client, "Missing argument");
-        }
-        unsigned netId = stringToNetId(argv[3]);
-        if (!strcmp(argv[2], "add")) {
-            if (int ret = gCtls->netCtrl.addInterfaceToNetwork(netId, argv[4])) {
-                return operationError(client, "addInterfaceToNetwork() failed", ret);
-            }
-        } else if (!strcmp(argv[2], "remove")) {
-            if (int ret = gCtls->netCtrl.removeInterfaceFromNetwork(netId, argv[4])) {
-                return operationError(client, "removeInterfaceFromNetwork() failed", ret);
-            }
-        } else {
-            return syntaxError(client, "Unknown argument");
-        }
-        return success(client);
-    }
-
-    //    0      1       2         3
-    // network create <netId> [permission]
-    //
-    //    0      1       2     3     4        5
-    // network create <netId> vpn <hasDns> <secure>
-    if (!strcmp(argv[1], "create")) {
-        if (argc < 3) {
-            return syntaxError(client, "Missing argument");
-        }
-        unsigned netId = stringToNetId(argv[2]);
-        if (argc == 6 && !strcmp(argv[3], "vpn")) {
-            bool hasDns = atoi(argv[4]);
-            bool secure = atoi(argv[5]);
-            if (int ret = gCtls->netCtrl.createVirtualNetwork(netId, hasDns, secure)) {
-                return operationError(client, "createVirtualNetwork() failed", ret);
-            }
-        } else if (argc > 4) {
-            return syntaxError(client, "Unknown trailing argument(s)");
-        } else {
-            Permission permission = PERMISSION_NONE;
-            if (argc == 4) {
-                permission = stringToPermission(argv[3]);
-                if (permission == PERMISSION_NONE) {
-                    return syntaxError(client, "Unknown permission");
-                }
-            }
-            if (int ret = gCtls->netCtrl.createPhysicalNetwork(netId, permission)) {
-                return operationError(client, "createPhysicalNetwork() failed", ret);
-            }
-        }
-        return success(client);
-    }
-
-    //    0       1       2
-    // network destroy <netId>
-    if (!strcmp(argv[1], "destroy")) {
-        if (argc != 3) {
-            return syntaxError(client, "Incorrect number of arguments");
-        }
-        unsigned netId = stringToNetId(argv[2]);
-        // Both of these functions manage their own locking internally.
-        if (int ret = gCtls->netCtrl.destroyNetwork(netId)) {
-            return operationError(client, "destroyNetwork() failed", ret);
-        }
-        gCtls->resolverCtrl.clearDnsServers(netId);
-        return success(client);
-    }
-
-    //    0       1      2      3
-    // network default  set  <netId>
-    // network default clear
-    if (!strcmp(argv[1], "default")) {
-        if (argc < 3) {
-            return syntaxError(client, "Missing argument");
-        }
-        unsigned netId = NETID_UNSET;
-        if (!strcmp(argv[2], "set")) {
-            if (argc < 4) {
-                return syntaxError(client, "Missing netId");
-            }
-            netId = stringToNetId(argv[3]);
-        } else if (strcmp(argv[2], "clear")) {
-            return syntaxError(client, "Unknown argument");
-        }
-        if (int ret = gCtls->netCtrl.setDefaultNetwork(netId)) {
-            return operationError(client, "setDefaultNetwork() failed", ret);
-        }
-        return success(client);
-    }
-
-    //    0        1         2      3        4          5
-    // network permission   user   set  <permission>  <uid> ...
-    // network permission   user  clear    <uid> ...
-    // network permission network  set  <permission> <netId> ...
-    // network permission network clear   <netId> ...
-    if (!strcmp(argv[1], "permission")) {
-        if (argc < 5) {
-            return syntaxError(client, "Missing argument");
-        }
-        int nextArg = 4;
-        Permission permission = PERMISSION_NONE;
-        if (!strcmp(argv[3], "set")) {
-            permission = stringToPermission(argv[4]);
-            if (permission == PERMISSION_NONE) {
-                return syntaxError(client, "Unknown permission");
-            }
-            nextArg = 5;
-        } else if (strcmp(argv[3], "clear")) {
-            return syntaxError(client, "Unknown argument");
-        }
-        if (nextArg == argc) {
-            return syntaxError(client, "Missing id");
-        }
-
-        bool userPermissions = !strcmp(argv[2], "user");
-        bool networkPermissions = !strcmp(argv[2], "network");
-        if (!userPermissions && !networkPermissions) {
-            return syntaxError(client, "Unknown argument");
-        }
-
-        std::vector<unsigned> ids;
-        for (; nextArg < argc; ++nextArg) {
-            if (userPermissions) {
-                char* endPtr;
-                unsigned id = strtoul(argv[nextArg], &endPtr, 0);
-                if (!*argv[nextArg] || *endPtr) {
-                    return syntaxError(client, "Invalid id");
-                }
-                ids.push_back(id);
-            } else {
-                // networkPermissions
-                ids.push_back(stringToNetId(argv[nextArg]));
-            }
-        }
-        if (userPermissions) {
-            gCtls->netCtrl.setPermissionForUsers(permission, ids);
-        } else {
-            // networkPermissions
-            if (int ret = gCtls->netCtrl.setPermissionForNetworks(permission, ids)) {
-                return operationError(client, "setPermissionForNetworks() failed", ret);
-            }
-        }
-
-        return success(client);
-    }
-
-    //    0      1     2       3           4
-    // network users  add   <netId> [<uid>[-<uid>]] ...
-    // network users remove <netId> [<uid>[-<uid>]] ...
-    if (!strcmp(argv[1], "users")) {
-        if (argc < 4) {
-            return syntaxError(client, "Missing argument");
-        }
-        unsigned netId = stringToNetId(argv[3]);
-        UidRanges uidRanges;
-        if (!uidRanges.parseFrom(argc - 4, argv + 4)) {
-            return syntaxError(client, "Invalid UIDs");
-        }
-        if (!strcmp(argv[2], "add")) {
-            if (int ret = gCtls->netCtrl.addUsersToNetwork(netId, uidRanges)) {
-                return operationError(client, "addUsersToNetwork() failed", ret);
-            }
-        } else if (!strcmp(argv[2], "remove")) {
-            if (int ret = gCtls->netCtrl.removeUsersFromNetwork(netId, uidRanges)) {
-                return operationError(client, "removeUsersFromNetwork() failed", ret);
-            }
-        } else {
-            return syntaxError(client, "Unknown argument");
-        }
-        return success(client);
-    }
-
-    //    0       1      2     3
-    // network protect allow <uid> ...
-    // network protect  deny <uid> ...
-    if (!strcmp(argv[1], "protect")) {
-        if (argc < 4) {
-            return syntaxError(client, "Missing argument");
-        }
-        std::vector<uid_t> uids;
-        for (int i = 3; i < argc; ++i) {
-            uids.push_back(strtoul(argv[i], NULL, 0));
-        }
-        if (!strcmp(argv[2], "allow")) {
-            gCtls->netCtrl.allowProtect(uids);
-        } else if (!strcmp(argv[2], "deny")) {
-            gCtls->netCtrl.denyProtect(uids);
-        } else {
-            return syntaxError(client, "Unknown argument");
-        }
-        return success(client);
-    }
-
-    return syntaxError(client, "Unknown argument");
-}
-
-}  // namespace net
-}  // namespace android
diff --git a/server/CommandListener.h b/server/CommandListener.h
deleted file mode 100644
index 95e5830..0000000
--- a/server/CommandListener.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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 _COMMANDLISTENER_H__
-#define _COMMANDLISTENER_H__
-
-#include <sysutils/FrameworkListener.h>
-#include "utils/RWLock.h"
-
-#include "NetdCommand.h"
-#include "NetdConstants.h"
-#include "NetworkController.h"
-#include "TetherController.h"
-#include "PppController.h"
-#include "BandwidthController.h"
-#include "IdletimerController.h"
-#include "InterfaceController.h"
-#include "ResolverController.h"
-#include "FirewallController.h"
-#include "ClatdController.h"
-#include "StrictController.h"
-
-namespace android {
-namespace net {
-
-class CommandListener : public FrameworkListener {
-public:
-    CommandListener();
-    virtual ~CommandListener() {}
-
-    static constexpr const char* SOCKET_NAME = "netd";
-
-private:
-    void registerLockingCmd(FrameworkCommand *cmd, android::RWLock& lock);
-    void registerLockingCmd(FrameworkCommand *cmd) {
-        registerLockingCmd(cmd, android::net::gBigNetdLock);
-    }
-
-    class InterfaceCmd : public NetdCommand {
-    public:
-        InterfaceCmd();
-        virtual ~InterfaceCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class IpFwdCmd : public NetdCommand {
-    public:
-        IpFwdCmd();
-        virtual ~IpFwdCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class TetherCmd : public NetdCommand {
-    public:
-        TetherCmd();
-        virtual ~TetherCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class NatCmd : public NetdCommand {
-    public:
-        NatCmd();
-        virtual ~NatCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class ListTtysCmd : public NetdCommand {
-    public:
-        ListTtysCmd();
-        virtual ~ListTtysCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class PppdCmd : public NetdCommand {
-    public:
-        PppdCmd();
-        virtual ~PppdCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class BandwidthControlCmd : public NetdCommand {
-    public:
-        BandwidthControlCmd();
-        virtual ~BandwidthControlCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    protected:
-        void sendGenericOkFail(SocketClient *cli, int cond);
-        void sendGenericOpFailed(SocketClient *cli, const char *errMsg);
-        void sendGenericSyntaxError(SocketClient *cli, const char *usageMsg);
-    };
-
-    class IdletimerControlCmd : public NetdCommand {
-    public:
-        IdletimerControlCmd();
-        virtual ~IdletimerControlCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class ResolverCmd : public NetdCommand {
-    public:
-        ResolverCmd();
-        virtual ~ResolverCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-
-    private:
-        bool parseAndExecuteSetNetDns(int netId, int argc, const char** argv);
-    };
-
-    class FirewallCmd: public NetdCommand {
-    public:
-        FirewallCmd();
-        virtual ~FirewallCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    protected:
-        int sendGenericOkFail(SocketClient *cli, int cond);
-        static FirewallRule parseRule(const char* arg);
-        static FirewallType parseFirewallType(const char* arg);
-        static ChildChain parseChildChain(const char* arg);
-    };
-
-    class ClatdCmd : public NetdCommand {
-    public:
-        ClatdCmd();
-        virtual ~ClatdCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
-    class StrictCmd : public NetdCommand {
-    public:
-        StrictCmd();
-        virtual ~StrictCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    protected:
-        int sendGenericOkFail(SocketClient *cli, int cond);
-        static StrictPenalty parsePenalty(const char* arg);
-    };
-
-    class NetworkCommand : public NetdCommand {
-    public:
-        NetworkCommand();
-        virtual ~NetworkCommand() {}
-        int runCommand(SocketClient* client, int argc, char** argv);
-    private:
-        int syntaxError(SocketClient* cli, const char* message);
-        int operationError(SocketClient* cli, const char* message, int ret);
-        int success(SocketClient* cli);
-    };
-};
-
-}  // namespace net
-}  // namespace android
-
-#endif
diff --git a/server/Controllers.cpp b/server/Controllers.cpp
index 221a20a..c941a80 100644
--- a/server/Controllers.cpp
+++ b/server/Controllers.cpp
@@ -18,30 +18,33 @@
 #include <set>
 #include <string>
 
-#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <netdutils/Stopwatch.h>
 
 #define LOG_TAG "Netd"
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include "Controllers.h"
 #include "IdletimerController.h"
 #include "NetworkController.h"
 #include "RouteController.h"
-#include "Stopwatch.h"
-#include "oem_iptables_hook.h"
 #include "XfrmController.h"
+#include "oem_iptables_hook.h"
 
 namespace android {
 namespace net {
 
-using android::base::Join;
-using android::base::StringPrintf;
 using android::base::StringAppendF;
+using android::base::StringPrintf;
+using android::netdutils::Stopwatch;
 
 auto Controllers::execIptablesRestore  = ::execIptablesRestore;
 auto Controllers::execIptablesRestoreWithOutput = ::execIptablesRestoreWithOutput;
 
+netdutils::Log gLog("netd");
+netdutils::Log gUnsolicitedLog("netdUnsolicited");
+
 namespace {
 
 /**
@@ -70,6 +73,7 @@
 };
 
 static const std::vector<const char*> RAW_PREROUTING = {
+        ClatdController::LOCAL_RAW_PREROUTING,
         BandwidthController::LOCAL_RAW_PREROUTING,
         IdletimerController::LOCAL_RAW_PREROUTING,
         TetherController::LOCAL_RAW_PREROUTING,
@@ -189,20 +193,20 @@
 Controllers::Controllers()
     : clatdCtrl(&netCtrl),
       wakeupCtrl(
-          [this](const WakeupController::ReportArgs& args) {
-              const auto listener = eventReporter.getNetdEventListener();
-              if (listener == nullptr) {
-                  ALOGE("getNetdEventListener() returned nullptr. dropping wakeup event");
-                  return;
-              }
-              String16 prefix = String16(args.prefix.c_str());
-              String16 srcIp = String16(args.srcIp.c_str());
-              String16 dstIp = String16(args.dstIp.c_str());
-              listener->onWakeupEvent(prefix, args.uid, args.ethertype, args.ipNextHeader,
-                                      args.dstHw, srcIp, dstIp, args.srcPort, args.dstPort,
-                                      args.timestampNs);
-          },
-          &iptablesRestoreCtrl) {
+              [this](const WakeupController::ReportArgs& args) {
+                  const auto listener = eventReporter.getNetdEventListener();
+                  if (listener == nullptr) {
+                      gLog.error("getNetdEventListener() returned nullptr. dropping wakeup event");
+                      return;
+                  }
+                  String16 prefix = String16(args.prefix.c_str());
+                  String16 srcIp = String16(args.srcIp.c_str());
+                  String16 dstIp = String16(args.dstIp.c_str());
+                  listener->onWakeupEvent(prefix, args.uid, args.ethertype, args.ipNextHeader,
+                                          args.dstHw, srcIp, dstIp, args.srcPort, args.dstPort,
+                                          args.timestampNs);
+              },
+              &iptablesRestoreCtrl) {
     InterfaceController::initializeAll();
 }
 
@@ -235,57 +239,68 @@
 void Controllers::initIptablesRules() {
     Stopwatch s;
     initChildChains();
-    ALOGI("Creating child chains: %.1fms", s.getTimeAndReset());
+    gLog.info("Creating child chains: %.1fms", s.getTimeAndReset());
 
     // Let each module setup their child chains
     setupOemIptablesHook();
-    ALOGI("Setting up OEM hooks: %.1fms", s.getTimeAndReset());
+    gLog.info("Setting up OEM hooks: %.1fms", s.getTimeAndReset());
 
     /* When enabled, DROPs all packets except those matching rules. */
     firewallCtrl.setupIptablesHooks();
-    ALOGI("Setting up FirewallController hooks: %.1fms", s.getTimeAndReset());
+    gLog.info("Setting up FirewallController hooks: %.1fms", s.getTimeAndReset());
 
     /* Does DROPs in FORWARD by default */
     tetherCtrl.setupIptablesHooks();
-    ALOGI("Setting up TetherController hooks: %.1fms", s.getTimeAndReset());
+    gLog.info("Setting up TetherController hooks: %.1fms", s.getTimeAndReset());
 
     /*
      * Does REJECT in INPUT, OUTPUT. Does counting also.
      * No DROP/REJECT allowed later in netfilter-flow hook order.
      */
     bandwidthCtrl.setupIptablesHooks();
-    ALOGI("Setting up BandwidthController hooks: %.1fms", s.getTimeAndReset());
+    gLog.info("Setting up BandwidthController hooks: %.1fms", s.getTimeAndReset());
 
     /*
      * Counts in nat: PREROUTING, POSTROUTING.
      * No DROP/REJECT allowed later in netfilter-flow hook order.
      */
     idletimerCtrl.setupIptablesHooks();
-    ALOGI("Setting up IdletimerController hooks: %.1fms", s.getTimeAndReset());
+    gLog.info("Setting up IdletimerController hooks: %.1fms", s.getTimeAndReset());
+
+    /*
+     * Add rules for detecting IPv6/IPv4 TCP/UDP connections with TLS/DTLS header
+     */
+    strictCtrl.setupIptablesHooks();
+    gLog.info("Setting up StrictController hooks: %.1fms", s.getTimeAndReset());
 }
 
 void Controllers::init() {
     initIptablesRules();
     Stopwatch s;
+
+    clatdCtrl.init();
+    gLog.info("Initializing ClatdController: %.1fms", s.getTimeAndReset());
+
     netdutils::Status tcStatus = trafficCtrl.start();
     if (!isOk(tcStatus)) {
-        ALOGE("failed to start trafficcontroller: (%s)", toString(tcStatus).c_str());
+        gLog.error("Failed to start trafficcontroller: (%s)", toString(tcStatus).c_str());
     }
-    ALOGI("initializing traffic control: %.1fms", s.getTimeAndReset());
+    gLog.info("Initializing traffic control: %.1fms", s.getTimeAndReset());
 
-    bandwidthCtrl.enableBandwidthControl(false);
-    ALOGI("Disabling bandwidth control: %.1fms", s.getTimeAndReset());
+    bandwidthCtrl.setBpfEnabled(trafficCtrl.getBpfLevel() != android::bpf::BpfLevel::NONE);
+    bandwidthCtrl.enableBandwidthControl();
+    gLog.info("Enabling bandwidth control: %.1fms", s.getTimeAndReset());
 
     if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) {
-        ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
+        gLog.error("Failed to initialize RouteController (%s)", strerror(-ret));
     }
-    ALOGI("Initializing RouteController: %.1fms", s.getTimeAndReset());
+    gLog.info("Initializing RouteController: %.1fms", s.getTimeAndReset());
 
     netdutils::Status xStatus = XfrmController::Init();
     if (!isOk(xStatus)) {
-        ALOGE("Failed to initialize XfrmController (%s)", netdutils::toString(xStatus).c_str());
+        gLog.error("Failed to initialize XfrmController (%s)", netdutils::toString(xStatus).c_str());
     };
-    ALOGI("Initializing XfrmController: %.1fms", s.getTimeAndReset());
+    gLog.info("Initializing XfrmController: %.1fms", s.getTimeAndReset());
 }
 
 Controllers* gCtls = nullptr;
diff --git a/server/Controllers.h b/server/Controllers.h
index 38ffade..fbd9bf1 100644
--- a/server/Controllers.h
+++ b/server/Controllers.h
@@ -17,8 +17,6 @@
 #ifndef _CONTROLLERS_H__
 #define _CONTROLLERS_H__
 
-#include <sysutils/FrameworkListener.h>
-
 #include "BandwidthController.h"
 #include "ClatdController.h"
 #include "EventReporter.h"
@@ -28,19 +26,19 @@
 #include "IptablesRestoreController.h"
 #include "NetworkController.h"
 #include "PppController.h"
-#include "ResolverController.h"
 #include "StrictController.h"
+#include "TcpSocketMonitor.h"
 #include "TetherController.h"
 #include "TrafficController.h"
 #include "WakeupController.h"
 #include "XfrmController.h"
-#include "TcpSocketMonitor.h"
+#include "netdutils/Log.h"
 
 namespace android {
 namespace net {
 
 class Controllers {
-public:
+  public:
     Controllers();
 
     NetworkController netCtrl;
@@ -48,7 +46,6 @@
     PppController pppCtrl;
     BandwidthController bandwidthCtrl;
     IdletimerController idletimerCtrl;
-    ResolverController resolverCtrl;
     FirewallController firewallCtrl;
     ClatdController clatdCtrl;
     StrictController strictCtrl;
@@ -61,7 +58,7 @@
 
     void init();
 
-private:
+  private:
     friend class ControllersTest;
     void initIptablesRules();
     static void initChildChains();
@@ -74,6 +71,8 @@
     static int (*execIptablesRestoreWithOutput)(IptablesTarget, const std::string&, std::string *);
 };
 
+extern netdutils::Log gLog;
+extern netdutils::Log gUnsolicitedLog;
 extern Controllers* gCtls;
 
 }  // namespace net
diff --git a/server/ControllersTest.cpp b/server/ControllersTest.cpp
index 0c56594..ebaa38f 100644
--- a/server/ControllersTest.cpp
+++ b/server/ControllersTest.cpp
@@ -70,121 +70,127 @@
 TEST_F(ControllersTest, TestInitIptablesRules) {
     // Test what happens when we boot and there are no rules.
     ExpectedIptablesCommands expected = {
-        { V4V6, "*filter\n"
-                ":INPUT -\n"
-                "-F INPUT\n"
-                ":bw_INPUT -\n"
-                "-A INPUT -j bw_INPUT\n"
-                ":fw_INPUT -\n"
-                "-A INPUT -j fw_INPUT\n"
-                "COMMIT\n"
-        },
-        { V4V6, "*filter\n"
-                ":FORWARD -\n"
-                "-F FORWARD\n"
-                ":oem_fwd -\n"
-                "-A FORWARD -j oem_fwd\n"
-                ":fw_FORWARD -\n"
-                "-A FORWARD -j fw_FORWARD\n"
-                ":bw_FORWARD -\n"
-                "-A FORWARD -j bw_FORWARD\n"
-                ":tetherctrl_FORWARD -\n"
-                "-A FORWARD -j tetherctrl_FORWARD\n"
-                "COMMIT\n"
-        },
-        { V4V6, "*raw\n"
-                ":PREROUTING -\n"
-                "-F PREROUTING\n"
-                ":bw_raw_PREROUTING -\n"
-                "-A PREROUTING -j bw_raw_PREROUTING\n"
-                ":idletimer_raw_PREROUTING -\n"
-                "-A PREROUTING -j idletimer_raw_PREROUTING\n"
-                ":tetherctrl_raw_PREROUTING -\n"
-                "-A PREROUTING -j tetherctrl_raw_PREROUTING\n"
-                "COMMIT\n"
-        },
-        { V4V6, "*mangle\n"
-                ":FORWARD -\n"
-                "-F FORWARD\n"
-                ":tetherctrl_mangle_FORWARD -\n"
-                "-A FORWARD -j tetherctrl_mangle_FORWARD\n"
-                "COMMIT\n"
-        },
-        { V4V6, "*mangle\n"
-                ":INPUT -\n"
-                "-F INPUT\n"
-                ":wakeupctrl_mangle_INPUT -\n"
-                "-A INPUT -j wakeupctrl_mangle_INPUT\n"
-                ":routectrl_mangle_INPUT -\n"
-                "-A INPUT -j routectrl_mangle_INPUT\n"
-                "COMMIT\n"
-        },
-        { V4,   "*nat\n"
-                ":PREROUTING -\n"
-                "-F PREROUTING\n"
-                ":oem_nat_pre -\n"
-                "-A PREROUTING -j oem_nat_pre\n"
-                "COMMIT\n"
-        },
-        { V4,   "*nat\n"
-                ":POSTROUTING -\n"
-                "-F POSTROUTING\n"
-                ":tetherctrl_nat_POSTROUTING -\n"
-                "-A POSTROUTING -j tetherctrl_nat_POSTROUTING\n"
-                "COMMIT\n"
-        },
-        { V4, "*filter\n"
-              "-S OUTPUT\n"
-              "COMMIT\n" },
-        { V4, "*filter\n"
-              ":oem_out -\n"
-              "-A OUTPUT -j oem_out\n"
-              ":fw_OUTPUT -\n"
-              "-A OUTPUT -j fw_OUTPUT\n"
-              ":st_OUTPUT -\n"
-              "-A OUTPUT -j st_OUTPUT\n"
-              ":bw_OUTPUT -\n"
-              "-A OUTPUT -j bw_OUTPUT\n"
-              "COMMIT\n"
-        },
-        { V6, "*filter\n"
-              "-S OUTPUT\n"
-              "COMMIT\n" },
-        { V6, "*filter\n"
-              ":oem_out -\n"
-              "-A OUTPUT -j oem_out\n"
-              ":fw_OUTPUT -\n"
-              "-A OUTPUT -j fw_OUTPUT\n"
-              ":st_OUTPUT -\n"
-              "-A OUTPUT -j st_OUTPUT\n"
-              ":bw_OUTPUT -\n"
-              "-A OUTPUT -j bw_OUTPUT\n"
-              "COMMIT\n"
-        },
-        { V4, "*mangle\n"
-              "-S POSTROUTING\n"
-              "COMMIT\n" },
-        { V4, "*mangle\n"
-              ":oem_mangle_post -\n"
-              "-A POSTROUTING -j oem_mangle_post\n"
-              ":bw_mangle_POSTROUTING -\n"
-              "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
-              ":idletimer_mangle_POSTROUTING -\n"
-              "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
-              "COMMIT\n"
-        },
-        { V6, "*mangle\n"
-              "-S POSTROUTING\n"
-              "COMMIT\n" },
-        { V6, "*mangle\n"
-              ":oem_mangle_post -\n"
-              "-A POSTROUTING -j oem_mangle_post\n"
-              ":bw_mangle_POSTROUTING -\n"
-              "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
-              ":idletimer_mangle_POSTROUTING -\n"
-              "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
-              "COMMIT\n"
-        },
+            {V4V6,
+             "*filter\n"
+             ":INPUT -\n"
+             "-F INPUT\n"
+             ":bw_INPUT -\n"
+             "-A INPUT -j bw_INPUT\n"
+             ":fw_INPUT -\n"
+             "-A INPUT -j fw_INPUT\n"
+             "COMMIT\n"},
+            {V4V6,
+             "*filter\n"
+             ":FORWARD -\n"
+             "-F FORWARD\n"
+             ":oem_fwd -\n"
+             "-A FORWARD -j oem_fwd\n"
+             ":fw_FORWARD -\n"
+             "-A FORWARD -j fw_FORWARD\n"
+             ":bw_FORWARD -\n"
+             "-A FORWARD -j bw_FORWARD\n"
+             ":tetherctrl_FORWARD -\n"
+             "-A FORWARD -j tetherctrl_FORWARD\n"
+             "COMMIT\n"},
+            {V4V6,
+             "*raw\n"
+             ":PREROUTING -\n"
+             "-F PREROUTING\n"
+             ":clat_raw_PREROUTING -\n"
+             "-A PREROUTING -j clat_raw_PREROUTING\n"
+             ":bw_raw_PREROUTING -\n"
+             "-A PREROUTING -j bw_raw_PREROUTING\n"
+             ":idletimer_raw_PREROUTING -\n"
+             "-A PREROUTING -j idletimer_raw_PREROUTING\n"
+             ":tetherctrl_raw_PREROUTING -\n"
+             "-A PREROUTING -j tetherctrl_raw_PREROUTING\n"
+             "COMMIT\n"},
+            {V4V6,
+             "*mangle\n"
+             ":FORWARD -\n"
+             "-F FORWARD\n"
+             ":tetherctrl_mangle_FORWARD -\n"
+             "-A FORWARD -j tetherctrl_mangle_FORWARD\n"
+             "COMMIT\n"},
+            {V4V6,
+             "*mangle\n"
+             ":INPUT -\n"
+             "-F INPUT\n"
+             ":wakeupctrl_mangle_INPUT -\n"
+             "-A INPUT -j wakeupctrl_mangle_INPUT\n"
+             ":routectrl_mangle_INPUT -\n"
+             "-A INPUT -j routectrl_mangle_INPUT\n"
+             "COMMIT\n"},
+            {V4,
+             "*nat\n"
+             ":PREROUTING -\n"
+             "-F PREROUTING\n"
+             ":oem_nat_pre -\n"
+             "-A PREROUTING -j oem_nat_pre\n"
+             "COMMIT\n"},
+            {V4,
+             "*nat\n"
+             ":POSTROUTING -\n"
+             "-F POSTROUTING\n"
+             ":tetherctrl_nat_POSTROUTING -\n"
+             "-A POSTROUTING -j tetherctrl_nat_POSTROUTING\n"
+             "COMMIT\n"},
+            {V4,
+             "*filter\n"
+             "-S OUTPUT\n"
+             "COMMIT\n"},
+            {V4,
+             "*filter\n"
+             ":oem_out -\n"
+             "-A OUTPUT -j oem_out\n"
+             ":fw_OUTPUT -\n"
+             "-A OUTPUT -j fw_OUTPUT\n"
+             ":st_OUTPUT -\n"
+             "-A OUTPUT -j st_OUTPUT\n"
+             ":bw_OUTPUT -\n"
+             "-A OUTPUT -j bw_OUTPUT\n"
+             "COMMIT\n"},
+            {V6,
+             "*filter\n"
+             "-S OUTPUT\n"
+             "COMMIT\n"},
+            {V6,
+             "*filter\n"
+             ":oem_out -\n"
+             "-A OUTPUT -j oem_out\n"
+             ":fw_OUTPUT -\n"
+             "-A OUTPUT -j fw_OUTPUT\n"
+             ":st_OUTPUT -\n"
+             "-A OUTPUT -j st_OUTPUT\n"
+             ":bw_OUTPUT -\n"
+             "-A OUTPUT -j bw_OUTPUT\n"
+             "COMMIT\n"},
+            {V4,
+             "*mangle\n"
+             "-S POSTROUTING\n"
+             "COMMIT\n"},
+            {V4,
+             "*mangle\n"
+             ":oem_mangle_post -\n"
+             "-A POSTROUTING -j oem_mangle_post\n"
+             ":bw_mangle_POSTROUTING -\n"
+             "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
+             ":idletimer_mangle_POSTROUTING -\n"
+             "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
+             "COMMIT\n"},
+            {V6,
+             "*mangle\n"
+             "-S POSTROUTING\n"
+             "COMMIT\n"},
+            {V6,
+             "*mangle\n"
+             ":oem_mangle_post -\n"
+             "-A POSTROUTING -j oem_mangle_post\n"
+             ":bw_mangle_POSTROUTING -\n"
+             "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
+             ":idletimer_mangle_POSTROUTING -\n"
+             "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
+             "COMMIT\n"},
     };
 
     // Check that we run these commands and these only.
diff --git a/server/DnsProxyListener.cpp b/server/DnsProxyListener.cpp
deleted file mode 100644
index 9babac2..0000000
--- a/server/DnsProxyListener.cpp
+++ /dev/null
@@ -1,767 +0,0 @@
-/*
- * Copyright (C) 2010 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 <dirent.h>
-#include <errno.h>
-#include <linux/if.h>
-#include <math.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <string.h>
-#include <pthread.h>
-#include <resolv_netid.h>
-#include <net/if.h>
-
-#define LOG_TAG "DnsProxyListener"
-#define DBG 0
-#define VDBG 0
-
-#include <algorithm>
-#include <chrono>
-#include <list>
-#include <vector>
-
-#include <cutils/log.h>
-#include <cutils/misc.h>
-#include <netdutils/Slice.h>
-#include <netdutils/OperationLimiter.h>
-#include <utils/String16.h>
-#include <sysutils/SocketClient.h>
-
-#include <binder/IServiceManager.h>
-
-#include "Controllers.h"
-#include "Fwmark.h"
-#include "DnsProxyListener.h"
-#include "dns/DnsTlsDispatcher.h"
-#include "dns/DnsTlsTransport.h"
-#include "dns/DnsTlsServer.h"
-#include "NetdClient.h"
-#include "NetdConstants.h"
-#include "NetworkController.h"
-#include "ResponseCode.h"
-#include "Stopwatch.h"
-#include "thread_util.h"
-#include "android/net/metrics/INetdEventListener.h"
-
-using android::String16;
-using android::net::metrics::INetdEventListener;
-
-namespace android {
-namespace net {
-
-namespace {
-
-// TODO: move to a separate file (with other constants from FwmarkService and NetdNativeService)
-constexpr const char CONNECTIVITY_USE_RESTRICTED_NETWORKS[] =
-    "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
-constexpr const char NETWORK_BYPASS_PRIVATE_DNS[] =
-    "android.permission.NETWORK_BYPASS_PRIVATE_DNS";
-
-// Limits the number of outstanding DNS queries by client UID.
-constexpr int MAX_QUERIES_PER_UID = 256;
-android::netdutils::OperationLimiter<uid_t> queryLimiter(MAX_QUERIES_PER_UID);
-
-void logArguments(int argc, char** argv) {
-    for (int i = 0; i < argc; i++) {
-        ALOGD("argv[%i]=%s", i, argv[i]);
-    }
-}
-
-template<typename T>
-void tryThreadOrError(SocketClient* cli, T* handler) {
-    cli->incRef();
-
-    const int rval = threadLaunch(handler);
-    if (rval == 0) {
-        // SocketClient decRef() happens in the handler's run() method.
-        return;
-    }
-
-    char* msg = NULL;
-    asprintf(&msg, "%s (%d)", strerror(-rval), -rval);
-    cli->sendMsg(ResponseCode::OperationFailed, msg, false);
-    free(msg);
-
-    delete handler;
-    cli->decRef();
-}
-
-bool checkAndClearUseLocalNameserversFlag(unsigned* netid) {
-    if (netid == nullptr || ((*netid) & NETID_USE_LOCAL_NAMESERVERS) == 0) {
-        return false;
-    }
-    *netid = (*netid) & ~NETID_USE_LOCAL_NAMESERVERS;
-    return true;
-}
-
-thread_local android_net_context thread_netcontext = {};
-
-DnsTlsDispatcher dnsTlsDispatcher;
-
-void catnap() {
-    using namespace std::chrono_literals;
-    std::this_thread::sleep_for(100ms);
-}
-
-res_sendhookact qhook(sockaddr* const * /*ns*/, const u_char** buf, int* buflen,
-                      u_char* ans, int anssiz, int* resplen) {
-    if (!thread_netcontext.qhook) {
-        ALOGE("qhook abort: thread qhook is null");
-        return res_goahead;
-    }
-    if (thread_netcontext.flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
-        return res_goahead;
-    }
-    if (!net::gCtls) {
-        ALOGE("qhook abort: gCtls is null");
-        return res_goahead;
-    }
-
-    const auto privateDnsStatus =
-            net::gCtls->resolverCtrl.getPrivateDnsStatus(thread_netcontext.dns_netid);
-
-    if (privateDnsStatus.mode == PrivateDnsMode::OFF) return res_goahead;
-
-    if (privateDnsStatus.validatedServers.empty()) {
-        if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
-            return res_goahead;
-        } else {
-            // Sleep and iterate some small number of times checking for the
-            // arrival of resolved and validated server IP addresses, instead
-            // of returning an immediate error.
-            catnap();
-            return res_modified;
-        }
-    }
-
-    if (DBG) ALOGD("Performing query over TLS");
-
-    Slice query = netdutils::Slice(const_cast<u_char*>(*buf), *buflen);
-    Slice answer = netdutils::Slice(const_cast<u_char*>(ans), anssiz);
-    const auto response = dnsTlsDispatcher.query(
-            privateDnsStatus.validatedServers, thread_netcontext.dns_mark,
-            query, answer, resplen);
-    if (response == DnsTlsTransport::Response::success) {
-        if (DBG) ALOGD("qhook success");
-        return res_done;
-    }
-
-    if (DBG) {
-        ALOGW("qhook abort: TLS query failed: %d", (int)response);
-    }
-
-    if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
-        // In opportunistic mode, handle falling back to cleartext in some
-        // cases (DNS shouldn't fail if a validated opportunistic mode server
-        // becomes unreachable for some reason).
-        switch (response) {
-            case DnsTlsTransport::Response::network_error:
-            case DnsTlsTransport::Response::internal_error:
-                // Note: this will cause cleartext queries to be emitted, with
-                // all of the EDNS0 goodness enabled. Fingers crossed.  :-/
-                return res_goahead;
-            default:
-                break;
-        }
-    }
-
-    // There was an internal error.  Fail hard.
-    return res_error;
-}
-
-constexpr bool requestingUseLocalNameservers(unsigned flags) {
-    return (flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) != 0;
-}
-
-inline bool queryingViaTls(unsigned dns_netid) {
-    const auto privateDnsStatus = net::gCtls->resolverCtrl.getPrivateDnsStatus(dns_netid);
-    switch (privateDnsStatus.mode) {
-        case PrivateDnsMode::OPPORTUNISTIC:
-           return !privateDnsStatus.validatedServers.empty();
-        case PrivateDnsMode::STRICT:
-            return true;
-        default:
-            return false;
-    }
-}
-
-bool hasPermissionToBypassPrivateDns(uid_t uid) {
-    static_assert(AID_SYSTEM >= 0 && AID_SYSTEM < FIRST_APPLICATION_UID,
-        "Calls from AID_SYSTEM must not result in a permission check to avoid deadlock.");
-    if (uid >= 0 && uid < FIRST_APPLICATION_UID) {
-        return true;
-    }
-
-    for (auto& permission : {CONNECTIVITY_USE_RESTRICTED_NETWORKS, NETWORK_BYPASS_PRIVATE_DNS}) {
-        if (checkCallingPermission(String16(permission))) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void maybeFixupNetContext(android_net_context* ctx) {
-    if (requestingUseLocalNameservers(ctx->flags) && !hasPermissionToBypassPrivateDns(ctx->uid)) {
-        // Not permitted; clear the flag.
-        ctx->flags &= ~NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
-    }
-
-    if (!requestingUseLocalNameservers(ctx->flags)) {
-        // If we're not explicitly bypassing DNS-over-TLS servers, check whether
-        // DNS-over-TLS is in use as an indicator for when to use more modern
-        // DNS resolution mechanics.
-        if (queryingViaTls(ctx->dns_netid)) {
-            ctx->flags |= NET_CONTEXT_FLAG_USE_EDNS;
-        }
-    }
-
-    // Always set the qhook. An opportunistic mode server might have finished
-    // validating by the time the qhook runs. Note that this races with the
-    // queryingViaTls() check above, resulting in possibly sending queries over
-    // TLS without taking advantage of features like EDNS; c'est la guerre.
-    ctx->qhook = &qhook;
-
-    // Store the android_net_context instance in a thread_local variable
-    // so that the static qhook can access other fields of the struct.
-    thread_netcontext = *ctx;
-}
-
-}  // namespace
-
-DnsProxyListener::DnsProxyListener(const NetworkController* netCtrl, EventReporter* eventReporter) :
-        FrameworkListener(SOCKET_NAME), mNetCtrl(netCtrl), mEventReporter(eventReporter) {
-    registerCmd(new GetAddrInfoCmd(this));
-    registerCmd(new GetHostByAddrCmd(this));
-    registerCmd(new GetHostByNameCmd(this));
-}
-
-DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(
-        SocketClient *c, char* host, char* service, struct addrinfo* hints,
-        const android_net_context& netcontext, const int reportingLevel,
-        const android::sp<android::net::metrics::INetdEventListener>& netdEventListener)
-        : mClient(c),
-          mHost(host),
-          mService(service),
-          mHints(hints),
-          mNetContext(netcontext),
-          mReportingLevel(reportingLevel),
-          mNetdEventListener(netdEventListener) {
-}
-
-DnsProxyListener::GetAddrInfoHandler::~GetAddrInfoHandler() {
-    free(mHost);
-    free(mService);
-    free(mHints);
-}
-
-static bool sendBE32(SocketClient* c, uint32_t data) {
-    uint32_t be_data = htonl(data);
-    return c->sendData(&be_data, sizeof(be_data)) == 0;
-}
-
-// Sends 4 bytes of big-endian length, followed by the data.
-// Returns true on success.
-static bool sendLenAndData(SocketClient* c, const int len, const void* data) {
-    return sendBE32(c, len) && (len == 0 || c->sendData(data, len) == 0);
-}
-
-// Returns true on success
-static bool sendhostent(SocketClient *c, struct hostent *hp) {
-    bool success = true;
-    int i;
-    if (hp->h_name != NULL) {
-        success &= sendLenAndData(c, strlen(hp->h_name)+1, hp->h_name);
-    } else {
-        success &= sendLenAndData(c, 0, "") == 0;
-    }
-
-    for (i=0; hp->h_aliases[i] != NULL; i++) {
-        success &= sendLenAndData(c, strlen(hp->h_aliases[i])+1, hp->h_aliases[i]);
-    }
-    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
-
-    uint32_t buf = htonl(hp->h_addrtype);
-    success &= c->sendData(&buf, sizeof(buf)) == 0;
-
-    buf = htonl(hp->h_length);
-    success &= c->sendData(&buf, sizeof(buf)) == 0;
-
-    for (i=0; hp->h_addr_list[i] != NULL; i++) {
-        success &= sendLenAndData(c, 16, hp->h_addr_list[i]);
-    }
-    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
-    return success;
-}
-
-static bool sendaddrinfo(SocketClient* c, struct addrinfo* ai) {
-    // struct addrinfo {
-    //      int     ai_flags;       /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
-    //      int     ai_family;      /* PF_xxx */
-    //      int     ai_socktype;    /* SOCK_xxx */
-    //      int     ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
-    //      socklen_t ai_addrlen;   /* length of ai_addr */
-    //      char    *ai_canonname;  /* canonical name for hostname */
-    //      struct  sockaddr *ai_addr;      /* binary address */
-    //      struct  addrinfo *ai_next;      /* next structure in linked list */
-    // };
-
-    // Write the struct piece by piece because we might be a 64-bit netd
-    // talking to a 32-bit process.
-    bool success =
-            sendBE32(c, ai->ai_flags) &&
-            sendBE32(c, ai->ai_family) &&
-            sendBE32(c, ai->ai_socktype) &&
-            sendBE32(c, ai->ai_protocol);
-    if (!success) {
-        return false;
-    }
-
-    // ai_addrlen and ai_addr.
-    if (!sendLenAndData(c, ai->ai_addrlen, ai->ai_addr)) {
-        return false;
-    }
-
-    // strlen(ai_canonname) and ai_canonname.
-    if (!sendLenAndData(c, ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0, ai->ai_canonname)) {
-        return false;
-    }
-
-    return true;
-}
-
-void DnsProxyListener::GetAddrInfoHandler::run() {
-    if (DBG) {
-        ALOGD("GetAddrInfoHandler, now for %s / %s / {%u,%u,%u,%u,%u,%u}", mHost, mService,
-                mNetContext.app_netid, mNetContext.app_mark,
-                mNetContext.dns_netid, mNetContext.dns_mark,
-                mNetContext.uid, mNetContext.flags);
-    }
-
-    struct addrinfo* result = NULL;
-    Stopwatch s;
-    maybeFixupNetContext(&mNetContext);
-    const uid_t uid = mClient->getUid();
-    uint32_t rv = 0;
-    if (queryLimiter.start(uid)) {
-        rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result);
-        queryLimiter.finish(uid);
-    } else {
-        // Note that this error code is currently not passed down to the client.
-        // android_getaddrinfo_proxy() returns EAI_NODATA on any error.
-        rv = EAI_MEMORY;
-        ALOGE("getaddrinfo: from UID %d, max concurrent queries reached", uid);
-    }
-    const int latencyMs = lround(s.timeTaken());
-
-    if (rv) {
-        // getaddrinfo failed
-        mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, &rv, sizeof(rv));
-    } else {
-        bool success = !mClient->sendCode(ResponseCode::DnsProxyQueryResult);
-        struct addrinfo* ai = result;
-        while (ai && success) {
-            success = sendBE32(mClient, 1) && sendaddrinfo(mClient, ai);
-            ai = ai->ai_next;
-        }
-        success = success && sendBE32(mClient, 0);
-        if (!success) {
-            ALOGW("Error writing DNS result to client");
-        }
-    }
-    std::vector<String16> ip_addrs;
-    int total_ip_addr_count = 0;
-    if (result) {
-        if (mNetdEventListener != nullptr
-                && mReportingLevel == INetdEventListener::REPORTING_LEVEL_FULL) {
-            for (addrinfo* ai = result; ai; ai = ai->ai_next) {
-                sockaddr* ai_addr = ai->ai_addr;
-                if (ai_addr) {
-                    addIpAddrWithinLimit(ip_addrs, ai_addr, ai->ai_addrlen);
-                    total_ip_addr_count++;
-                }
-            }
-        }
-        freeaddrinfo(result);
-    }
-    mClient->decRef();
-    if (mNetdEventListener != nullptr) {
-        switch (mReportingLevel) {
-            case INetdEventListener::REPORTING_LEVEL_NONE:
-                // Skip reporting.
-                break;
-            case INetdEventListener::REPORTING_LEVEL_METRICS:
-                // Metrics reporting is on. Send metrics.
-                mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
-                                               INetdEventListener::EVENT_GETADDRINFO, (int32_t) rv,
-                                               latencyMs, String16(""), {}, -1, -1);
-                break;
-            case INetdEventListener::REPORTING_LEVEL_FULL:
-                // Full event info reporting is on. Send full info.
-                mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
-                                               INetdEventListener::EVENT_GETADDRINFO, (int32_t) rv,
-                                               latencyMs, String16(mHost), ip_addrs,
-                                               total_ip_addr_count, mNetContext.uid);
-                break;
-        }
-    } else {
-        ALOGW("Netd event listener is not available; skipping.");
-    }
-}
-
-void DnsProxyListener::addIpAddrWithinLimit(std::vector<android::String16>& ip_addrs,
-        const sockaddr* addr, socklen_t addrlen) {
-    // ipAddresses array is limited to first INetdEventListener::DNS_REPORTED_IP_ADDRESSES_LIMIT
-    // addresses for A and AAAA. Total count of addresses is provided, to be able to tell whether
-    // some addresses didn't get logged.
-    if (ip_addrs.size() < INetdEventListener::DNS_REPORTED_IP_ADDRESSES_LIMIT) {
-        char ip_addr[INET6_ADDRSTRLEN];
-        if (getnameinfo(addr, addrlen, ip_addr, sizeof(ip_addr), nullptr, 0, NI_NUMERICHOST) == 0) {
-            ip_addrs.push_back(String16(ip_addr));
-        }
-    }
-}
-
-DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd(DnsProxyListener* dnsProxyListener) :
-    NetdCommand("getaddrinfo"),
-    mDnsProxyListener(dnsProxyListener) {
-}
-
-int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
-                                            int argc, char **argv) {
-    if (DBG) logArguments(argc, argv);
-
-    if (argc != 8) {
-        char* msg = NULL;
-        asprintf( &msg, "Invalid number of arguments to getaddrinfo: %i", argc);
-        ALOGW("%s", msg);
-        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
-        free(msg);
-        return -1;
-    }
-
-    char* name = argv[1];
-    if (strcmp("^", name) == 0) {
-        name = NULL;
-    } else {
-        name = strdup(name);
-    }
-
-    char* service = argv[2];
-    if (strcmp("^", service) == 0) {
-        service = NULL;
-    } else {
-        service = strdup(service);
-    }
-
-    struct addrinfo* hints = NULL;
-    int ai_flags = atoi(argv[3]);
-    int ai_family = atoi(argv[4]);
-    int ai_socktype = atoi(argv[5]);
-    int ai_protocol = atoi(argv[6]);
-    unsigned netId = strtoul(argv[7], NULL, 10);
-    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
-    const uid_t uid = cli->getUid();
-
-    android_net_context netcontext;
-    mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
-    if (useLocalNameservers) {
-        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
-    }
-
-    if (ai_flags != -1 || ai_family != -1 ||
-        ai_socktype != -1 || ai_protocol != -1) {
-        hints = (struct addrinfo*) calloc(1, sizeof(struct addrinfo));
-        hints->ai_flags = ai_flags;
-        hints->ai_family = ai_family;
-        hints->ai_socktype = ai_socktype;
-        hints->ai_protocol = ai_protocol;
-    }
-
-    if (DBG) {
-        ALOGD("GetAddrInfoHandler for %s / %s / {%u,%u,%u,%u,%u}",
-             name ? name : "[nullhost]",
-             service ? service : "[nullservice]",
-             netcontext.app_netid, netcontext.app_mark,
-             netcontext.dns_netid, netcontext.dns_mark,
-             netcontext.uid);
-    }
-
-    const int metricsLevel = mDnsProxyListener->mEventReporter->getMetricsReportingLevel();
-
-    DnsProxyListener::GetAddrInfoHandler* handler =
-            new DnsProxyListener::GetAddrInfoHandler(cli, name, service, hints, netcontext,
-                    metricsLevel, mDnsProxyListener->mEventReporter->getNetdEventListener());
-    tryThreadOrError(cli, handler);
-    return 0;
-}
-
-/*******************************************************
- *                  GetHostByName                      *
- *******************************************************/
-DnsProxyListener::GetHostByNameCmd::GetHostByNameCmd(DnsProxyListener* dnsProxyListener) :
-      NetdCommand("gethostbyname"),
-      mDnsProxyListener(dnsProxyListener) {
-}
-
-int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient *cli,
-                                            int argc, char **argv) {
-    if (DBG) logArguments(argc, argv);
-
-    if (argc != 4) {
-        char* msg = NULL;
-        asprintf(&msg, "Invalid number of arguments to gethostbyname: %i", argc);
-        ALOGW("%s", msg);
-        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
-        free(msg);
-        return -1;
-    }
-
-    uid_t uid = cli->getUid();
-    unsigned netId = strtoul(argv[1], NULL, 10);
-    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
-    char* name = argv[2];
-    int af = atoi(argv[3]);
-
-    if (strcmp(name, "^") == 0) {
-        name = NULL;
-    } else {
-        name = strdup(name);
-    }
-
-    android_net_context netcontext;
-    mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
-    if (useLocalNameservers) {
-        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
-    }
-
-    const int metricsLevel = mDnsProxyListener->mEventReporter->getMetricsReportingLevel();
-
-    DnsProxyListener::GetHostByNameHandler* handler =
-            new DnsProxyListener::GetHostByNameHandler(cli, name, af, netcontext, metricsLevel,
-                    mDnsProxyListener->mEventReporter->getNetdEventListener());
-    tryThreadOrError(cli, handler);
-    return 0;
-}
-
-DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, char* name, int af,
-        const android_net_context& netcontext, const int metricsLevel,
-        const android::sp<android::net::metrics::INetdEventListener>& netdEventListener)
-        : mClient(c),
-          mName(name),
-          mAf(af),
-          mNetContext(netcontext),
-          mReportingLevel(metricsLevel),
-          mNetdEventListener(netdEventListener) {
-}
-
-DnsProxyListener::GetHostByNameHandler::~GetHostByNameHandler() {
-    free(mName);
-}
-
-void DnsProxyListener::GetHostByNameHandler::run() {
-    if (DBG) {
-        ALOGD("DnsProxyListener::GetHostByNameHandler::run");
-    }
-
-    Stopwatch s;
-    maybeFixupNetContext(&mNetContext);
-    const uid_t uid = mClient->getUid();
-    struct hostent* hp = nullptr;
-    if (queryLimiter.start(uid)) {
-        hp = android_gethostbynamefornetcontext(mName, mAf, &mNetContext);
-        queryLimiter.finish(uid);
-    } else {
-        ALOGE("gethostbyname: from UID %d, max concurrent queries reached", uid);
-    }
-    const int latencyMs = lround(s.timeTaken());
-
-    if (DBG) {
-        ALOGD("GetHostByNameHandler::run gethostbyname errno: %s hp->h_name = %s, name_len = %zu",
-                hp ? "success" : strerror(errno),
-                (hp && hp->h_name) ? hp->h_name : "null",
-                (hp && hp->h_name) ? strlen(hp->h_name) + 1 : 0);
-    }
-
-    bool success = true;
-    if (hp) {
-        success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
-        success &= sendhostent(mClient, hp);
-    } else {
-        success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, NULL, 0) == 0;
-    }
-
-    if (!success) {
-        ALOGW("GetHostByNameHandler: Error writing DNS result to client");
-    }
-
-    if (mNetdEventListener != nullptr) {
-        std::vector<String16> ip_addrs;
-        int total_ip_addr_count = 0;
-        if (mReportingLevel == INetdEventListener::REPORTING_LEVEL_FULL) {
-            if (hp != nullptr && hp->h_addrtype == AF_INET) {
-                in_addr** list = (in_addr**) hp->h_addr_list;
-                for (int i = 0; list[i] != NULL; i++) {
-                    sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = *list[i] };
-                    addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin, sizeof(sin));
-                    total_ip_addr_count++;
-                }
-            } else if (hp != nullptr && hp->h_addrtype == AF_INET6) {
-                in6_addr** list = (in6_addr**) hp->h_addr_list;
-                for (int i = 0; list[i] != NULL; i++) {
-                    sockaddr_in6 sin6 = { .sin6_family = AF_INET6, .sin6_addr = *list[i] };
-                    addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin6, sizeof(sin6));
-                    total_ip_addr_count++;
-                }
-            }
-        }
-        switch (mReportingLevel) {
-            case INetdEventListener::REPORTING_LEVEL_NONE:
-                // Reporting is off.
-                break;
-            case INetdEventListener::REPORTING_LEVEL_METRICS:
-                // Metrics reporting is on. Send metrics.
-                mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
-                                               INetdEventListener::EVENT_GETHOSTBYNAME,
-                                               h_errno, latencyMs, String16(""), {}, -1, -1);
-                break;
-            case INetdEventListener::REPORTING_LEVEL_FULL:
-                // Full event info reporting is on. Send full info.
-                mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
-                                               INetdEventListener::EVENT_GETHOSTBYNAME,
-                                               h_errno, latencyMs, String16(mName), ip_addrs,
-                                               total_ip_addr_count, uid);
-                break;
-        }
-    }
-
-    mClient->decRef();
-}
-
-
-/*******************************************************
- *                  GetHostByAddr                      *
- *******************************************************/
-DnsProxyListener::GetHostByAddrCmd::GetHostByAddrCmd(const DnsProxyListener* dnsProxyListener) :
-        NetdCommand("gethostbyaddr"),
-        mDnsProxyListener(dnsProxyListener) {
-}
-
-int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient *cli,
-                                            int argc, char **argv) {
-    if (DBG) logArguments(argc, argv);
-
-    if (argc != 5) {
-        char* msg = NULL;
-        asprintf(&msg, "Invalid number of arguments to gethostbyaddr: %i", argc);
-        ALOGW("%s", msg);
-        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
-        free(msg);
-        return -1;
-    }
-
-    char* addrStr = argv[1];
-    int addrLen = atoi(argv[2]);
-    int addrFamily = atoi(argv[3]);
-    uid_t uid = cli->getUid();
-    unsigned netId = strtoul(argv[4], NULL, 10);
-    const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
-
-    void* addr = malloc(sizeof(struct in6_addr));
-    errno = 0;
-    int result = inet_pton(addrFamily, addrStr, addr);
-    if (result <= 0) {
-        char* msg = NULL;
-        asprintf(&msg, "inet_pton(\"%s\") failed %s", addrStr, strerror(errno));
-        ALOGW("%s", msg);
-        cli->sendMsg(ResponseCode::OperationFailed, msg, false);
-        free(addr);
-        free(msg);
-        return -1;
-    }
-
-    android_net_context netcontext;
-    mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
-    if (useLocalNameservers) {
-        netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
-    }
-
-    DnsProxyListener::GetHostByAddrHandler* handler =
-            new DnsProxyListener::GetHostByAddrHandler(cli, addr, addrLen, addrFamily, netcontext);
-    tryThreadOrError(cli, handler);
-    return 0;
-}
-
-DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(
-          SocketClient* c,
-          void* address,
-          int addressLen,
-          int addressFamily,
-          const android_net_context& netcontext)
-        : mClient(c),
-          mAddress(address),
-          mAddressLen(addressLen),
-          mAddressFamily(addressFamily),
-          mNetContext(netcontext) {
-}
-
-DnsProxyListener::GetHostByAddrHandler::~GetHostByAddrHandler() {
-    free(mAddress);
-}
-
-void DnsProxyListener::GetHostByAddrHandler::run() {
-    if (DBG) {
-        ALOGD("DnsProxyListener::GetHostByAddrHandler::run");
-    }
-
-    maybeFixupNetContext(&mNetContext);
-    const uid_t uid = mClient->getUid();
-    struct hostent* hp = nullptr;
-    if (queryLimiter.start(uid)) {
-        hp = android_gethostbyaddrfornetcontext(
-                mAddress, mAddressLen, mAddressFamily, &mNetContext);
-        queryLimiter.finish(uid);
-    } else {
-        ALOGE("gethostbyaddr: from UID %d, max concurrent queries reached", uid);
-    }
-
-    if (DBG) {
-        ALOGD("GetHostByAddrHandler::run gethostbyaddr errno: %s hp->h_name = %s, name_len = %zu",
-                hp ? "success" : strerror(errno),
-                (hp && hp->h_name) ? hp->h_name : "null",
-                (hp && hp->h_name) ? strlen(hp->h_name) + 1 : 0);
-    }
-
-    bool success = true;
-    if (hp) {
-        success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
-        success &= sendhostent(mClient, hp);
-    } else {
-        success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, NULL, 0) == 0;
-    }
-
-    if (!success) {
-        ALOGW("GetHostByAddrHandler: Error writing DNS result to client");
-    }
-    mClient->decRef();
-}
-
-}  // namespace net
-}  // namespace android
diff --git a/server/DnsProxyListener.h b/server/DnsProxyListener.h
deleted file mode 100644
index a215bb6..0000000
--- a/server/DnsProxyListener.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2010 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 _DNSPROXYLISTENER_H__
-#define _DNSPROXYLISTENER_H__
-
-#include <resolv_netid.h>  // struct android_net_context
-#include <binder/IServiceManager.h>
-#include <sysutils/FrameworkListener.h>
-
-#include "android/net/metrics/INetdEventListener.h"
-#include "EventReporter.h"
-#include "NetdCommand.h"
-
-namespace android {
-namespace net {
-
-class NetworkController;
-
-class DnsProxyListener : public FrameworkListener {
-public:
-    explicit DnsProxyListener(const NetworkController* netCtrl, EventReporter* eventReporter);
-    virtual ~DnsProxyListener() {}
-
-    static constexpr const char* SOCKET_NAME = "dnsproxyd";
-
-private:
-    const NetworkController *mNetCtrl;
-    EventReporter *mEventReporter;
-    static void addIpAddrWithinLimit(std::vector<android::String16>& ip_addrs, const sockaddr* addr,
-            socklen_t addrlen);
-
-    class GetAddrInfoCmd : public NetdCommand {
-    public:
-        explicit GetAddrInfoCmd(DnsProxyListener* dnsProxyListener);
-        virtual ~GetAddrInfoCmd() {}
-        int runCommand(SocketClient *c, int argc, char** argv);
-    private:
-        DnsProxyListener* mDnsProxyListener;
-    };
-
-    class GetAddrInfoHandler {
-    public:
-        // Note: All of host, service, and hints may be NULL
-        GetAddrInfoHandler(SocketClient *c,
-                           char* host,
-                           char* service,
-                           struct addrinfo* hints,
-                           const struct android_net_context& netcontext,
-                           const int reportingLevel,
-                           const android::sp<android::net::metrics::INetdEventListener>& listener);
-        ~GetAddrInfoHandler();
-
-        void run();
-
-    private:
-        SocketClient* mClient;  // ref counted
-        char* mHost;    // owned
-        char* mService; // owned
-        struct addrinfo* mHints;  // owned
-        struct android_net_context mNetContext;
-        const int mReportingLevel;
-        android::sp<android::net::metrics::INetdEventListener> mNetdEventListener;
-    };
-
-    /* ------ gethostbyname ------*/
-    class GetHostByNameCmd : public NetdCommand {
-    public:
-        explicit GetHostByNameCmd(DnsProxyListener* dnsProxyListener);
-        virtual ~GetHostByNameCmd() {}
-        int runCommand(SocketClient *c, int argc, char** argv);
-    private:
-        DnsProxyListener* mDnsProxyListener;
-    };
-
-    class GetHostByNameHandler {
-    public:
-        GetHostByNameHandler(SocketClient *c,
-                            char *name,
-                            int af,
-                            const android_net_context& netcontext,
-                            int reportingLevel,
-                            const android::sp<android::net::metrics::INetdEventListener>& listener);
-        ~GetHostByNameHandler();
-
-        void run();
-
-    private:
-        SocketClient* mClient; //ref counted
-        char* mName; // owned
-        int mAf;
-        android_net_context mNetContext;
-        const int mReportingLevel;
-        android::sp<android::net::metrics::INetdEventListener> mNetdEventListener;
-    };
-
-    /* ------ gethostbyaddr ------*/
-    class GetHostByAddrCmd : public NetdCommand {
-    public:
-        explicit GetHostByAddrCmd(const DnsProxyListener* dnsProxyListener);
-        virtual ~GetHostByAddrCmd() {}
-        int runCommand(SocketClient *c, int argc, char** argv);
-    private:
-        const DnsProxyListener* mDnsProxyListener;
-    };
-
-    class GetHostByAddrHandler {
-    public:
-        GetHostByAddrHandler(SocketClient *c,
-                            void* address,
-                            int addressLen,
-                            int addressFamily,
-                            const android_net_context& netcontext);
-        ~GetHostByAddrHandler();
-
-        void run();
-
-    private:
-        SocketClient* mClient;  // ref counted
-        void* mAddress;    // address to lookup; owned
-        int mAddressLen; // length of address to look up
-        int mAddressFamily;  // address family
-        android_net_context mNetContext;
-    };
-};
-
-}  // namespace net
-}  // namespace android
-
-#endif
diff --git a/server/DumpWriter.h b/server/DumpWriter.h
deleted file mode 100644
index 47adac9..0000000
--- a/server/DumpWriter.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 NETD_SERVER_DUMPWRITER_H_
-#define NETD_SERVER_DUMPWRITER_H_
-
-#include <string>
-#include <utils/String16.h>
-#include <utils/Vector.h>
-
-namespace android {
-namespace net {
-
-class DumpWriter {
-public:
-    DumpWriter(int fd);
-
-    void incIndent();
-    void decIndent();
-
-    void println(const std::string& line);
-    void println(const char* fmt, ...);
-    void blankline() { println(""); }
-
-private:
-    uint8_t mIndentLevel;
-    int mFd;
-};
-
-}  // namespace net
-}  // namespace android
-
-#endif  // NETD_SERVER_DUMPWRITER_H_
diff --git a/server/EventReporter.cpp b/server/EventReporter.cpp
index c9238f4..64a1393 100644
--- a/server/EventReporter.cpp
+++ b/server/EventReporter.cpp
@@ -14,30 +14,14 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Netd"
-
 #include "EventReporter.h"
-#include "log/log.h"
 
 using android::interface_cast;
+using android::net::INetdUnsolicitedEventListener;
 using android::net::metrics::INetdEventListener;
 
-int EventReporter::setMetricsReportingLevel(const int level) {
-    if (level < INetdEventListener::REPORTING_LEVEL_NONE
-            || level > INetdEventListener::REPORTING_LEVEL_FULL) {
-        ALOGE("Invalid metrics reporting level %d", level);
-        return -EINVAL;
-    }
-    mReportingLevel = level;
-    return 0;
-}
-
-int EventReporter::getMetricsReportingLevel() const {
-    return mReportingLevel;
-}
-
 android::sp<INetdEventListener> EventReporter::getNetdEventListener() {
-    std::lock_guard<std::mutex> lock(mutex);
+    std::lock_guard lock(mEventMutex);
     if (mNetdEventListener == nullptr) {
         // Use checkService instead of getService because getService waits for 5 seconds for the
         // service to become available. The DNS resolver inside netd is started much earlier in the
@@ -45,9 +29,7 @@
         // for 5 seconds until the DNS listener starts up.
         android::sp<android::IBinder> b = android::defaultServiceManager()->checkService(
                 android::String16("netd_listener"));
-        if (b != nullptr) {
-            mNetdEventListener = interface_cast<INetdEventListener>(b);
-        }
+        mNetdEventListener = interface_cast<INetdEventListener>(b);
     }
     // If the netd listener service is dead, the binder call will just return an error, which should
     // be fine because the only impact is that we can't log netd events. In any case, this should
@@ -55,3 +37,42 @@
     // with it.
     return mNetdEventListener;
 }
+
+EventReporter::UnsolListenerMap EventReporter::getNetdUnsolicitedEventListenerMap() const {
+    std::lock_guard lock(mUnsolicitedMutex);
+    return mUnsolListenerMap;
+}
+
+void EventReporter::registerUnsolEventListener(
+        const android::sp<INetdUnsolicitedEventListener>& listener) {
+    std::lock_guard lock(mUnsolicitedMutex);
+
+    // Create the death listener.
+    class DeathRecipient : public android::IBinder::DeathRecipient {
+      public:
+        DeathRecipient(EventReporter* eventReporter,
+                       android::sp<INetdUnsolicitedEventListener> listener)
+            : mEventReporter(eventReporter), mListener(std::move(listener)) {}
+        ~DeathRecipient() override = default;
+        void binderDied(const android::wp<android::IBinder>& /* who */) override {
+            mEventReporter->unregisterUnsolEventListener(mListener);
+        }
+
+      private:
+        EventReporter* mEventReporter;
+        android::sp<INetdUnsolicitedEventListener> mListener;
+    };
+    android::sp<android::IBinder::DeathRecipient> deathRecipient =
+            new DeathRecipient(this, listener);
+
+    android::IInterface::asBinder(listener)->linkToDeath(deathRecipient);
+
+    // TODO: Consider to use remote binder address as registering key
+    mUnsolListenerMap.insert({listener, deathRecipient});
+}
+
+void EventReporter::unregisterUnsolEventListener(
+        const android::sp<INetdUnsolicitedEventListener>& listener) {
+    std::lock_guard lock(mUnsolicitedMutex);
+    mUnsolListenerMap.erase(listener);
+}
diff --git a/server/EventReporter.h b/server/EventReporter.h
index 3338a04..934aa7a 100644
--- a/server/EventReporter.h
+++ b/server/EventReporter.h
@@ -17,36 +17,44 @@
 #ifndef NETD_SERVER_EVENT_REPORTER_H
 #define NETD_SERVER_EVENT_REPORTER_H
 
-#include <atomic>
-#include <binder/IServiceManager.h>
+#include <map>
 #include <mutex>
 
+#include <android-base/thread_annotations.h>
+#include <binder/IServiceManager.h>
+#include "android/net/INetd.h"
+#include "android/net/INetdUnsolicitedEventListener.h"
 #include "android/net/metrics/INetdEventListener.h"
 
 /*
- * This class stores the reporting level and can be used to get the event listener service.
+ * This class can be used to get the event listener service.
  */
 class EventReporter {
-public:
-    int setMetricsReportingLevel(const int level);
-    int getMetricsReportingLevel() const;
+  public:
+    using UnsolListenerMap =
+            std::map<const android::sp<android::net::INetdUnsolicitedEventListener>,
+                     const android::sp<android::IBinder::DeathRecipient>>;
 
     // Returns the binder reference to the netd events listener service, attempting to fetch it if
     // we do not have it already. This method is threadsafe.
     android::sp<android::net::metrics::INetdEventListener> getNetdEventListener();
 
-private:
-    std::atomic_int mReportingLevel{
-            android::net::metrics::INetdEventListener::REPORTING_LEVEL_FULL};
-    // TODO: consider changing this into an atomic type such as
-    // std::atomic<android::net::metrics::INetdEventListener> and deleting the mutex.
-    //
-    // Alternatively, if this locking causes a performance penalty, have each single-threaded
-    // caller (DnsProxyListener, FwmarkServer) keep their own per-thread copy of NetdEventListener
-    // and remove mNetdEventListener entirely.
-    android::sp<android::net::metrics::INetdEventListener> mNetdEventListener;
-    std::mutex mutex;
+    // Returns a copy of the registered listeners.
+    UnsolListenerMap getNetdUnsolicitedEventListenerMap() const EXCLUDES(mUnsolicitedMutex);
 
+    void registerUnsolEventListener(
+            const android::sp<android::net::INetdUnsolicitedEventListener>& listener)
+            EXCLUDES(mUnsolicitedMutex);
+    void unregisterUnsolEventListener(
+            const android::sp<android::net::INetdUnsolicitedEventListener>& listener)
+            EXCLUDES(mUnsolicitedMutex);
+
+  private:
+    std::mutex mEventMutex;
+    mutable std::mutex mUnsolicitedMutex;
+    android::sp<android::net::metrics::INetdEventListener> mNetdEventListener
+            GUARDED_BY(mEventMutex);
+    UnsolListenerMap mUnsolListenerMap GUARDED_BY(mUnsolicitedMutex);
 };
 
 #endif  // NETD_SERVER_EVENT_REPORTER_H
diff --git a/server/FirewallController.cpp b/server/FirewallController.cpp
index 5f3663f..7512c09 100644
--- a/server/FirewallController.cpp
+++ b/server/FirewallController.cpp
@@ -16,18 +16,20 @@
 
 #include <set>
 
-#include <cstdint>
 #include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <cstdint>
 
 #define LOG_TAG "FirewallController"
 #define LOG_NDEBUG 0
 
-#include <android-base/strings.h>
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
-#include <cutils/log.h>
+#include <android-base/strings.h>
+#include <log/log.h>
 
 #include "Controllers.h"
 #include "FirewallController.h"
@@ -35,13 +37,32 @@
 #include "bpf/BpfUtils.h"
 
 using android::base::Join;
+using android::base::ReadFileToString;
+using android::base::Split;
 using android::base::StringAppendF;
 using android::base::StringPrintf;
-using android::bpf::DOZABLE_UID_MAP_PATH;
-using android::bpf::POWERSAVE_UID_MAP_PATH;
-using android::bpf::STANDBY_UID_MAP_PATH;
+using android::bpf::BpfLevel;
 using android::net::gCtls;
 
+namespace {
+
+// Default maximum valid uid in a normal root user namespace. The maximum valid uid is used in
+// rules that exclude all possible UIDs in the namespace in order to match packets that have
+// no socket associated with them.
+constexpr const uid_t kDefaultMaximumUid = UID_MAX - 1;  // UID_MAX defined as UINT_MAX
+
+// Proc file containing the uid mapping for the user namespace of the current process.
+const char kUidMapProcFile[] = "/proc/self/uid_map";
+
+android::bpf::BpfLevel getBpfOwnerStatus() {
+    return gCtls->trafficCtrl.getBpfLevel();
+}
+
+}  // namespace
+
+namespace android {
+namespace net {
+
 auto FirewallController::execIptablesRestore = ::execIptablesRestore;
 
 const char* FirewallController::TABLE = "filter";
@@ -66,11 +87,7 @@
     "redirect",
 };
 
-bool getBpfOwnerStatus() {
-    return gCtls->trafficCtrl.checkBpfStatsEnable();
-}
-
-FirewallController::FirewallController(void) {
+FirewallController::FirewallController(void) : mMaxUid(discoverMaximumValidUid(kUidMapProcFile)) {
     // If no rules are set, it's in BLACKLIST mode
     mFirewallType = BLACKLIST;
     mIfaceRules = {};
@@ -79,7 +96,7 @@
 int FirewallController::setupIptablesHooks(void) {
     int res = 0;
     mUseBpfOwnerMatch = getBpfOwnerStatus();
-    if (mUseBpfOwnerMatch) {
+    if (mUseBpfOwnerMatch != BpfLevel::NONE) {
         return res;
     }
     res |= createChain(LOCAL_DOZABLE, getFirewallType(DOZABLE));
@@ -88,11 +105,11 @@
     return res;
 }
 
-int FirewallController::enableFirewall(FirewallType ftype) {
+int FirewallController::setFirewallType(FirewallType ftype) {
     int res = 0;
     if (mFirewallType != ftype) {
         // flush any existing rules
-        disableFirewall();
+        resetFirewall();
 
         if (ftype == WHITELIST) {
             // create default rule to drop all traffic
@@ -108,10 +125,10 @@
         // Set this after calling disableFirewall(), since it defaults to WHITELIST there
         mFirewallType = ftype;
     }
-    return res;
+    return res ? -EREMOTEIO : 0;
 }
 
-int FirewallController::disableFirewall(void) {
+int FirewallController::resetFirewall(void) {
     mFirewallType = WHITELIST;
     mIfaceRules.clear();
 
@@ -123,7 +140,7 @@
         ":fw_FORWARD -\n"
         "COMMIT\n";
 
-    return execIptablesRestore(V4V6, command.c_str());
+    return (execIptablesRestore(V4V6, command.c_str()) == 0) ? 0 : -EREMOTEIO;
 }
 
 int FirewallController::enableChildChains(ChildChain chain, bool enable) {
@@ -143,7 +160,7 @@
             return res;
     }
 
-    if (mUseBpfOwnerMatch) {
+    if (mUseBpfOwnerMatch != BpfLevel::NONE) {
         return gCtls->trafficCtrl.toggleUidOwnerMap(chain, enable);
     }
 
@@ -164,12 +181,12 @@
 int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
     if (mFirewallType == BLACKLIST) {
         // Unsupported in BLACKLIST mode
-        return -1;
+        return -EINVAL;
     }
 
     if (!isIfaceName(iface)) {
         errno = ENOENT;
-        return -1;
+        return -ENOENT;
     }
 
     // Only delete rules if we actually added them, because otherwise our iptables-restore
@@ -192,7 +209,7 @@
         StringPrintf("%s fw_OUTPUT -o %s -j RETURN", op, iface),
         "COMMIT\n"
     }, "\n");
-    return execIptablesRestore(V4V6, command);
+    return (execIptablesRestore(V4V6, command) == 0) ? 0 : -EREMOTEIO;
 }
 
 FirewallType FirewallController::getFirewallType(ChildChain chain) {
@@ -240,20 +257,20 @@
             break;
         default:
             ALOGW("Unknown child chain: %d", chain);
-            return -1;
+            return -EINVAL;
     }
-    if (mUseBpfOwnerMatch) {
+    if (mUseBpfOwnerMatch != BpfLevel::NONE) {
         return gCtls->trafficCtrl.changeUidOwnerRule(chain, uid, rule, firewallType);
     }
 
     std::string command = "*filter\n";
-    for (std::string chainName : chainNames) {
+    for (const std::string& chainName : chainNames) {
         StringAppendF(&command, "%s %s -m owner --uid-owner %d -j %s\n",
                       op, chainName.c_str(), uid, target);
     }
     StringAppendF(&command, "COMMIT\n");
 
-    return execIptablesRestore(V4V6, command);
+    return (execIptablesRestore(V4V6, command) == 0) ? 0 : -EREMOTEIO;
 }
 
 int FirewallController::createChain(const char* chain, FirewallType type) {
@@ -292,7 +309,7 @@
         // This rule inverts the match for all UIDs; ie, if there is no UID match here,
         // there is no socket to be found
         StringAppendF(&commands,
-                "-A %s -m owner ! --uid-owner %d-%u -j RETURN\n", name, 0, UINT32_MAX-1);
+                "-A %s -m owner ! --uid-owner %d-%u -j RETURN\n", name, 0, mMaxUid);
 
         // Always whitelist traffic with protocol ESP, or no known socket - required for IPSec
         StringAppendF(&commands, "-A %s -p esp -j RETURN\n", name);
@@ -329,11 +346,57 @@
 }
 
 int FirewallController::replaceUidChain(
-        const char *name, bool isWhitelist, const std::vector<int32_t>& uids) {
-   if (mUseBpfOwnerMatch) {
-       return gCtls->trafficCtrl.replaceUidOwnerMap(name, isWhitelist, uids);
+        const std::string &name, bool isWhitelist, const std::vector<int32_t>& uids) {
+    if (mUseBpfOwnerMatch != BpfLevel::NONE) {
+        return gCtls->trafficCtrl.replaceUidOwnerMap(name, isWhitelist, uids);
    }
-   std::string commands4 = makeUidRules(V4, name, isWhitelist, uids);
-   std::string commands6 = makeUidRules(V6, name, isWhitelist, uids);
+   std::string commands4 = makeUidRules(V4, name.c_str(), isWhitelist, uids);
+   std::string commands6 = makeUidRules(V6, name.c_str(), isWhitelist, uids);
    return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
 }
+
+/* static */
+uid_t FirewallController::discoverMaximumValidUid(const std::string& fileName) {
+    std::string content;
+    if (!ReadFileToString(fileName, &content, false)) {
+        // /proc/self/uid_map only exists if a uid mapping has been set.
+        ALOGD("Could not read %s, max uid defaulting to %u", fileName.c_str(), kDefaultMaximumUid);
+        return kDefaultMaximumUid;
+    }
+
+    std::vector<std::string> lines = Split(content, "\n");
+    if (lines.empty()) {
+        ALOGD("%s was empty, max uid defaulting to %u", fileName.c_str(), kDefaultMaximumUid);
+        return kDefaultMaximumUid;
+    }
+
+    uint32_t maxUid = 0;
+    for (const auto& line : lines) {
+        if (line.empty()) {
+            continue;
+        }
+
+        // Choose the end of the largest range found in the file.
+        uint32_t start;
+        uint32_t ignored;
+        uint32_t rangeLength;
+        int items = sscanf(line.c_str(), "%u %u %u", &start, &ignored, &rangeLength);
+        if (items != 3) {
+            // uid_map lines must have 3 items, see the man page of 'user_namespaces' for details.
+            ALOGD("Format of %s unrecognized, max uid defaulting to %u", fileName.c_str(),
+                  kDefaultMaximumUid);
+            return kDefaultMaximumUid;
+        }
+        maxUid = std::max(maxUid, start + rangeLength - 1);
+    }
+
+    if (maxUid == 0) {
+        ALOGD("No max uid found, max uid defaulting to %u", kDefaultMaximumUid);
+        return kDefaultMaximumUid;
+    }
+
+    return maxUid;
+}
+
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/server/FirewallController.h b/server/FirewallController.h
index fbf5d6c..43da322 100644
--- a/server/FirewallController.h
+++ b/server/FirewallController.h
@@ -17,25 +17,34 @@
 #ifndef _FIREWALL_CONTROLLER_H
 #define _FIREWALL_CONTROLLER_H
 
+#include <sys/types.h>
+#include <mutex>
 #include <set>
 #include <string>
 #include <vector>
 
-#include <utils/RWLock.h>
+#include "android/net/INetd.h"
 
 #include "NetdConstants.h"
+#include "bpf/BpfUtils.h"
 
-enum FirewallRule { DENY, ALLOW };
+namespace android {
+namespace net {
+
+enum FirewallRule { ALLOW = INetd::FIREWALL_RULE_ALLOW, DENY = INetd::FIREWALL_RULE_DENY };
 
 // WHITELIST means the firewall denies all by default, uids must be explicitly ALLOWed
 // BLACKLIST means the firewall allows all by default, uids must be explicitly DENYed
 
-enum FirewallType { WHITELIST, BLACKLIST };
+enum FirewallType { WHITELIST = INetd::FIREWALL_WHITELIST, BLACKLIST = INetd::FIREWALL_BLACKLIST };
 
-enum ChildChain { NONE, DOZABLE, STANDBY, POWERSAVE, INVALID_CHAIN };
-
-#define PROTOCOL_TCP 6
-#define PROTOCOL_UDP 17
+enum ChildChain {
+    NONE = INetd::FIREWALL_CHAIN_NONE,
+    DOZABLE = INetd::FIREWALL_CHAIN_DOZABLE,
+    STANDBY = INetd::FIREWALL_CHAIN_STANDBY,
+    POWERSAVE = INetd::FIREWALL_CHAIN_POWERSAVE,
+    INVALID_CHAIN
+};
 
 /*
  * Simple firewall that drops all packets except those matching explicitly
@@ -51,8 +60,8 @@
 
     int setupIptablesHooks(void);
 
-    int enableFirewall(FirewallType);
-    int disableFirewall(void);
+    int setFirewallType(FirewallType);
+    int resetFirewall(void);
     int isFirewallEnabled(void);
 
     /* Match traffic going in/out over the given iface. */
@@ -62,9 +71,10 @@
 
     int enableChildChains(ChildChain, bool);
 
-    int replaceUidChain(const char*, bool, const std::vector<int32_t>&);
+    int replaceUidChain(const std::string&, bool, const std::vector<int32_t>&);
 
     static std::string makeCriticalCommands(IptablesTarget target, const char* chainName);
+    static uid_t discoverMaximumValidUid(const std::string& fileName);
 
     static const char* TABLE;
 
@@ -78,7 +88,7 @@
 
     static const char* ICMPV6_TYPES[];
 
-    android::RWLock lock;
+    std::mutex lock;
 
 protected:
     friend class FirewallControllerTest;
@@ -87,13 +97,22 @@
     static int (*execIptablesRestore)(IptablesTarget target, const std::string& commands);
 
 private:
-    FirewallType mFirewallType;
-    bool mUseBpfOwnerMatch;
-    std::set<std::string> mIfaceRules;
-    int attachChain(const char*, const char*);
-    int detachChain(const char*, const char*);
-    int createChain(const char*, FirewallType);
-    FirewallType getFirewallType(ChildChain);
+  // Netd supports two cases, in both of which mMaxUid that derives from the uid mapping is const:
+  //  - netd runs in a root namespace which contains all UIDs.
+  //  - netd runs in a user namespace where the uid mapping is written once before netd starts.
+  //    In that case, an attempt to write more than once to a uid_map file in a user namespace
+  //    fails with EPERM. Netd can therefore assumes the max valid uid to be const.
+  const uid_t mMaxUid;
+  FirewallType mFirewallType;
+  android::bpf::BpfLevel mUseBpfOwnerMatch;
+  std::set<std::string> mIfaceRules;
+  int attachChain(const char*, const char*);
+  int detachChain(const char*, const char*);
+  int createChain(const char*, FirewallType);
+  FirewallType getFirewallType(ChildChain);
 };
 
+}  // namespace net
+}  // namespace android
+
 #endif
diff --git a/server/FirewallControllerTest.cpp b/server/FirewallControllerTest.cpp
index 952b21e..bd933b4 100644
--- a/server/FirewallControllerTest.cpp
+++ b/server/FirewallControllerTest.cpp
@@ -22,20 +22,27 @@
 
 #include <gtest/gtest.h>
 
-#include <android-base/strings.h>
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #include "FirewallController.h"
 #include "IptablesBaseTest.h"
 
 using android::base::Join;
-using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+
+namespace android {
+namespace net {
 
 class FirewallControllerTest : public IptablesBaseTest {
 protected:
     FirewallControllerTest() {
         FirewallController::execIptablesRestore = fakeExecIptablesRestore;
-        mFw.mUseBpfOwnerMatch = false;
+        // This unit test currently doesn't cover the eBPF owner match case so
+        // we have to manually turn eBPF support off.
+        // TODO: find a way to unit test the eBPF code path.
+        mFw.mUseBpfOwnerMatch = android::bpf::BpfLevel::NONE;
     }
     FirewallController mFw;
 
@@ -49,7 +56,6 @@
     }
 };
 
-
 TEST_F(FirewallControllerTest, TestCreateWhitelistChain) {
     std::vector<std::string> expectedRestore4 = {
         "*filter",
@@ -82,8 +88,8 @@
         "COMMIT\n"
     };
     std::vector<std::pair<IptablesTarget, std::string>> expectedRestoreCommands = {
-        { V4, android::base::Join(expectedRestore4, '\n') },
-        { V6, android::base::Join(expectedRestore6, '\n') },
+            {V4, Join(expectedRestore4, '\n')},
+            {V6, Join(expectedRestore6, '\n')},
     };
 
     createChain("fw_whitelist", WHITELIST);
@@ -100,8 +106,8 @@
         "COMMIT\n"
     };
     std::vector<std::pair<IptablesTarget, std::string>> expectedRestoreCommands = {
-        { V4, android::base::Join(expectedRestore, '\n') },
-        { V6, android::base::Join(expectedRestore, '\n') },
+            {V4, Join(expectedRestore, '\n')},
+            {V6, Join(expectedRestore, '\n')},
     };
 
     createChain("fw_blacklist", BLACKLIST);
@@ -239,16 +245,16 @@
     };
     std::vector<std::string> noCommands = {};
 
-    EXPECT_EQ(0, mFw.disableFirewall());
+    EXPECT_EQ(0, mFw.resetFirewall());
     expectIptablesRestoreCommands(disableCommands);
 
-    EXPECT_EQ(0, mFw.disableFirewall());
+    EXPECT_EQ(0, mFw.resetFirewall());
     expectIptablesRestoreCommands(disableCommands);
 
-    EXPECT_EQ(0, mFw.enableFirewall(BLACKLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(BLACKLIST));
     expectIptablesRestoreCommands(disableCommands);
 
-    EXPECT_EQ(0, mFw.enableFirewall(BLACKLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(BLACKLIST));
     expectIptablesRestoreCommands(noCommands);
 
     std::vector<std::string> disableEnableCommands;
@@ -257,7 +263,7 @@
     disableEnableCommands.insert(
             disableEnableCommands.end(), enableCommands.begin(), enableCommands.end());
 
-    EXPECT_EQ(0, mFw.enableFirewall(WHITELIST));
+    EXPECT_EQ(0, mFw.setFirewallType(WHITELIST));
     expectIptablesRestoreCommands(disableEnableCommands);
 
     std::vector<std::string> ifaceCommands = {
@@ -284,14 +290,64 @@
     EXPECT_EQ(0, mFw.setInterfaceRule("rmnet_data0", DENY));
     expectIptablesRestoreCommands(noCommands);
 
-    EXPECT_EQ(0, mFw.enableFirewall(WHITELIST));
+    EXPECT_EQ(0, mFw.setFirewallType(WHITELIST));
     expectIptablesRestoreCommands(noCommands);
 
-    EXPECT_EQ(0, mFw.disableFirewall());
+    EXPECT_EQ(0, mFw.resetFirewall());
     expectIptablesRestoreCommands(disableCommands);
 
-    // TODO: calling disableFirewall and then enableFirewall(WHITELIST) does
+    // TODO: calling resetFirewall and then setFirewallType(WHITELIST) does
     // nothing. This seems like a clear bug.
-    EXPECT_EQ(0, mFw.enableFirewall(WHITELIST));
+    EXPECT_EQ(0, mFw.setFirewallType(WHITELIST));
     expectIptablesRestoreCommands(noCommands);
 }
+
+TEST_F(FirewallControllerTest, TestDiscoverMaximumValidUid) {
+    struct {
+        const std::string description;
+        const std::string content;
+        const uint32_t expected;
+    } testCases[] = {
+            {
+                    .description = "root namespace case",
+                    .content = "         0          0 4294967295",
+                    .expected = 4294967294,
+            },
+            {
+                    .description = "container namespace case",
+                    .content = "         0     655360       5000\n"
+                               "      5000        600         50\n"
+                               "      5050     660410    1994950\n",
+                    .expected = 1999999,
+            },
+            {
+                    .description = "garbage content case",
+                    .content = "garbage",
+                    .expected = 4294967294,
+            },
+            {
+                    .description = "no content case",
+                    .content = "",
+                    .expected = 4294967294,
+            },
+    };
+
+    const std::string tempFile = "/data/local/tmp/fake_uid_mapping";
+
+    for (const auto& test : testCases) {
+        EXPECT_TRUE(WriteStringToFile(test.content, tempFile, false));
+        uint32_t got = FirewallController::discoverMaximumValidUid(tempFile);
+        EXPECT_EQ(0, remove(tempFile.c_str()));
+        if (got != test.expected) {
+            FAIL() << test.description << ":\n"
+                   << test.content << "\ngot " << got << ", but expected " << test.expected;
+        }
+    }
+
+    // Also check when the file is not defined
+    EXPECT_NE(0, access(tempFile.c_str(), F_OK));
+    EXPECT_EQ(4294967294, FirewallController::discoverMaximumValidUid(tempFile));
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/FwmarkServer.cpp b/server/FwmarkServer.cpp
index 32b856a..51d5398 100644
--- a/server/FwmarkServer.cpp
+++ b/server/FwmarkServer.cpp
@@ -16,13 +16,6 @@
 
 #include "FwmarkServer.h"
 
-#include "Fwmark.h"
-#include "FwmarkCommand.h"
-#include "NetdConstants.h"
-#include "NetworkController.h"
-#include "TrafficController.h"
-#include "resolv_netid.h"
-
 #include <netinet/in.h>
 #include <selinux/selinux.h>
 #include <sys/socket.h>
@@ -30,6 +23,13 @@
 #include <utils/String16.h>
 
 #include <binder/IServiceManager.h>
+#include <netd_resolv/resolv.h>  // NETID_UNSET
+
+#include "Fwmark.h"
+#include "FwmarkCommand.h"
+#include "NetdConstants.h"
+#include "NetworkController.h"
+#include "TrafficController.h"
 
 using android::String16;
 using android::net::metrics::INetdEventListener;
@@ -37,7 +37,6 @@
 namespace android {
 namespace net {
 
-constexpr const char *UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
 constexpr const char *SYSTEM_SERVER_CONTEXT = "u:r:system_server:s0";
 
 bool isSystemServer(SocketClient* client) {
@@ -58,14 +57,6 @@
     return ret;
 }
 
-bool hasUpdateDeviceStatsPermission(SocketClient* client) {
-    // If the caller is the system server, allow without any further checks.
-    // Otherwise, if the system server's binder thread pool is full, and all the threads are
-    // blocked on a thread that's waiting for us to complete, we deadlock. http://b/69389492
-    return isSystemServer(client) ||
-           checkPermission(String16(UPDATE_DEVICE_STATS), client->getPid(), client->getUid());
-}
-
 FwmarkServer::FwmarkServer(NetworkController* networkController, EventReporter* eventReporter,
                            TrafficController* trafficCtrl)
     : SocketListener(SOCKET_NAME, true),
@@ -133,17 +124,11 @@
     }
 
     if (command.cmdId == FwmarkCommand::SET_COUNTERSET) {
-        if (!hasUpdateDeviceStatsPermission(client)) {
-            return -EPERM;
-        }
-        return mTrafficCtrl->setCounterSet(command.trafficCtrlInfo, command.uid);
+        return mTrafficCtrl->setCounterSet(command.trafficCtrlInfo, command.uid, client->getUid());
     }
 
     if (command.cmdId == FwmarkCommand::DELETE_TAGDATA) {
-        if (!hasUpdateDeviceStatsPermission(client)) {
-            return -EPERM;
-        }
-        return mTrafficCtrl->deleteTagData(command.trafficCtrlInfo, command.uid);
+        return mTrafficCtrl->deleteTagData(command.trafficCtrlInfo, command.uid, client->getUid());
     }
 
     cmsghdr* const cmsgh = CMSG_FIRSTHDR(&message);
@@ -156,6 +141,15 @@
         return -EBADF;
     }
 
+    int family;
+    socklen_t familyLen = sizeof(family);
+    if (getsockopt(*socketFd, SOL_SOCKET, SO_DOMAIN, &family, &familyLen) == -1) {
+        return -errno;
+    }
+    if (!FwmarkCommand::isSupportedFamily(family)) {
+        return -EAFNOSUPPORT;
+    }
+
     Fwmark fwmark;
     socklen_t fwmarkLen = sizeof(fwmark.intValue);
     if (getsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen) == -1) {
@@ -240,7 +234,7 @@
                 netdEventListener->onConnectEvent(fwmark.netId, connectInfo.error,
                         connectInfo.latencyMs,
                         (ret == 0) ? String16(addrstr) : String16(""),
-                        (ret == 0) ? strtoul(portstr, NULL, 10) : 0, client->getUid());
+                        (ret == 0) ? strtoul(portstr, nullptr, 10) : 0, client->getUid());
             }
             break;
         }
@@ -298,10 +292,8 @@
             if (static_cast<int>(command.uid) == -1) {
                 command.uid = client->getUid();
             }
-            if (command.uid != client->getUid() && !hasUpdateDeviceStatsPermission(client)) {
-                return -EPERM;
-            }
-            return mTrafficCtrl->tagSocket(*socketFd, command.trafficCtrlInfo, command.uid);
+            return mTrafficCtrl->tagSocket(*socketFd, command.trafficCtrlInfo, command.uid,
+                                           client->getUid());
         }
 
         case FwmarkCommand::UNTAG_SOCKET: {
diff --git a/server/FwmarkServer.h b/server/FwmarkServer.h
index 2f44db1..c17dc0d 100644
--- a/server/FwmarkServer.h
+++ b/server/FwmarkServer.h
@@ -18,13 +18,13 @@
 #define NETD_SERVER_FWMARK_SERVER_H
 
 #include "EventReporter.h"
-#include "TrafficController.h"
 #include "sysutils/SocketListener.h"
 
 namespace android {
 namespace net {
 
 class NetworkController;
+class TrafficController;
 
 class FwmarkServer : public SocketListener {
 public:
diff --git a/server/IdletimerController.cpp b/server/IdletimerController.cpp
index da19453..acb8c6a 100644
--- a/server/IdletimerController.cpp
+++ b/server/IdletimerController.cpp
@@ -114,7 +114,7 @@
 #include <android-base/stringprintf.h>
 
 #define LOG_TAG "IdletimerController"
-#include <cutils/log.h>
+#include <log/log.h>
 #include <logwrap/logwrap.h>
 
 #include "IdletimerController.h"
@@ -138,29 +138,6 @@
     return true;
 }
 
-int IdletimerController::setDefaults() {
-    std::vector<std::string> cmds = {
-        "*raw",
-        StringPrintf(":%s -", LOCAL_RAW_PREROUTING),
-        "COMMIT",
-        "*mangle",
-        StringPrintf(":%s -", LOCAL_MANGLE_POSTROUTING),
-        "COMMIT\n",
-    };
-
-    return execIptablesRestore(V4V6, Join(cmds, '\n'));
-}
-
-int IdletimerController::enableIdletimerControl() {
-    int res = setDefaults();
-    return res;
-}
-
-int IdletimerController::disableIdletimerControl() {
-    int res = setDefaults();
-    return res;
-}
-
 int IdletimerController::modifyInterfaceIdletimer(IptOp op, const char *iface,
                                                   uint32_t timeout,
                                                   const char *classLabel) {
@@ -181,7 +158,7 @@
         "COMMIT\n",
     };
 
-    return execIptablesRestore(V4V6, Join(cmds, '\n'));
+    return (execIptablesRestore(V4V6, Join(cmds, '\n')) == 0) ? 0 : -EREMOTEIO;
 }
 
 int IdletimerController::addInterfaceIdletimer(const char *iface,
diff --git a/server/IdletimerController.h b/server/IdletimerController.h
index 87e0b4e..5cd162c 100644
--- a/server/IdletimerController.h
+++ b/server/IdletimerController.h
@@ -26,8 +26,6 @@
     IdletimerController();
     virtual ~IdletimerController();
 
-    int enableIdletimerControl();
-    int disableIdletimerControl();
     int addInterfaceIdletimer(const char *iface, uint32_t timeout,
                               const char *classLabel);
     int removeInterfaceIdletimer(const char *iface, uint32_t timeout,
@@ -36,10 +34,10 @@
 
     static const char* LOCAL_RAW_PREROUTING;
     static const char* LOCAL_MANGLE_POSTROUTING;
+    std::mutex lock;
 
- private:
+  private:
     enum IptOp { IptOpAdd, IptOpDelete };
-    int setDefaults();
     int runIpxtablesCmd(int argc, const char **cmd);
     int modifyInterfaceIdletimer(IptOp op, const char *iface, uint32_t timeout,
                                  const char *classLabel);
diff --git a/server/IdletimerControllerTest.cpp b/server/IdletimerControllerTest.cpp
index ace3fd9..30c2298 100644
--- a/server/IdletimerControllerTest.cpp
+++ b/server/IdletimerControllerTest.cpp
@@ -40,29 +40,6 @@
     expectIptablesRestoreCommands(ExpectedIptablesCommands{});
 }
 
-TEST_F(IdletimerControllerTest, TestEnableDisable) {
-    std::vector<std::string> expected = {
-        "*raw\n"
-        ":idletimer_raw_PREROUTING -\n"
-        "COMMIT\n"
-        "*mangle\n"
-        ":idletimer_mangle_POSTROUTING -\n"
-        "COMMIT\n",
-    };
-
-    mIt.enableIdletimerControl();
-    expectIptablesRestoreCommands(expected);
-
-    mIt.enableIdletimerControl();
-    expectIptablesRestoreCommands(expected);
-
-    mIt.disableIdletimerControl();
-    expectIptablesRestoreCommands(expected);
-
-    mIt.disableIdletimerControl();
-    expectIptablesRestoreCommands(expected);
-}
-
 const std::vector<std::string> makeAddRemoveCommands(bool add) {
     const char *op = add ? "-A" : "-D";
     std::vector<std::string> cmds = {
diff --git a/server/InterfaceController.cpp b/server/InterfaceController.cpp
index c0210d7..bad4ba2 100644
--- a/server/InterfaceController.cpp
+++ b/server/InterfaceController.cpp
@@ -18,6 +18,7 @@
 #include <errno.h>
 #include <malloc.h>
 #include <net/if.h>
+#include <net/if_arp.h>
 #include <sys/socket.h>
 
 #include <functional>
@@ -26,7 +27,9 @@
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
-#include <cutils/log.h>
+#include <android-base/strings.h>
+#include <linux/if_ether.h>
+#include <log/log.h>
 #include <logwrap/logwrap.h>
 #include <netutils/ifc.h>
 
@@ -40,24 +43,32 @@
 
 using android::base::ReadFileToString;
 using android::base::StringPrintf;
+using android::base::Trim;
 using android::base::WriteStringToFile;
 using android::net::INetd;
 using android::net::RouteController;
 using android::netdutils::isOk;
-using android::netdutils::Status;
-using android::netdutils::StatusOr;
 using android::netdutils::makeSlice;
 using android::netdutils::sSyscalls;
-using android::netdutils::status::ok;
+using android::netdutils::Status;
 using android::netdutils::statusFromErrno;
+using android::netdutils::StatusOr;
 using android::netdutils::toString;
+using android::netdutils::status::ok;
+
+#define RETURN_STATUS_IF_IFCERROR(exp)                           \
+    do {                                                         \
+        if ((exp) == -1) {                                       \
+            return statusFromErrno(errno, "Failed to add addr"); \
+        }                                                        \
+    } while (0);
 
 namespace {
 
+const char ipv4_proc_path[] = "/proc/sys/net/ipv4/conf";
 const char ipv6_proc_path[] = "/proc/sys/net/ipv6/conf";
 
 const char ipv4_neigh_conf_dir[] = "/proc/sys/net/ipv4/neigh";
-
 const char ipv6_neigh_conf_dir[] = "/proc/sys/net/ipv6/neigh";
 
 const char proc_net_path[] = "/proc/sys/net";
@@ -103,12 +114,13 @@
         const char* dirname, const char* subdirname, const char* basename,
         const char* value) {
     std::string path(StringPrintf("%s/%s/%s", dirname, subdirname, basename));
-    return WriteStringToFile(value, path) ? 0 : -1;
+    return WriteStringToFile(value, path) ? 0 : -EREMOTEIO;
 }
 
 // Run @fn on each interface as well as 'default' in the path @dirname.
-void forEachInterface(const std::string& dirname,
-                      std::function<void(const std::string& path, const std::string& iface)> fn) {
+void forEachInterface(
+        const std::string& dirname,
+        const std::function<void(const std::string& path, const std::string& iface)>& fn) {
     // Run on default, which controls the behavior of any interfaces that are created in the future.
     fn(dirname, "default");
     DIR* dir = opendir(dirname.c_str());
@@ -189,8 +201,14 @@
 
 }  // namespace
 
+namespace android {
+namespace net {
+std::mutex InterfaceController::mutex;
+
 android::netdutils::Status InterfaceController::enableStablePrivacyAddresses(
-        const std::string& iface, GetPropertyFn getProperty, SetPropertyFn setProperty) {
+        const std::string& iface,
+        const GetPropertyFn& getProperty,
+        const SetPropertyFn& setProperty) {
     const auto& sys = sSyscalls.get();
     const std::string procTarget = std::string(ipv6_proc_path) + "/" + iface + "/stable_secret";
     auto procFd = sys.open(procTarget, O_CLOEXEC | O_WRONLY);
@@ -245,14 +263,16 @@
     setBaseReachableTimeMs(15 * 1000);
 
     // When sending traffic via a given interface use only addresses configured
-       // on that interface as possible source addresses.
+    // on that interface as possible source addresses.
     setIPv6UseOutgoingInterfaceAddrsOnly("1");
+
+    // Ensure that ICMP redirects are rejected globally on all interfaces.
+    disableIcmpRedirects();
 }
 
 int InterfaceController::setEnableIPv6(const char *interface, const int on) {
     if (!isIfaceName(interface)) {
-        errno = ENOENT;
-        return -1;
+        return -ENOENT;
     }
     // When disable_ipv6 changes from 1 to 0, the kernel starts autoconf.
     // When disable_ipv6 changes from 0 to 1, the kernel clears all autoconf
@@ -317,7 +337,7 @@
 int InterfaceController::setIPv6PrivacyExtensions(const char *interface, const int on) {
     if (!isIfaceName(interface)) {
         errno = ENOENT;
-        return -1;
+        return -errno;
     }
     // 0: disable IPv6 privacy addresses
     // 2: enable IPv6 privacy addresses and prefer them over non-privacy ones.
@@ -343,7 +363,7 @@
 {
     if (!isIfaceName(interface)) {
         errno = ENOENT;
-        return -1;
+        return -errno;
     }
     return writeValueToPath(sys_net_path, interface, "mtu", mtu);
 }
@@ -358,6 +378,15 @@
     return ifc_del_address(interface, addrString, prefixLength);
 }
 
+int InterfaceController::disableIcmpRedirects() {
+    int rv = 0;
+    rv |= writeValueToPath(ipv4_proc_path, "all", "accept_redirects", "0");
+    rv |= writeValueToPath(ipv6_proc_path, "all", "accept_redirects", "0");
+    setOnAllInterfaces(ipv4_proc_path, "accept_redirects", "0");
+    setOnAllInterfaces(ipv6_proc_path, "accept_redirects", "0");
+    return rv;
+}
+
 int InterfaceController::getParameter(
         const char *family, const char *which, const char *interface, const char *parameter,
         std::string *value) {
@@ -365,7 +394,11 @@
     if (path.empty()) {
         return -errno;
     }
-    return ReadFileToString(path, value) ? 0 : -errno;
+    if (ReadFileToString(path, value)) {
+        *value = Trim(*value);
+        return 0;
+    }
+    return -errno;
 }
 
 int InterfaceController::setParameter(
@@ -398,6 +431,7 @@
         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));
     }
@@ -418,3 +452,119 @@
     }
     return ifacePairs;
 }
+
+namespace {
+
+std::string hwAddrToStr(unsigned char* hwaddr) {
+    return StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3],
+                        hwaddr[4], hwaddr[5]);
+}
+
+int ipv4NetmaskToPrefixLength(in_addr_t mask) {
+    int prefixLength = 0;
+    uint32_t m = ntohl(mask);
+    while (m & (1 << 31)) {
+        prefixLength++;
+        m = m << 1;
+    }
+    return prefixLength;
+}
+
+std::string toStdString(const String16& s) {
+    return std::string(String8(s.string()));
+}
+
+}  // namespace
+
+Status InterfaceController::setCfg(const InterfaceConfigurationParcel& cfg) {
+    const auto& sys = sSyscalls.get();
+    ASSIGN_OR_RETURN(auto fd, sys.socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+    struct ifreq ifr = {
+            .ifr_addr = {.sa_family = AF_INET},  // Clear the IPv4 address.
+    };
+    strlcpy(ifr.ifr_name, cfg.ifName.c_str(), IFNAMSIZ);
+
+    // Make sure that clear IPv4 address before set flag
+    // SIOCGIFFLAGS might override ifr and caused clear IPv4 addr ioctl error
+    RETURN_IF_NOT_OK(sys.ioctl(fd, SIOCSIFADDR, &ifr));
+
+    if (!cfg.flags.empty()) {
+        RETURN_IF_NOT_OK(sys.ioctl(fd, SIOCGIFFLAGS, &ifr));
+        uint16_t flags = ifr.ifr_flags;
+
+        for (const auto& flag : cfg.flags) {
+            if (flag == toStdString(INetd::IF_STATE_UP())) {
+                ifr.ifr_flags = ifr.ifr_flags | IFF_UP;
+            } else if (flag == toStdString(INetd::IF_STATE_DOWN())) {
+                ifr.ifr_flags = (ifr.ifr_flags & (~IFF_UP));
+            }
+        }
+
+        if (ifr.ifr_flags != flags) {
+            RETURN_IF_NOT_OK(sys.ioctl(fd, SIOCSIFFLAGS, &ifr));
+        }
+    }
+
+    RETURN_STATUS_IF_IFCERROR(
+            ifc_add_address(cfg.ifName.c_str(), cfg.ipv4Addr.c_str(), cfg.prefixLength));
+
+    return ok;
+}
+
+StatusOr<InterfaceConfigurationParcel> InterfaceController::getCfg(const std::string& ifName) {
+    struct in_addr addr = {};
+    int prefixLength = 0;
+    unsigned char hwaddr[ETH_ALEN] = {};
+    unsigned flags = 0;
+    InterfaceConfigurationParcel cfgResult;
+
+    const auto& sys = sSyscalls.get();
+    ASSIGN_OR_RETURN(auto fd, sys.socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+
+    struct ifreq ifr = {};
+    strlcpy(ifr.ifr_name, ifName.c_str(), IFNAMSIZ);
+
+    if (isOk(sys.ioctl(fd, SIOCGIFADDR, &ifr))) {
+        addr.s_addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr;
+    }
+
+    if (isOk(sys.ioctl(fd, SIOCGIFNETMASK, &ifr))) {
+        prefixLength =
+                ipv4NetmaskToPrefixLength(((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr);
+    }
+
+    if (isOk(sys.ioctl(fd, SIOCGIFFLAGS, &ifr))) {
+        flags = ifr.ifr_flags;
+    }
+
+    // ETH_ALEN is for ARPHRD_ETHER, it is better to check the sa_family.
+    // However, we keep old design for the consistency.
+    if (isOk(sys.ioctl(fd, SIOCGIFHWADDR, &ifr))) {
+        memcpy((void*) hwaddr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+    } else {
+        ALOGW("Failed to retrieve HW addr for %s (%s)", ifName.c_str(), strerror(errno));
+    }
+
+    cfgResult.ifName = ifName;
+    cfgResult.hwAddr = hwAddrToStr(hwaddr);
+    cfgResult.ipv4Addr = std::string(inet_ntoa(addr));
+    cfgResult.prefixLength = prefixLength;
+    cfgResult.flags.push_back(flags & IFF_UP ? toStdString(INetd::IF_STATE_UP())
+                                             : toStdString(INetd::IF_STATE_DOWN()));
+
+    if (flags & IFF_BROADCAST) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_BROADCAST()));
+    if (flags & IFF_LOOPBACK) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_LOOPBACK()));
+    if (flags & IFF_POINTOPOINT)
+        cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_POINTOPOINT()));
+    if (flags & IFF_RUNNING) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_RUNNING()));
+    if (flags & IFF_MULTICAST) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_MULTICAST()));
+
+    return cfgResult;
+}
+
+int InterfaceController::clearAddrs(const std::string& ifName) {
+    return ifc_clear_addresses(ifName.c_str());
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/InterfaceController.h b/server/InterfaceController.h
index cd6f0eb..e21c75d 100644
--- a/server/InterfaceController.h
+++ b/server/InterfaceController.h
@@ -18,64 +18,75 @@
 #define _INTERFACE_CONTROLLER_H
 
 #include <functional>
+#include <map>
 #include <string>
 
+#include <android/net/InterfaceConfigurationParcel.h>
 #include <netdutils/Status.h>
 #include <netdutils/StatusOr.h>
 
-// TODO: move InterfaceController into android::net namespace.
 namespace android {
 namespace net {
+
 class StablePrivacyTest;
-}  // namespace net
-}  // namespace android
 
 class InterfaceController {
 public:
     static void initializeAll();
 
-    static int setEnableIPv6(const char *interface, const int on);
-    static android::netdutils::Status setIPv6AddrGenMode(const std::string& interface, int mode);
-    static int setAcceptIPv6Ra(const char *interface, const int on);
-    static int setAcceptIPv6Dad(const char *interface, const int on);
-    static int setIPv6DadTransmits(const char *interface, const char *value);
-    static int setIPv6PrivacyExtensions(const char *interface, const int on);
-    static int setMtu(const char *interface, const char *mtu);
-    static int addAddress(const char *interface, const char *addrString, int prefixLength);
-    static int delAddress(const char *interface, const char *addrString, int prefixLength);
+    static int setEnableIPv6(const char* ifName, const int on);
+    static android::netdutils::Status setIPv6AddrGenMode(const std::string& ifName, int mode);
+    static int setAcceptIPv6Ra(const char* ifName, const int on);
+    static int setAcceptIPv6Dad(const char* ifName, const int on);
+    static int setIPv6DadTransmits(const char* ifName, const char* value);
+    static int setIPv6PrivacyExtensions(const char* ifName, const int on);
+    static int setMtu(const char* ifName, const char* mtu);
+    static int addAddress(const char* ifName, const char* addrString, int prefixLength);
+    static int delAddress(const char* ifName, const char* addrString, int prefixLength);
+    static int disableIcmpRedirects();
+    static android::netdutils::Status setCfg(const InterfaceConfigurationParcel& cfg);
+    static android::netdutils::StatusOr<InterfaceConfigurationParcel> getCfg(
+            const std::string& ifName);
+    static int clearAddrs(const std::string& ifName);
 
     // Read and write values in files of the form:
-    //     /proc/sys/net/<family>/<which>/<interface>/<parameter>
-    static int getParameter(
-            const char *family, const char *which, const char *interface, const char *parameter,
-            std::string *value);
-    static int setParameter(
-            const char *family, const char *which, const char *interface, const char *parameter,
-            const char *value);
+    //     /proc/sys/net/<family>/<which>/<ifName>/<parameter>
+    //
+    // NOTE: getParameter() trims whitespace so the caller does not need extra
+    // code to crop trailing newlines, for example.
+    static int getParameter(const char* family, const char* which, const char* ifName,
+                            const char* parameter, std::string* value);
+    static int setParameter(const char* family, const char* which, const char* ifName,
+                            const char* parameter, const char* value);
 
     static android::netdutils::StatusOr<std::vector<std::string>> getIfaceNames();
     static android::netdutils::StatusOr<std::map<std::string, uint32_t>> getIfaceList();
 
-private:
-  friend class android::net::StablePrivacyTest;
+    static std::mutex mutex;
 
-  using GetPropertyFn =
-      std::function<std::string(const std::string& key, const std::string& dflt)>;
-  using SetPropertyFn =
-      std::function<android::netdutils::Status(const std::string& key, const std::string& val)>;
+  private:
+    friend class android::net::StablePrivacyTest;
 
-  // Helper function exported from this compilation unit for testing.
-  static android::netdutils::Status enableStablePrivacyAddresses(const std::string& iface,
-                                                                 GetPropertyFn getProperty,
-                                                                 SetPropertyFn setProperty);
+    using GetPropertyFn =
+            std::function<std::string(const std::string& key, const std::string& dflt)>;
+    using SetPropertyFn = std::function<android::netdutils::Status(const std::string& key,
+                                                                   const std::string& val)>;
 
-  static void setAcceptRA(const char* value);
-  static void setAcceptRARouteTable(int tableOrOffset);
-  static void setBaseReachableTimeMs(unsigned int millis);
-  static void setIPv6OptimisticMode(const char* value);
+    // Helper function exported from this compilation unit for testing.
+    static android::netdutils::Status enableStablePrivacyAddresses(
+            const std::string& ifName, const GetPropertyFn& getProperty,
+            const SetPropertyFn& setProperty);
 
-  InterfaceController() = delete;
-  ~InterfaceController() = delete;
+    static void setAcceptRA(const char* value);
+    static void setAcceptRARouteTable(int tableOrOffset);
+    static void setBaseReachableTimeMs(unsigned int millis);
+    static void setIPv6OptimisticMode(const char* value);
+
+    InterfaceController() = delete;
+    ~InterfaceController() = delete;
 };
 
+}  // namespace net
+}  // namespace android
+
 #endif
diff --git a/server/InterfaceControllerTest.cpp b/server/InterfaceControllerTest.cpp
index fc5dce1..78d507f 100644
--- a/server/InterfaceControllerTest.cpp
+++ b/server/InterfaceControllerTest.cpp
@@ -94,7 +94,7 @@
     }
 
     void expectSetProperty(const std::string& key, const std::string& val, Status status) {
-        EXPECT_CALL(mProperties, set(key, val)).WillOnce(Return(status));
+        EXPECT_CALL(mProperties, set(key, val)).WillOnce(Return(std::move(status)));
     }
 
     void expectWriteToFile(const Fd fd, const std::string& val, int err) {
@@ -183,7 +183,7 @@
     EXPECT_EQ(ok, ifaceNames.status());
     struct ifaddrs *ifaddr, *ifa;
     EXPECT_EQ(0, getifaddrs(&ifaddr));
-    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+    for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
         const auto val = std::find(
                 ifaceNames.value().begin(), ifaceNames.value().end(), ifa->ifa_name);
         EXPECT_NE(ifaceNames.value().end(), val);
@@ -196,7 +196,7 @@
     EXPECT_EQ(ok, ifaceMap.status());
     struct ifaddrs *ifaddr, *ifa;
     EXPECT_EQ(0, getifaddrs(&ifaddr));
-    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+    for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
         uint32_t ifaceIndex = if_nametoindex(ifa->ifa_name);
         const auto ifacePair = ifaceMap.value().find(ifa->ifa_name);
         EXPECT_NE(ifaceMap.value().end(), ifacePair);
diff --git a/server/IptablesBaseTest.cpp b/server/IptablesBaseTest.cpp
index f0a6d2c..b3748cd 100644
--- a/server/IptablesBaseTest.cpp
+++ b/server/IptablesBaseTest.cpp
@@ -28,7 +28,7 @@
 #include "NetdConstants.h"
 
 #define LOG_TAG "IptablesBaseTest"
-#include <cutils/log.h>
+#include <log/log.h>
 
 using android::base::StringPrintf;
 
@@ -41,7 +41,7 @@
 int IptablesBaseTest::fake_android_fork_exec(int argc, char* argv[], int *status, bool, bool) {
     std::string cmd = argv[0];
     for (int i = 1; i < argc; i++) {
-        if (argv[i] == NULL) break;
+        if (argv[i] == nullptr) break;
         cmd += " ";
         cmd += argv[i];
     }
@@ -63,12 +63,12 @@
 
 FILE *IptablesBaseTest::fake_popen(const char * /* cmd */, const char *type) {
     if (sPopenContents.empty() || strcmp(type, "r") != 0) {
-        return NULL;
+        return nullptr;
     }
 
     std::string realCmd = StringPrintf("echo '%s'", sPopenContents.front().c_str());
     sPopenContents.pop_front();
-    return popen(realCmd.c_str(), "r");
+    return popen(realCmd.c_str(), "r");  // NOLINT(cert-env33-c)
 }
 
 int IptablesBaseTest::fakeExecIptablesRestoreWithOutput(IptablesTarget target,
diff --git a/server/IptablesRestoreController.cpp b/server/IptablesRestoreController.cpp
index 88d88f6..8339177 100644
--- a/server/IptablesRestoreController.cpp
+++ b/server/IptablesRestoreController.cpp
@@ -104,8 +104,8 @@
         processTerminated = true;
     }
 
-    const pid_t pid;
-    const int stdIn;
+    const pid_t pid;  // NOLINT(misc-non-private-member-variables-in-classes)
+    const int stdIn;  // NOLINT(misc-non-private-member-variables-in-classes)
 
     struct pollfd pollFds[2];
     std::string errBuf;
@@ -347,7 +347,7 @@
 
 int IptablesRestoreController::execute(const IptablesTarget target, const std::string& command,
                                        std::string *output) {
-    std::lock_guard<std::mutex> lock(mLock);
+    std::lock_guard lock(mLock);
 
     std::string buffer;
     if (output == nullptr) {
diff --git a/server/IptablesRestoreControllerTest.cpp b/server/IptablesRestoreControllerTest.cpp
index 8194f58..d01d7ce 100644
--- a/server/IptablesRestoreControllerTest.cpp
+++ b/server/IptablesRestoreControllerTest.cpp
@@ -24,14 +24,14 @@
 #include <gtest/gtest.h>
 
 #define LOG_TAG "IptablesRestoreControllerTest"
-#include <cutils/log.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <log/log.h>
 #include <netdutils/MockSyscalls.h>
+#include <netdutils/Stopwatch.h>
 
 #include "IptablesRestoreController.h"
 #include "NetdConstants.h"
-#include "Stopwatch.h"
 #include "bpf/BpfUtils.h"
 
 #define XT_LOCK_NAME "/system/etc/xtables.lock"
@@ -40,10 +40,8 @@
 
 using android::base::Join;
 using android::base::StringPrintf;
-using android::bpf::DOZABLE_UID_MAP_PATH;
-using android::bpf::STANDBY_UID_MAP_PATH;
-using android::bpf::POWERSAVE_UID_MAP_PATH;
 using android::netdutils::ScopedMockSyscalls;
+using android::netdutils::Stopwatch;
 using testing::Return;
 using testing::StrictMock;
 
@@ -81,7 +79,7 @@
     // We can't readlink /proc/PID/exe, because zombie processes don't have it.
     // Parse /proc/PID/stat instead.
     std::string statPath = StringPrintf("/proc/%d/stat", pid);
-    int fd = open(statPath.c_str(), O_RDONLY);
+    int fd = open(statPath.c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
       // ENOENT means the process is gone (expected).
       ASSERT_EQ(errno, ENOENT)
@@ -131,7 +129,7 @@
   }
 
   int acquireIptablesLock() {
-    mIptablesLock = open(XT_LOCK_NAME, O_CREAT, 0600);
+    mIptablesLock = open(XT_LOCK_NAME, O_CREAT | O_CLOEXEC, 0600);
     if (mIptablesLock == -1) return mIptablesLock;
     int attempts;
     for (attempts = 0; attempts < XT_LOCK_ATTEMPTS; attempts++) {
diff --git a/server/MDnsSdListener.cpp b/server/MDnsSdListener.cpp
index e497bfd..42dcddf 100644
--- a/server/MDnsSdListener.cpp
+++ b/server/MDnsSdListener.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "MDnsSdListener.h"
+
 #include <arpa/inet.h>
 #include <dirent.h>
 #include <errno.h>
@@ -21,30 +23,32 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <pthread.h>
+#include <resolv.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/poll.h>
 #include <sys/socket.h>
 #include <sys/types.h>
-#include <string.h>
-#include <resolv.h>
 
 #define LOG_TAG "MDnsDS"
 #define DBG 1
 #define VDBG 1
 
-#include <cutils/log.h>
 #include <cutils/properties.h>
+#include <log/log.h>
+#include <netdutils/ResponseCode.h>
+#include <netdutils/ThreadUtil.h>
 #include <sysutils/SocketClient.h>
 
-#include "MDnsSdListener.h"
-#include "ResponseCode.h"
-#include "thread_util.h"
-
 #define MDNS_SERVICE_NAME "mdnsd"
 #define MDNS_SERVICE_STATUS "init.svc.mdnsd"
 
 #define CEIL(x, y) (((x) + (y) - 1) / (y))
 
+constexpr char RESCAN[] = "1";
+
+using android::netdutils::ResponseCode;
+
 MDnsSdListener::MDnsSdListener() : FrameworkListener(SOCKET_NAME, true) {
     Monitor *m = new Monitor();
     registerCmd(new Handler(m, this));
@@ -71,7 +75,7 @@
     }
     Context *context = new Context(requestId, mListener);
     DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
-    if (ref == NULL) {
+    if (ref == nullptr) {
         ALOGE("requestId %d already in use during discover call", requestId);
         cli->sendMsg(ResponseCode::CommandParameterError,
                 "RequestId already in use during discover call", false);
@@ -138,15 +142,15 @@
         free(msg);
         return;
     }
-    int requestId = atoi(argv[2]);
+    int requestId = strtol(argv[2], nullptr, 10);
     DNSServiceRef *ref = mMonitor->lookupServiceRef(requestId);
-    if (ref == NULL) {
+    if (ref == nullptr) {
         if (DBG) ALOGE("%s stop used unknown requestId %d", str, requestId);
         cli->sendMsg(ResponseCode::CommandParameterError, "Unknown requestId", false);
         return;
     }
     if (VDBG) ALOGD("Stopping %s with ref %p", str, ref);
-    DNSServiceRefDeallocate(*ref);
+    mMonitor->deallocateServiceRef(ref);
     mMonitor->freeServiceRef(requestId);
     char *msg;
     asprintf(&msg, "%s stopped", str);
@@ -164,7 +168,7 @@
     Context *context = new Context(requestId, mListener);
     DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
     port = htons(port);
-    if (ref == NULL) {
+    if (ref == nullptr) {
         ALOGE("requestId %d already in use during register call", requestId);
         cli->sendMsg(ResponseCode::CommandParameterError,
                 "RequestId already in use during register call", false);
@@ -219,7 +223,7 @@
     }
     Context *context = new Context(requestId, mListener);
     DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
-    if (ref == NULL) {
+    if (ref == nullptr) {
         ALOGE("request Id %d already in use during resolve call", requestId);
         cli->sendMsg(ResponseCode::CommandParameterError,
                 "RequestId already in use during resolve call", false);
@@ -285,7 +289,7 @@
     if (VDBG) ALOGD("getAddrInfo(%d, %s %d, %s)", requestId, interfaceName, protocol, hostname);
     Context *context = new Context(requestId, mListener);
     DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
-    if (ref == NULL) {
+    if (ref == nullptr) {
         ALOGE("request ID %d already in use during getAddrInfo call", requestId);
         cli->sendMsg(ResponseCode::CommandParameterError,
                 "RequestId already in use during getAddrInfo call", false);
@@ -345,7 +349,7 @@
     if (VDBG) ALOGD("setHostname(%d, %s)", requestId, hostname);
     Context *context = new Context(requestId, mListener);
     DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
-    if (ref == NULL) {
+    if (ref == nullptr) {
         ALOGE("request Id %d already in use during setHostname call", requestId);
         cli->sendMsg(ResponseCode::CommandParameterError,
                 "RequestId already in use during setHostname call", false);
@@ -392,7 +396,7 @@
 }
 
 const char *MDnsSdListener::Handler::iToIfaceName(int /* i */) {
-    return NULL;
+    return nullptr;
 }
 
 DNSServiceFlags MDnsSdListener::Handler::iToFlags(int /* i */) {
@@ -406,7 +410,7 @@
 int MDnsSdListener::Handler::runCommand(SocketClient *cli,
                                         int argc, char **argv) {
     if (argc < 2) {
-        char* msg = NULL;
+        char* msg = nullptr;
         asprintf( &msg, "Invalid number of arguments to mdnssd: %i", argc);
         ALOGW("%s", msg);
         cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
@@ -422,10 +426,10 @@
                     "Invalid number of arguments to mdnssd discover", false);
             return 0;
         }
-        int requestId = atoi(argv[2]);
+        int requestId = strtol(argv[2], nullptr, 10);
         char *serviceType = argv[3];
 
-        discover(cli, NULL, serviceType, NULL, requestId, 0);
+        discover(cli, nullptr, serviceType, nullptr, requestId, 0);
     } else if (strcmp(cmd, "stop-discover") == 0) {
         stop(cli, argc, argv, "discover");
     } else if (strcmp(cmd, "register") == 0) {
@@ -437,10 +441,10 @@
         int requestId = atoi(argv[2]);
         char *serviceName = argv[3];
         char *serviceType = argv[4];
-        int port = atoi(argv[5]);
-        char *interfaceName = NULL; // will use all
-        char *domain = NULL;        // will use default
-        char *host = NULL;          // will use default hostname
+        int port = strtol(argv[5], nullptr, 10);
+        char *interfaceName = nullptr; // will use all
+        char *domain = nullptr;        // will use default
+        char *host = nullptr;          // will use default hostname
 
         // TXT record length is <= 1300, see NsdServiceInfo.setAttribute
         char dst[1300];
@@ -464,7 +468,7 @@
             return 0;
         }
         int requestId = atoi(argv[2]);
-        char *interfaceName = NULL;  // will use all
+        char *interfaceName = nullptr;  // will use all
         char *serviceName = argv[3];
         char *regType = argv[4];
         char *domain = argv[5];
@@ -489,7 +493,7 @@
                     "Invalid number of arguments to mdnssd sethostname", false);
             return 0;
         }
-        int requestId = atoi(argv[2]);
+        int requestId = strtol(argv[2], nullptr, 10);
         char *hostname = argv[3];
         setHostname(cli, requestId, hostname);
     } else if (strcmp(cmd, "stop-sethostname") == 0) {
@@ -502,7 +506,7 @@
         }
         int requestId = atoi(argv[2]);
         char *hostname = argv[3];
-        char *interfaceName = NULL;  // default
+        char *interfaceName = nullptr;  // default
         int protocol = 0;            // intelligient heuristic (both v4 + v6)
         getAddrInfo(cli, requestId, interfaceName, protocol, hostname);
     } else if (strcmp(cmd, "stop-getaddrinfo") == 0) {
@@ -516,15 +520,14 @@
 }
 
 MDnsSdListener::Monitor::Monitor() {
-    mHead = NULL;
+    mHead = nullptr;
     mLiveCount = 0;
-    mPollFds = NULL;
-    mPollRefs = NULL;
+    mPollFds = nullptr;
+    mPollRefs = nullptr;
     mPollSize = 10;
-    socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);
-    pthread_mutex_init(&mHeadMutex, NULL);
+    socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, mCtrlSocketPair);
 
-    const int rval = ::android::net::threadLaunch(this);
+    const int rval = ::android::netdutils::threadLaunch(this);
     if (rval != 0) {
         ALOGW("Error spawning monitor thread: %s (%d)", strerror(-rval), -rval);
     }
@@ -542,8 +545,8 @@
 
     while (maxnaps-- > 0) {
         usleep(NAP_TIME * 1000);
-        if (property_get(name, value, NULL)) {
-            if (desired_value == NULL || strcmp(value, desired_value) == 0) {
+        if (property_get(name, value, nullptr)) {
+            if (desired_value == nullptr || strcmp(value, desired_value) == 0) {
                 return 0;
             }
         }
@@ -552,35 +555,27 @@
 }
 
 int MDnsSdListener::Monitor::startService() {
-    int result = 0;
     char property_value[PROPERTY_VALUE_MAX];
-    pthread_mutex_lock(&mHeadMutex);
+    std::lock_guard guard(mMutex);
     property_get(MDNS_SERVICE_STATUS, property_value, "");
     if (strcmp("running", property_value) != 0) {
         ALOGD("Starting MDNSD");
         property_set("ctl.start", MDNS_SERVICE_NAME);
         wait_for_property(MDNS_SERVICE_STATUS, "running", 5);
-        result = -1;
-    } else {
-        result = 0;
+        return -1;
     }
-    pthread_mutex_unlock(&mHeadMutex);
-    return result;
+    return 0;
 }
 
 int MDnsSdListener::Monitor::stopService() {
-    int result = 0;
-    pthread_mutex_lock(&mHeadMutex);
-    if (mHead == NULL) {
+    std::lock_guard guard(mMutex);
+    if (mHead == nullptr) {
         ALOGD("Stopping MDNSD");
         property_set("ctl.stop", MDNS_SERVICE_NAME);
         wait_for_property(MDNS_SERVICE_STATUS, "stopped", 5);
-        result = -1;
-    } else {
-        result = 0;
+        return -1;
     }
-    pthread_mutex_unlock(&mHeadMutex);
-    return result;
+    return 0;
 }
 
 void MDnsSdListener::Monitor::run() {
@@ -588,9 +583,9 @@
 
     mPollFds = (struct pollfd *)calloc(sizeof(struct pollfd), mPollSize);
     mPollRefs = (DNSServiceRef **)calloc(sizeof(DNSServiceRef *), mPollSize);
-    LOG_ALWAYS_FATAL_IF((mPollFds == NULL), "initial calloc failed on mPollFds with a size of %d",
+    LOG_ALWAYS_FATAL_IF((mPollFds == nullptr), "initial calloc failed on mPollFds with a size of %d",
             ((int)sizeof(struct pollfd)) * mPollSize);
-    LOG_ALWAYS_FATAL_IF((mPollRefs == NULL), "initial calloc failed on mPollRefs with a size of %d",
+    LOG_ALWAYS_FATAL_IF((mPollRefs == nullptr), "initial calloc failed on mPollRefs with a size of %d",
             ((int)sizeof(DNSServiceRef *)) * mPollSize);
 
     mPollFds[0].fd = mCtrlSocketPair[0];
@@ -610,6 +605,7 @@
                         ALOGD("Monitor found [%d].revents = %d - calling ProcessResults",
                                 i, mPollFds[i].revents);
                     }
+                    std::lock_guard guard(mMutex);
                     DNSServiceProcessResult(*(mPollRefs[i]));
                     mPollFds[i].revents = 0;
                 }
@@ -641,7 +637,7 @@
     if (VDBG) {
         ALOGD("MDnsSdListener::Monitor poll rescanning - size=%d, live=%d", mPollSize, mLiveCount);
     }
-    pthread_mutex_lock(&mHeadMutex);
+    std::lock_guard guard(mMutex);
     Element **prevPtr = &mHead;
     int i = 1;
     if (mPollSize <= mLiveCount) {
@@ -650,9 +646,9 @@
         free(mPollRefs);
         mPollFds = (struct pollfd *)calloc(sizeof(struct pollfd), mPollSize);
         mPollRefs = (DNSServiceRef **)calloc(sizeof(DNSServiceRef *), mPollSize);
-        LOG_ALWAYS_FATAL_IF((mPollFds == NULL), "calloc failed on mPollFds with a size of %d",
+        LOG_ALWAYS_FATAL_IF((mPollFds == nullptr), "calloc failed on mPollFds with a size of %d",
                 ((int)sizeof(struct pollfd)) * mPollSize);
-        LOG_ALWAYS_FATAL_IF((mPollRefs == NULL), "calloc failed on mPollRefs with a size of %d",
+        LOG_ALWAYS_FATAL_IF((mPollRefs == nullptr), "calloc failed on mPollRefs with a size of %d",
                 ((int)sizeof(DNSServiceRef *)) * mPollSize);
     } else {
         memset(mPollFds, 0, sizeof(struct pollfd) * mPollSize);
@@ -661,7 +657,7 @@
     mPollFds[0].fd = mCtrlSocketPair[0];
     mPollFds[0].events = POLLIN;
     if (DBG_RESCAN) ALOGD("mHead = %p", mHead);
-    while (*prevPtr != NULL) {
+    while (*prevPtr != nullptr) {
         if (DBG_RESCAN) ALOGD("checking %p, mReady = %d", *prevPtr, (*prevPtr)->mReady);
         if ((*prevPtr)->mReady == 1) {
             int fd = DNSServiceRefSockFD((*prevPtr)->mRef);
@@ -686,80 +682,75 @@
             prevPtr = &((*prevPtr)->mNext);
         }
     }
-    pthread_mutex_unlock(&mHeadMutex);
+
     return i;
 }
 
 DNSServiceRef *MDnsSdListener::Monitor::allocateServiceRef(int id, Context *context) {
-    if (lookupServiceRef(id) != NULL) {
+    if (lookupServiceRef(id) != nullptr) {
         delete(context);
-        return NULL;
+        return nullptr;
     }
     Element *e = new Element(id, context);
-    pthread_mutex_lock(&mHeadMutex);
+    std::lock_guard guard(mMutex);
     e->mNext = mHead;
     mHead = e;
-    pthread_mutex_unlock(&mHeadMutex);
     return &(e->mRef);
 }
 
 DNSServiceRef *MDnsSdListener::Monitor::lookupServiceRef(int id) {
-    pthread_mutex_lock(&mHeadMutex);
+    std::lock_guard guard(mMutex);
     Element *cur = mHead;
-    while (cur != NULL) {
+    while (cur != nullptr) {
         if (cur->mId == id) {
             DNSServiceRef *result = &(cur->mRef);
-            pthread_mutex_unlock(&mHeadMutex);
             return result;
         }
         cur = cur->mNext;
     }
-    pthread_mutex_unlock(&mHeadMutex);
-    return NULL;
+    return nullptr;
 }
 
 void MDnsSdListener::Monitor::startMonitoring(int id) {
     if (VDBG) ALOGD("startMonitoring %d", id);
-    pthread_mutex_lock(&mHeadMutex);
-    Element *cur = mHead;
-    while (cur != NULL) {
+    std::lock_guard guard(mMutex);
+    for (Element* cur = mHead; cur != nullptr; cur = cur->mNext) {
         if (cur->mId == id) {
             if (DBG_RESCAN) ALOGD("marking %p as ready to be added", cur);
             mLiveCount++;
             cur->mReady = 1;
-            pthread_mutex_unlock(&mHeadMutex);
             write(mCtrlSocketPair[1], RESCAN, 1);  // trigger a rescan for a fresh poll
             if (VDBG) ALOGD("triggering rescan");
             return;
         }
-        cur = cur->mNext;
     }
-    pthread_mutex_unlock(&mHeadMutex);
 }
 
 void MDnsSdListener::Monitor::freeServiceRef(int id) {
     if (VDBG) ALOGD("freeServiceRef %d", id);
-    pthread_mutex_lock(&mHeadMutex);
-    Element **prevPtr = &mHead;
-    Element *cur;
-    while (*prevPtr != NULL) {
+    std::lock_guard guard(mMutex);
+    Element* cur;
+    for (Element** prevPtr = &mHead; *prevPtr != nullptr; prevPtr = &(cur->mNext)) {
         cur = *prevPtr;
         if (cur->mId == id) {
             if (DBG_RESCAN) ALOGD("marking %p as ready to be removed", cur);
             mLiveCount--;
             if (cur->mReady == 1) {
                 cur->mReady = -1; // tell poll thread to delete
-                cur->mRef = NULL; // do not process further results
+                cur->mRef = nullptr; // do not process further results
                 write(mCtrlSocketPair[1], RESCAN, 1); // trigger a rescan for a fresh poll
                 if (VDBG) ALOGD("triggering rescan");
             } else {
                 *prevPtr = cur->mNext;
                 delete cur;
             }
-            pthread_mutex_unlock(&mHeadMutex);
             return;
         }
-        prevPtr = &(cur->mNext);
     }
-    pthread_mutex_unlock(&mHeadMutex);
+}
+
+void MDnsSdListener::Monitor::deallocateServiceRef(DNSServiceRef* ref) {
+    std::lock_guard guard(mMutex);
+    DNSServiceRefDeallocate(*ref);
+    *ref = nullptr;
 }
diff --git a/server/MDnsSdListener.h b/server/MDnsSdListener.h
index 8cd596a..47ddc28 100644
--- a/server/MDnsSdListener.h
+++ b/server/MDnsSdListener.h
@@ -17,9 +17,10 @@
 #ifndef _MDNSSDLISTENER_H__
 #define _MDNSSDLISTENER_H__
 
-#include <pthread.h>
-#include <sysutils/FrameworkListener.h>
+#include <android-base/thread_annotations.h>
 #include <dns_sd.h>
+#include <sysutils/FrameworkListener.h>
+#include <mutex>
 
 #include "NetdCommand.h"
 
@@ -44,17 +45,15 @@
         uint32_t interface, DNSServiceErrorType errorCode, const char *hostname,
         const struct sockaddr *const sa, uint32_t ttl, void *inContext);
 
-#define RESCAN "1"
-
 class MDnsSdListener : public FrameworkListener {
-public:
+  public:
     MDnsSdListener();
     virtual ~MDnsSdListener() {}
 
     static constexpr const char* SOCKET_NAME = "mdns";
 
     class Context {
-    public:
+      public:
         MDnsSdListener *mListener;
         int mRefNumber;
 
@@ -79,27 +78,28 @@
         int startService();
         int stopService();
         void run();
+        void deallocateServiceRef(DNSServiceRef* ref);
 
-    private:
+      private:
         int rescan(); // returns the number of elements in the poll
-        class Element {
-        public:
+
+        struct Element {
+            Element(int id, Context* context) : mId(id), mContext(context) {}
+            ~Element() { delete mContext; }
+
             int mId;
-            Element *mNext;
-            DNSServiceRef mRef;
+            Element* mNext = nullptr;
+            DNSServiceRef mRef = nullptr;
             Context *mContext;
-            int mReady;
-            Element(int id, Context *context)
-                    : mId(id), mNext(NULL), mContext(context), mReady(0) {}
-            virtual ~Element() { delete(mContext); }
+            int mReady = 0;
         };
-        Element *mHead;
+        Element* mHead GUARDED_BY(mMutex);
         int mLiveCount;
         struct pollfd *mPollFds;
         DNSServiceRef **mPollRefs;
         int mPollSize;
         int mCtrlSocketPair[2];
-        pthread_mutex_t mHeadMutex;
+        std::mutex mMutex;
     };
 
     class Handler : public NetdCommand {
diff --git a/server/NFLogListener.cpp b/server/NFLogListener.cpp
index 7e3242f..76972b6 100644
--- a/server/NFLogListener.cpp
+++ b/server/NFLogListener.cpp
@@ -22,7 +22,7 @@
 #include <arpa/inet.h>
 #include <linux/netfilter/nfnetlink_log.h>
 
-#include <cutils/log.h>
+#include <log/log.h>
 #include <netdutils/Misc.h>
 #include <netdutils/Netfilter.h>
 #include <netdutils/Syscalls.h>
@@ -32,16 +32,14 @@
 namespace android {
 namespace net {
 
+using netdutils::extract;
+using netdutils::findWithDefault;
+using netdutils::makeSlice;
 using netdutils::Slice;
+using netdutils::sSyscalls;
 using netdutils::Status;
 using netdutils::StatusOr;
-using netdutils::UniqueFd;
-using netdutils::Status;
-using netdutils::makeSlice;
-using netdutils::sSyscalls;
-using netdutils::findWithDefault;
 using netdutils::status::ok;
-using netdutils::extract;
 
 constexpr int kNFLogConfigMsgType = (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG;
 constexpr int kNFLogPacketMsgType = (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET;
@@ -149,7 +147,7 @@
     const auto rxHandler = [this](const nlmsghdr& nlmsg, const Slice msg) {
         nfgenmsg nfmsg = {};
         extract(msg, nfmsg);
-        std::lock_guard<std::mutex> guard(mMutex);
+        std::lock_guard guard(mMutex);
         const auto& fn = findWithDefault(mDispatchMap, ntohs(nfmsg.res_id), kDefaultDispatchFn);
         fn(nlmsg, nfmsg, drop(msg, sizeof(nfmsg)));
     };
@@ -169,8 +167,8 @@
     expectOk(mListener->unsubscribe(kNFLogPacketMsgType));
     expectOk(mListener->unsubscribe(kNetlinkDoneMsgType));
     const auto sendFn = [this](const Slice msg) { return mListener->send(msg); };
-    for (auto pair : mDispatchMap) {
-        expectOk(cfgCmdUnbind(sendFn, pair.first));
+    for (const auto& [key, value] : mDispatchMap) {
+        expectOk(cfgCmdUnbind(sendFn, key));
     }
 }
 
@@ -183,7 +181,7 @@
     const auto sendFn = [this](const Slice msg) { return mListener->send(msg); };
     // Install fn into the dispatch map BEFORE requesting delivery of messages
     {
-        std::lock_guard<std::mutex> guard(mMutex);
+        std::lock_guard guard(mMutex);
         mDispatchMap[nfLogGroup] = fn;
     }
     RETURN_IF_NOT_OK(cfgCmdBind(sendFn, nfLogGroup));
@@ -198,7 +196,7 @@
     RETURN_IF_NOT_OK(cfgCmdUnbind(sendFn, nfLogGroup));
     // Remove from the dispatch map AFTER stopping message delivery.
     {
-        std::lock_guard<std::mutex> guard(mMutex);
+        std::lock_guard guard(mMutex);
         mDispatchMap.erase(nfLogGroup);
     }
     return ok;
@@ -216,7 +214,7 @@
     RETURN_IF_NOT_OK(sys.setsockopt<int32_t>(sock, SOL_SOCKET, SO_TIMESTAMP, 1));
 
     std::shared_ptr<NetlinkListenerInterface> listener =
-        std::make_unique<NetlinkListener>(std::move(event), std::move(sock));
+            std::make_unique<NetlinkListener>(std::move(event), std::move(sock), "NFLogListener");
     const auto sendFn = [&listener](const Slice msg) { return listener->send(msg); };
     RETURN_IF_NOT_OK(cfgCmdPfUnbind(sendFn));
     return std::unique_ptr<NFLogListener>(new NFLogListener(std::move(listener)));
diff --git a/server/NFLogListener.h b/server/NFLogListener.h
index 7eb9d10..459b5cf 100644
--- a/server/NFLogListener.h
+++ b/server/NFLogListener.h
@@ -20,6 +20,7 @@
 #include <netdutils/Netfilter.h>
 
 #include "NetlinkListener.h"
+#include "netdutils/StatusOr.h"
 
 namespace android {
 namespace net {
diff --git a/server/NFLogListenerTest.cpp b/server/NFLogListenerTest.cpp
index c7ee439..f3bd810 100644
--- a/server/NFLogListenerTest.cpp
+++ b/server/NFLogListenerTest.cpp
@@ -27,24 +27,19 @@
 #include <netdutils/MockSyscalls.h>
 #include "NFLogListener.h"
 
-using ::testing::ByMove;
+using ::testing::_;
+using ::testing::DoAll;
 using ::testing::Exactly;
 using ::testing::Invoke;
-using ::testing::Mock;
-using ::testing::SaveArg;
-using ::testing::DoAll;
 using ::testing::Return;
+using ::testing::SaveArg;
 using ::testing::StrictMock;
-using ::testing::_;
 
 namespace android {
 namespace net {
 
-using netdutils::Fd;
 using netdutils::Slice;
 using netdutils::StatusOr;
-using netdutils::UniqueFd;
-using netdutils::forEachNetlinkAttribute;
 using netdutils::makeSlice;
 using netdutils::status::ok;
 
@@ -55,10 +50,11 @@
   public:
     ~MockNetlinkListener() override = default;
 
-    MOCK_METHOD1(send, netdutils::Status(const netdutils::Slice msg));
+    MOCK_METHOD1(send, netdutils::Status(const Slice msg));
     MOCK_METHOD2(subscribe, netdutils::Status(uint16_t type, const DispatchFn& fn));
     MOCK_METHOD1(unsubscribe, netdutils::Status(uint16_t type));
     MOCK_METHOD0(join, void());
+    MOCK_METHOD1(registerSkErrorHandler, void(const SkErrorHandler& handler));
 };
 
 class NFLogListenerTest : public testing::Test {
@@ -78,10 +74,10 @@
 
     static StatusOr<size_t> sendOk(const Slice buf) { return buf.size(); }
 
-    void subscribe(uint16_t type, NFLogListenerInterface::DispatchFn fn) {
+    void subscribe(uint16_t type, const NFLogListenerInterface::DispatchFn& fn) {
         // Two sends for cfgCmdBind() & cfgMode(), one send at destruction time for cfgCmdUnbind()
         EXPECT_CALL(*mNLListener, send(_)).Times(Exactly(3)).WillRepeatedly(Invoke(sendOk));
-        mListener->subscribe(type, fn);
+        EXPECT_OK(mListener->subscribe(type, fn));
     }
 
     void sendEmptyMsg(uint16_t type) {
@@ -105,13 +101,13 @@
 
 TEST_F(NFLogListenerTest, subscribe) {
     constexpr uint16_t kType = 38;
-    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const netdutils::Slice) {};
+    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const Slice) {};
     subscribe(kType, dispatchFn);
 }
 
 TEST_F(NFLogListenerTest, nlmsgDone) {
     constexpr uint16_t kType = 38;
-    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const netdutils::Slice) {};
+    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const Slice) {};
     subscribe(kType, dispatchFn);
     mDoneFn({}, {});
 }
@@ -120,7 +116,7 @@
     int invocations = 0;
     constexpr uint16_t kType = 38;
     const auto dispatchFn = [&invocations, kType](const nlmsghdr&, const nfgenmsg& nfmsg,
-                                                  const netdutils::Slice) {
+                                                  const Slice) {
         EXPECT_EQ(kType, ntohs(nfmsg.res_id));
         ++invocations;
     };
@@ -132,7 +128,7 @@
 TEST_F(NFLogListenerTest, dispatchUnknownType) {
     constexpr uint16_t kType = 38;
     constexpr uint16_t kBadType = kType + 1;
-    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const netdutils::Slice) {
+    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const Slice) {
         // Expect no invocations
         ASSERT_TRUE(false);
     };
diff --git a/server/NdcDispatcher.cpp b/server/NdcDispatcher.cpp
new file mode 100644
index 0000000..7692a9c
--- /dev/null
+++ b/server/NdcDispatcher.cpp
@@ -0,0 +1,1239 @@
+/*
+ * 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 "NdcDispatcher.h"
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <linux/if.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <cinttypes>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/multinetwork.h>
+#include <netdutils/ResponseCode.h>
+#include <netdutils/Status.h>
+#include <netdutils/StatusOr.h>
+#include <netutils/ifc.h>
+
+#include "NetdConstants.h"
+#include "NetworkController.h"
+#include "Permission.h"
+#include "UidRanges.h"
+#include "netid_client.h"
+
+using android::base::Join;
+using android::base::StringPrintf;
+using android::binder::Status;
+
+#define PARSE_INT_RETURN_IF_FAIL(cli, label, intLabel, errMsg, addErrno)   \
+    do {                                                                   \
+        if (!android::base::ParseInt(label, &intLabel)) {                  \
+            errno = EINVAL;                                                \
+            cli->sendMsg(ResponseCode::OperationFailed, errMsg, addErrno); \
+            return 0;                                                      \
+        }                                                                  \
+    } while (0)
+
+#define PARSE_UINT_RETURN_IF_FAIL(cli, label, intLabel, errMsg, addErrno)  \
+    do {                                                                   \
+        if (!android::base::ParseUint(label, &intLabel)) {                 \
+            errno = EINVAL;                                                \
+            cli->sendMsg(ResponseCode::OperationFailed, errMsg, addErrno); \
+            return 0;                                                      \
+        }                                                                  \
+    } while (0)
+
+namespace android {
+
+using netdutils::ResponseCode;
+
+namespace net {
+namespace {
+
+const unsigned NUM_OEM_IDS = NetworkController::MAX_OEM_ID - NetworkController::MIN_OEM_ID + 1;
+
+unsigned stringToNetId(const char* arg) {
+    if (!strcmp(arg, "local")) {
+        return NetworkController::LOCAL_NET_ID;
+    }
+    // OEM NetIds are "oem1", "oem2", .., "oem50".
+    if (!strncmp(arg, "oem", 3)) {
+        unsigned n = strtoul(arg + 3, nullptr, 0);
+        if (1 <= n && n <= NUM_OEM_IDS) {
+            return NetworkController::MIN_OEM_ID + n;
+        }
+        return NETID_UNSET;
+    } else if (!strncmp(arg, "handle", 6)) {
+        unsigned n = netHandleToNetId((net_handle_t)strtoull(arg + 6, nullptr, 10));
+        if (NetworkController::MIN_OEM_ID <= n && n <= NetworkController::MAX_OEM_ID) {
+            return n;
+        }
+        return NETID_UNSET;
+    }
+    // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
+    return strtoul(arg, nullptr, 0);
+}
+
+std::string toStdString(const String16& s) {
+    return std::string(String8(s.string()));
+}
+
+int stringToINetdPermission(const char* arg) {
+    if (!strcmp(arg, "NETWORK")) {
+        return INetd::PERMISSION_NETWORK;
+    }
+    if (!strcmp(arg, "SYSTEM")) {
+        return INetd::PERMISSION_SYSTEM;
+    }
+    return INetd::PERMISSION_NONE;
+}
+
+}  // namespace
+
+sp<INetd> NdcDispatcher::mNetd;
+sp<IDnsResolver> NdcDispatcher::mDnsResolver;
+
+NdcDispatcher::NdcDispatcher() {
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binderNetd = sm->getService(String16("netd"));
+    sp<IBinder> binderDnsResolver = sm->getService(String16("dnsresolver"));
+    if ((binderNetd != nullptr) && (binderDnsResolver != nullptr)) {
+        NdcDispatcher::mNetd = interface_cast<INetd>(binderNetd);
+        NdcDispatcher::mDnsResolver = interface_cast<IDnsResolver>(binderDnsResolver);
+    } else {
+        LOG(LOGLEVEL) << "Unable to get binder service";
+        exit(1);
+    }
+    registerCmd(new InterfaceCmd());
+    registerCmd(new IpFwdCmd());
+    registerCmd(new TetherCmd());
+    registerCmd(new NatCmd());
+    registerCmd(new BandwidthControlCmd());
+    registerCmd(new IdletimerControlCmd());
+    registerCmd(new FirewallCmd());
+    registerCmd(new ClatdCmd());
+    registerCmd(new NetworkCommand());
+    registerCmd(new StrictCmd());
+}
+
+void NdcDispatcher::registerCmd(NdcNetdCommand* cmd) {
+    mCommands.push_back(cmd);
+}
+
+int NdcDispatcher::dispatchCommand(int argc, char** argv) {
+    if (argc >= CMD_ARGS_MAX) {
+        mNdc.sendMsg(500, "Command too long", false);
+    }
+
+    for (const auto* c : mCommands) {
+        if (c->getCommand() == argv[0]) {
+            if (c->runCommand(&mNdc, argc, argv)) {
+                mNdc.sendMsg(500, "Handler error", true);
+            }
+            return 0;
+        }
+    }
+    mNdc.sendMsg(500, "Command not recognized", false);
+    return 0;
+}
+
+NdcDispatcher::InterfaceCmd::InterfaceCmd() : NdcNetdCommand("interface") {}
+
+int NdcDispatcher::InterfaceCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "list")) {
+        std::vector<std::string> interfaceGetList;
+        Status status = mNetd->interfaceGetList(&interfaceGetList);
+
+        if (!status.isOk()) {
+            errno = status.serviceSpecificErrorCode();
+            cli->sendMsg(ResponseCode::OperationFailed, "Failed to get interface list", true);
+            return 0;
+        }
+        for (const auto& iface : interfaceGetList) {
+            cli->sendMsg(ResponseCode::InterfaceListResult, iface.c_str(), false);
+        }
+
+        cli->sendMsg(ResponseCode::CommandOkay, "Interface list completed", false);
+        return 0;
+    } else {
+        /*
+         * These commands take a minimum of 3 arguments
+         */
+        if (argc < 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+
+        if (!strcmp(argv[1], "getcfg")) {
+            InterfaceConfigurationParcel interfaceCfgResult;
+            Status status = mNetd->interfaceGetCfg(std::string(argv[2]), &interfaceCfgResult);
+
+            if (!status.isOk()) {
+                errno = status.serviceSpecificErrorCode();
+                cli->sendMsg(ResponseCode::OperationFailed, "Interface not found", true);
+                return 0;
+            }
+
+            std::string flags = Join(interfaceCfgResult.flags, " ");
+
+            std::string msg = StringPrintf("%s %s %d %s", interfaceCfgResult.hwAddr.c_str(),
+                                           interfaceCfgResult.ipv4Addr.c_str(),
+                                           interfaceCfgResult.prefixLength, flags.c_str());
+
+            cli->sendMsg(ResponseCode::InterfaceGetCfgResult, msg.c_str(), false);
+
+            return 0;
+        } else if (!strcmp(argv[1], "setcfg")) {
+            // arglist: iface [addr prefixLength] flags
+            if (argc < 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+                return 0;
+            }
+            LOG(LOGLEVEL) << "Setting iface cfg";
+
+            struct in_addr addr;
+            int index = 5;
+            InterfaceConfigurationParcel interfaceCfg;
+            interfaceCfg.ifName = argv[2];
+            interfaceCfg.hwAddr = "";
+
+            if (!inet_aton(argv[3], &addr)) {
+                // Handle flags only case
+                index = 3;
+                interfaceCfg.ipv4Addr = "";
+                interfaceCfg.prefixLength = 0;
+            } else {
+                if (addr.s_addr != 0) {
+                    interfaceCfg.ipv4Addr = argv[3];
+                    PARSE_INT_RETURN_IF_FAIL(cli, argv[4], interfaceCfg.prefixLength,
+                                             "Failed to set address", true);
+                    Status status = mNetd->interfaceSetCfg(interfaceCfg);
+                    if (!status.isOk()) {
+                        errno = status.serviceSpecificErrorCode();
+                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to set address", true);
+                        return 0;
+                    }
+                }
+            }
+
+            /* Process flags */
+            for (int i = index; i < argc; i++) {
+                char* flag = argv[i];
+                if (!strcmp(flag, "up")) {
+                    LOG(LOGLEVEL) << "Trying to bring up " << argv[2];
+                    interfaceCfg.flags.push_back(toStdString(INetd::IF_STATE_UP()));
+                    Status status = mNetd->interfaceSetCfg(interfaceCfg);
+                    if (!status.isOk()) {
+                        LOG(LOGLEVEL) << "Error upping interface";
+                        errno = status.serviceSpecificErrorCode();
+                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to up interface", true);
+                        ifc_close();
+                        return 0;
+                    }
+                } else if (!strcmp(flag, "down")) {
+                    LOG(LOGLEVEL) << "Trying to bring down " << argv[2];
+                    interfaceCfg.flags.push_back(toStdString(INetd::IF_STATE_DOWN()));
+                    Status status = mNetd->interfaceSetCfg(interfaceCfg);
+                    if (!status.isOk()) {
+                        LOG(LOGLEVEL) << "Error downing interface";
+                        errno = status.serviceSpecificErrorCode();
+                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to down interface",
+                                     true);
+                        return 0;
+                    }
+                } else if (!strcmp(flag, "broadcast")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "multicast")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "running")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "loopback")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "point-to-point")) {
+                    // currently ignored
+                } else {
+                    cli->sendMsg(ResponseCode::CommandParameterError, "Flag unsupported", false);
+                    return 0;
+                }
+            }
+
+            cli->sendMsg(ResponseCode::CommandOkay, "Interface configuration set", false);
+            return 0;
+        } else if (!strcmp(argv[1], "clearaddrs")) {
+            // arglist: iface
+            LOG(LOGLEVEL) << "Clearing all IP addresses on " << argv[2];
+
+            mNetd->interfaceClearAddrs(std::string(argv[2]));
+
+            cli->sendMsg(ResponseCode::CommandOkay, "Interface IP addresses cleared", false);
+            return 0;
+        } else if (!strcmp(argv[1], "ipv6privacyextensions")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                             "Usage: interface ipv6privacyextensions <interface> <enable|disable>",
+                             false);
+                return 0;
+            }
+            int enable = !strncmp(argv[3], "enable", 7);
+            Status status = mNetd->interfaceSetIPv6PrivacyExtensions(std::string(argv[2]), enable);
+            if (status.isOk()) {
+                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 privacy extensions changed", false);
+            } else {
+                errno = status.serviceSpecificErrorCode();
+                cli->sendMsg(ResponseCode::OperationFailed, "Failed to set ipv6 privacy extensions",
+                             true);
+            }
+            return 0;
+        } else if (!strcmp(argv[1], "ipv6")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                             "Usage: interface ipv6 <interface> <enable|disable>", false);
+                return 0;
+            }
+
+            int enable = !strncmp(argv[3], "enable", 7);
+            Status status = mNetd->interfaceSetEnableIPv6(std::string(argv[2]), enable);
+            if (status.isOk()) {
+                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 state changed", false);
+            } else {
+                errno = status.serviceSpecificErrorCode();
+                cli->sendMsg(ResponseCode::OperationFailed, "Failed to change IPv6 state", true);
+            }
+            return 0;
+        } else if (!strcmp(argv[1], "setmtu")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                             "Usage: interface setmtu <interface> <val>", false);
+                return 0;
+            }
+
+            int mtuValue = 0;
+            PARSE_INT_RETURN_IF_FAIL(cli, argv[3], mtuValue, "Failed to set MTU", true);
+            Status status = mNetd->interfaceSetMtu(std::string(argv[2]), mtuValue);
+            if (status.isOk()) {
+                cli->sendMsg(ResponseCode::CommandOkay, "MTU changed", false);
+            } else {
+                errno = status.serviceSpecificErrorCode();
+                cli->sendMsg(ResponseCode::OperationFailed, "Failed to set MTU", true);
+            }
+            return 0;
+        } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown interface cmd", false);
+            return 0;
+        }
+    }
+    return 0;
+}
+
+NdcDispatcher::IpFwdCmd::IpFwdCmd() : NdcNetdCommand("ipfwd") {}
+
+int NdcDispatcher::IpFwdCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    bool matched = false;
+    Status status;
+
+    if (argc == 2) {
+        //   0     1
+        // ipfwd status
+        if (!strcmp(argv[1], "status")) {
+            bool ipfwdEnabled;
+            mNetd->ipfwdEnabled(&ipfwdEnabled);
+            std::string msg = StringPrintf("Forwarding %s", ipfwdEnabled ? "enabled" : "disabled");
+            cli->sendMsg(ResponseCode::IpFwdStatusResult, msg.c_str(), false);
+            return 0;
+        }
+    } else if (argc == 3) {
+        //  0      1         2
+        // ipfwd enable  <requester>
+        // ipfwd disable <requester>
+        if (!strcmp(argv[1], "enable")) {
+            matched = true;
+            status = mNetd->ipfwdEnableForwarding(argv[2]);
+        } else if (!strcmp(argv[1], "disable")) {
+            matched = true;
+            status = mNetd->ipfwdDisableForwarding(argv[2]);
+        }
+    } else if (argc == 4) {
+        //  0      1      2     3
+        // ipfwd  add   wlan0 dummy0
+        // ipfwd remove wlan0 dummy0
+        if (!strcmp(argv[1], "add")) {
+            matched = true;
+            status = mNetd->ipfwdAddInterfaceForward(argv[2], argv[3]);
+        } else if (!strcmp(argv[1], "remove")) {
+            matched = true;
+            status = mNetd->ipfwdRemoveInterfaceForward(argv[2], argv[3]);
+        }
+    }
+
+    if (!matched) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown ipfwd cmd", false);
+        return 0;
+    }
+
+    if (status.isOk()) {
+        cli->sendMsg(ResponseCode::CommandOkay, "ipfwd operation succeeded", false);
+    } else {
+        errno = status.serviceSpecificErrorCode();
+        cli->sendMsg(ResponseCode::OperationFailed, "ipfwd operation failed", true);
+    }
+    return 0;
+}
+
+NdcDispatcher::TetherCmd::TetherCmd() : NdcNetdCommand("tether") {}
+
+int NdcDispatcher::TetherCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    Status status;
+
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "stop")) {
+        status = mNetd->tetherStop();
+    } else if (!strcmp(argv[1], "status")) {
+        bool tetherEnabled;
+        mNetd->tetherIsEnabled(&tetherEnabled);
+        std::string msg =
+                StringPrintf("Tethering services %s", tetherEnabled ? "started" : "stopped");
+        cli->sendMsg(ResponseCode::TetherStatusResult, msg.c_str(), false);
+        return 0;
+    } else if (argc == 3) {
+        if (!strcmp(argv[1], "interface") && !strcmp(argv[2], "list")) {
+            std::vector<std::string> ifList;
+            mNetd->tetherInterfaceList(&ifList);
+            for (const auto& ifname : ifList) {
+                cli->sendMsg(ResponseCode::TetherInterfaceListResult, ifname.c_str(), false);
+            }
+        }
+    } else if (!strcmp(argv[1], "start")) {
+        if (argc % 2 == 1) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Bad number of arguments", false);
+            return 0;
+        }
+
+        std::vector<std::string> dhcpRanges;
+        // We do the checking of the pairs & addr invalidation in binderService/tetherController.
+        for (int arg_index = 2; arg_index < argc; arg_index++) {
+            dhcpRanges.push_back(argv[arg_index]);
+        }
+
+        status = mNetd->tetherStart(dhcpRanges);
+    } else {
+        /*
+         * These commands take a minimum of 4 arguments
+         */
+        if (argc < 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+
+        if (!strcmp(argv[1], "interface")) {
+            if (!strcmp(argv[2], "add")) {
+                status = mNetd->tetherInterfaceAdd(argv[3]);
+            } else if (!strcmp(argv[2], "remove")) {
+                status = mNetd->tetherInterfaceRemove(argv[3]);
+                /* else if (!strcmp(argv[2], "list")) handled above */
+            } else {
+                cli->sendMsg(ResponseCode::CommandParameterError,
+                             "Unknown tether interface operation", false);
+                return 0;
+            }
+        } else if (!strcmp(argv[1], "dns")) {
+            if (!strcmp(argv[2], "set")) {
+                if (argc < 5) {
+                    cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+                    return 0;
+                }
+                std::vector<std::string> tetherDnsAddrs;
+                unsigned netId = stringToNetId(argv[3]);
+                for (int arg_index = 4; arg_index < argc; arg_index++) {
+                    tetherDnsAddrs.push_back(argv[arg_index]);
+                }
+                status = mNetd->tetherDnsSet(netId, tetherDnsAddrs);
+                /* else if (!strcmp(argv[2], "list")) handled above */
+            } else {
+                cli->sendMsg(ResponseCode::CommandParameterError,
+                             "Unknown tether interface operation", false);
+                return 0;
+            }
+        } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown tether cmd", false);
+            return 0;
+        }
+    }
+
+    if (status.isOk()) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Tether operation succeeded", false);
+    } else {
+        errno = status.serviceSpecificErrorCode();
+        cli->sendMsg(ResponseCode::OperationFailed, "Tether operation failed", true);
+    }
+
+    return 0;
+}
+
+NdcDispatcher::NatCmd::NatCmd() : NdcNetdCommand("nat") {}
+
+int NdcDispatcher::NatCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    Status status;
+
+    if (argc < 5) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    //  0     1       2        3
+    // nat  enable intiface extiface
+    // nat disable intiface extiface
+    if (!strcmp(argv[1], "enable") && argc >= 4) {
+        status = mNetd->tetherAddForward(argv[2], argv[3]);
+    } else if (!strcmp(argv[1], "disable") && argc >= 4) {
+        status = mNetd->tetherRemoveForward(argv[2], argv[3]);
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown nat cmd", false);
+        return 0;
+    }
+
+    if (status.isOk()) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Nat operation succeeded", false);
+    } else {
+        errno = status.serviceSpecificErrorCode();
+        cli->sendMsg(ResponseCode::OperationFailed, "Nat operation failed", true);
+    }
+
+    return 0;
+}
+
+NdcDispatcher::BandwidthControlCmd::BandwidthControlCmd() : NdcNetdCommand("bandwidth") {}
+
+void NdcDispatcher::BandwidthControlCmd::sendGenericSyntaxError(NdcClient* cli,
+                                                                const char* usageMsg) const {
+    char* msg;
+    asprintf(&msg, "Usage: bandwidth %s", usageMsg);
+    cli->sendMsg(ResponseCode::CommandSyntaxError, msg, false);
+    free(msg);
+}
+
+void NdcDispatcher::BandwidthControlCmd::sendGenericOkFail(NdcClient* cli, int cond) const {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Bandwidth command succeeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Bandwidth command failed", false);
+    }
+}
+
+void NdcDispatcher::BandwidthControlCmd::sendGenericOpFailed(NdcClient* cli,
+                                                             const char* errMsg) const {
+    cli->sendMsg(ResponseCode::OperationFailed, errMsg, false);
+}
+
+int NdcDispatcher::BandwidthControlCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    if (argc < 2) {
+        sendGenericSyntaxError(cli, "<cmds> <args...>");
+        return 0;
+    }
+
+    LOG(LOGLEVEL) << StringPrintf("bwctrlcmd: argc=%d %s %s ...", argc, argv[0], argv[1]).c_str();
+
+    if (!strcmp(argv[1], "removeiquota") || !strcmp(argv[1], "riq")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "removeiquota <interface>");
+            return 0;
+        }
+        int rc = !mNetd->bandwidthRemoveInterfaceQuota(argv[2]).isOk();
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "setiquota") || !strcmp(argv[1], "siq")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "setiquota <interface> <bytes>");
+            return 0;
+        }
+        int64_t bytes = 0;
+        PARSE_INT_RETURN_IF_FAIL(cli, argv[3], bytes, "Bandwidth command failed", false);
+        int rc = !mNetd->bandwidthSetInterfaceQuota(argv[2], bytes).isOk();
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "addnaughtyapps") || !strcmp(argv[1], "ana")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "addnaughtyapps <appUid> ...");
+            return 0;
+        }
+        int rc = 0;
+        for (int arg_index = 2; arg_index < argc; arg_index++) {
+            uid_t uid = 0;
+            PARSE_UINT_RETURN_IF_FAIL(cli, argv[arg_index], uid, "Bandwidth command failed", false);
+            rc = !mNetd->bandwidthAddNaughtyApp(uid).isOk();
+            if (rc) break;
+        }
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "removenaughtyapps") || !strcmp(argv[1], "rna")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "removenaughtyapps <appUid> ...");
+            return 0;
+        }
+        int rc = 0;
+        for (int arg_index = 2; arg_index < argc; arg_index++) {
+            uid_t uid = 0;
+            PARSE_UINT_RETURN_IF_FAIL(cli, argv[arg_index], uid, "Bandwidth command failed", false);
+            rc = !mNetd->bandwidthRemoveNaughtyApp(uid).isOk();
+            if (rc) break;
+        }
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "addniceapps") || !strcmp(argv[1], "aha")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "addniceapps <appUid> ...");
+            return 0;
+        }
+        int rc = 0;
+        for (int arg_index = 2; arg_index < argc; arg_index++) {
+            uid_t uid = 0;
+            PARSE_UINT_RETURN_IF_FAIL(cli, argv[arg_index], uid, "Bandwidth command failed", false);
+            rc = !mNetd->bandwidthAddNiceApp(uid).isOk();
+            if (rc) break;
+        }
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "removeniceapps") || !strcmp(argv[1], "rha")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "removeniceapps <appUid> ...");
+            return 0;
+        }
+        int rc = 0;
+        for (int arg_index = 2; arg_index < argc; arg_index++) {
+            uid_t uid = 0;
+            PARSE_UINT_RETURN_IF_FAIL(cli, argv[arg_index], uid, "Bandwidth command failed", false);
+            rc = !mNetd->bandwidthRemoveNiceApp(uid).isOk();
+            if (rc) break;
+        }
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "setglobalalert") || !strcmp(argv[1], "sga")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "setglobalalert <bytes>");
+            return 0;
+        }
+        int64_t bytes = 0;
+        PARSE_INT_RETURN_IF_FAIL(cli, argv[2], bytes, "Bandwidth command failed", false);
+        int rc = !mNetd->bandwidthSetGlobalAlert(bytes).isOk();
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "setinterfacealert") || !strcmp(argv[1], "sia")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "setinterfacealert <interface> <bytes>");
+            return 0;
+        }
+        int64_t bytes = 0;
+        PARSE_INT_RETURN_IF_FAIL(cli, argv[3], bytes, "Bandwidth command failed", false);
+        int rc = !mNetd->bandwidthSetInterfaceAlert(argv[2], bytes).isOk();
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "removeinterfacealert") || !strcmp(argv[1], "ria")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "removeinterfacealert <interface>");
+            return 0;
+        }
+        int rc = !mNetd->bandwidthRemoveInterfaceAlert(argv[2]).isOk();
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown bandwidth cmd", false);
+    return 0;
+}
+
+NdcDispatcher::IdletimerControlCmd::IdletimerControlCmd() : NdcNetdCommand("idletimer") {}
+
+int NdcDispatcher::IdletimerControlCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    // TODO(ashish): Change the error statements
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    LOG(LOGLEVEL)
+            << StringPrintf("idletimerctrlcmd: argc=%d %s %s ...", argc, argv[0], argv[1]).c_str();
+
+    if (!strcmp(argv[1], "add")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+
+        int timeout = 0;
+        PARSE_INT_RETURN_IF_FAIL(cli, argv[3], timeout, "Failed to add interface", false);
+        Status status = mNetd->idletimerAddInterface(argv[2], timeout, argv[4]);
+        if (!status.isOk()) {
+            cli->sendMsg(ResponseCode::OperationFailed, "Failed to add interface", false);
+        } else {
+            cli->sendMsg(ResponseCode::CommandOkay, "Add success", false);
+        }
+        return 0;
+    }
+    if (!strcmp(argv[1], "remove")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+        int timeout = 0;
+        PARSE_INT_RETURN_IF_FAIL(cli, argv[3], timeout, "Failed to remove interface", false);
+        Status status = mNetd->idletimerRemoveInterface(argv[2], timeout, argv[4]);
+        if (!status.isOk()) {
+            cli->sendMsg(ResponseCode::OperationFailed, "Failed to remove interface", false);
+        } else {
+            cli->sendMsg(ResponseCode::CommandOkay, "Remove success", false);
+        }
+        return 0;
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown idletimer cmd", false);
+    return 0;
+}
+
+NdcDispatcher::FirewallCmd::FirewallCmd() : NdcNetdCommand("firewall") {}
+
+int NdcDispatcher::FirewallCmd::sendGenericOkFail(NdcClient* cli, int cond) const {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Firewall command succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Firewall command failed", false);
+    }
+    return 0;
+}
+
+int NdcDispatcher::FirewallCmd::parseRule(const char* arg) {
+    if (!strcmp(arg, "allow")) {
+        return INetd::FIREWALL_RULE_ALLOW;
+    } else if (!strcmp(arg, "deny")) {
+        return INetd::FIREWALL_RULE_DENY;
+    } else {
+        LOG(LOGLEVEL) << "failed to parse uid rule " << arg;
+        return INetd::FIREWALL_RULE_ALLOW;
+    }
+}
+
+int NdcDispatcher::FirewallCmd::parseFirewallType(const char* arg) {
+    if (!strcmp(arg, "whitelist")) {
+        return INetd::FIREWALL_WHITELIST;
+    } else if (!strcmp(arg, "blacklist")) {
+        return INetd::FIREWALL_BLACKLIST;
+    } else {
+        LOG(LOGLEVEL) << "failed to parse firewall type " << arg;
+        return INetd::FIREWALL_BLACKLIST;
+    }
+}
+
+int NdcDispatcher::FirewallCmd::parseChildChain(const char* arg) {
+    if (!strcmp(arg, "dozable")) {
+        return INetd::FIREWALL_CHAIN_DOZABLE;
+    } else if (!strcmp(arg, "standby")) {
+        return INetd::FIREWALL_CHAIN_STANDBY;
+    } else if (!strcmp(arg, "powersave")) {
+        return INetd::FIREWALL_CHAIN_POWERSAVE;
+    } else if (!strcmp(arg, "none")) {
+        return INetd::FIREWALL_CHAIN_NONE;
+    } else {
+        LOG(LOGLEVEL) << "failed to parse child firewall chain " << arg;
+        return -1;
+    }
+}
+
+int NdcDispatcher::FirewallCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "enable")) {
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall enable <whitelist|blacklist>", false);
+            return 0;
+        }
+        int res = !mNetd->firewallSetFirewallType(parseFirewallType(argv[2])).isOk();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_interface_rule")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_interface_rule <rmnet0> <allow|deny>", false);
+            return 0;
+        }
+        int res = !mNetd->firewallSetInterfaceRule(argv[2], parseRule(argv[3])).isOk();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_uid_rule")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_uid_rule <dozable|standby|none> <1000> <allow|deny>",
+                         false);
+            return 0;
+        }
+
+        int childChain = parseChildChain(argv[2]);
+        if (childChain == -1) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Invalid chain name. Valid names are: <dozable|standby|none>", false);
+            return 0;
+        }
+        uid_t uid = 0;
+        PARSE_UINT_RETURN_IF_FAIL(cli, argv[3], uid, "Firewall command failed", false);
+        int res = !mNetd->firewallSetUidRule(childChain, uid, parseRule(argv[4])).isOk();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "enable_chain")) {
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall enable_chain <dozable|standby>", false);
+            return 0;
+        }
+        int res = !mNetd->firewallEnableChildChain(parseChildChain(argv[2]), true).isOk();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "disable_chain")) {
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall disable_chain <dozable|standby>", false);
+            return 0;
+        }
+        int res = !mNetd->firewallEnableChildChain(parseChildChain(argv[2]), false).isOk();
+        return sendGenericOkFail(cli, res);
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
+    return 0;
+}
+
+NdcDispatcher::ClatdCmd::ClatdCmd() : NdcNetdCommand("clatd") {}
+
+int NdcDispatcher::ClatdCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    int rc = 0;
+    if (argc < 3) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    std::string v6Addr;
+
+    if (!strcmp(argv[1], "stop")) {
+        rc = !mNetd->clatdStop(argv[2]).isOk();
+    } else if (!strcmp(argv[1], "start")) {
+        if (argc < 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+        rc = !mNetd->clatdStart(argv[2], argv[3], &v6Addr).isOk();
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown clatd cmd", false);
+        return 0;
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay,
+                     std::string(("Clatd operation succeeded ") + v6Addr).c_str(), false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Clatd operation failed", false);
+    }
+
+    return 0;
+}
+
+NdcDispatcher::StrictCmd::StrictCmd() : NdcNetdCommand("strict") {}
+
+int NdcDispatcher::StrictCmd::sendGenericOkFail(NdcClient* cli, int cond) const {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Strict command succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Strict command failed", false);
+    }
+    return 0;
+}
+
+int NdcDispatcher::StrictCmd::parsePenalty(const char* arg) {
+    if (!strcmp(arg, "reject")) {
+        return INetd::PENALTY_POLICY_REJECT;
+    } else if (!strcmp(arg, "log")) {
+        return INetd::PENALTY_POLICY_LOG;
+    } else if (!strcmp(arg, "accept")) {
+        return INetd::PENALTY_POLICY_ACCEPT;
+    } else {
+        return -1;
+    }
+}
+
+int NdcDispatcher::StrictCmd::runCommand(NdcClient* cli, int argc, char** argv) const {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "set_uid_cleartext_policy")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: strict set_uid_cleartext_policy <uid> <accept|log|reject>", false);
+            return 0;
+        }
+
+        errno = 0;
+        uid_t uid = 0;
+        PARSE_UINT_RETURN_IF_FAIL(cli, argv[2], uid, "Invalid UID", false);
+        if (uid > UID_MAX) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid UID", false);
+            return 0;
+        }
+
+        int penalty = parsePenalty(argv[3]);
+        if (penalty == -1) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid penalty argument", false);
+            return 0;
+        }
+
+        int res = !mNetd->strictUidCleartextPenalty(uid, penalty).isOk();
+        return sendGenericOkFail(cli, res);
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
+    return 0;
+}
+
+NdcDispatcher::NetworkCommand::NetworkCommand() : NdcNetdCommand("network") {}
+
+int NdcDispatcher::NetworkCommand::syntaxError(NdcClient* cli, const char* message) const {
+    cli->sendMsg(ResponseCode::CommandSyntaxError, message, false);
+    return 0;
+}
+
+int NdcDispatcher::NetworkCommand::operationError(NdcClient* cli, const char* message,
+                                                  int ret) const {
+    errno = ret;
+    cli->sendMsg(ResponseCode::OperationFailed, message, true);
+    return 0;
+}
+
+int NdcDispatcher::NetworkCommand::success(NdcClient* cli) const {
+    cli->sendMsg(ResponseCode::CommandOkay, "success", false);
+    return 0;
+}
+
+int NdcDispatcher::NetworkCommand::runCommand(NdcClient* cli, int argc, char** argv) const {
+    if (argc < 2) {
+        return syntaxError(cli, "Missing argument");
+    }
+
+    //    0      1      2      3      4       5         6            7           8
+    // network route [legacy <uid>]  add   <netId> <interface> <destination> [nexthop]
+    // network route [legacy <uid>] remove <netId> <interface> <destination> [nexthop]
+    //
+    // nexthop may be either an IPv4/IPv6 address or one of "unreachable" or "throw".
+    if (!strcmp(argv[1], "route")) {
+        if (argc < 6 || argc > 9) {
+            return syntaxError(cli, "Incorrect number of arguments");
+        }
+
+        int nextArg = 2;
+        bool legacy = false;
+        uid_t uid = 0;
+        if (!strcmp(argv[nextArg], "legacy")) {
+            ++nextArg;
+            legacy = true;
+            PARSE_UINT_RETURN_IF_FAIL(cli, argv[nextArg++], uid, "Unknown argument", false);
+        }
+
+        bool add = false;
+        if (!strcmp(argv[nextArg], "add")) {
+            add = true;
+        } else if (strcmp(argv[nextArg], "remove")) {
+            return syntaxError(cli, "Unknown argument");
+        }
+        ++nextArg;
+
+        if (argc < nextArg + 3 || argc > nextArg + 4) {
+            return syntaxError(cli, "Incorrect number of arguments");
+        }
+
+        unsigned netId = stringToNetId(argv[nextArg++]);
+        const char* interface = argv[nextArg++];
+        const char* destination = argv[nextArg++];
+        const char* nexthop = argc > nextArg ? argv[nextArg] : "";
+
+        Status status;
+        if (legacy) {
+            status = add ? mNetd->networkAddLegacyRoute(netId, interface, destination, nexthop, uid)
+
+                         : mNetd->networkRemoveLegacyRoute(netId, interface, destination, nexthop,
+                                                           uid);
+        } else {
+            status = add ? mNetd->networkAddRoute(netId, interface, destination, nexthop)
+                         : mNetd->networkRemoveRoute(netId, interface, destination, nexthop);
+        }
+
+        if (!status.isOk()) {
+            return operationError(cli, add ? "addRoute() failed" : "removeRoute() failed",
+                                  status.serviceSpecificErrorCode());
+        }
+
+        return success(cli);
+    }
+
+    //    0        1       2       3         4
+    // network interface  add   <netId> <interface>
+    // network interface remove <netId> <interface>
+    if (!strcmp(argv[1], "interface")) {
+        if (argc != 5) {
+            return syntaxError(cli, "Missing argument");
+        }
+        unsigned netId = stringToNetId(argv[3]);
+        if (!strcmp(argv[2], "add")) {
+            if (Status status = mNetd->networkAddInterface(netId, argv[4]); !status.isOk()) {
+                return operationError(cli, "addInterfaceToNetwork() failed",
+                                      status.serviceSpecificErrorCode());
+            }
+        } else if (!strcmp(argv[2], "remove")) {
+            if (Status status = mNetd->networkRemoveInterface(netId, argv[4]); !status.isOk()) {
+                return operationError(cli, "removeInterfaceFromNetwork() failed",
+                                      status.serviceSpecificErrorCode());
+            }
+        } else {
+            return syntaxError(cli, "Unknown argument");
+        }
+        return success(cli);
+    }
+
+    //    0      1       2         3
+    // network create <netId> [permission]
+    //
+    //    0      1       2     3      4
+    // network create <netId> vpn <secure>
+    if (!strcmp(argv[1], "create")) {
+        if (argc < 3) {
+            return syntaxError(cli, "Missing argument");
+        }
+        unsigned netId = stringToNetId(argv[2]);
+        if (argc == 6 && !strcmp(argv[3], "vpn")) {
+            bool secure = strtol(argv[4], nullptr, 2);
+            if (Status status = mNetd->networkCreateVpn(netId, secure); !status.isOk()) {
+                return operationError(cli, "createVirtualNetwork() failed",
+                                      status.serviceSpecificErrorCode());
+            }
+        } else if (argc > 4) {
+            return syntaxError(cli, "Unknown trailing argument(s)");
+        } else {
+            int permission = INetd::PERMISSION_NONE;
+            if (argc == 4) {
+                permission = stringToINetdPermission(argv[3]);
+                if (permission == INetd::PERMISSION_NONE) {
+                    return syntaxError(cli, "Unknown permission");
+                }
+            }
+            if (Status status = mNetd->networkCreatePhysical(netId, permission); !status.isOk()) {
+                return operationError(cli, "createPhysicalNetwork() failed",
+                                      status.serviceSpecificErrorCode());
+            }
+        }
+        return success(cli);
+    }
+
+    //    0       1       2
+    // network destroy <netId>
+    if (!strcmp(argv[1], "destroy")) {
+        if (argc != 3) {
+            return syntaxError(cli, "Incorrect number of arguments");
+        }
+        unsigned netId = stringToNetId(argv[2]);
+        // Both of these functions manage their own locking internally.
+        if (Status status = mNetd->networkDestroy(netId); !status.isOk()) {
+            return operationError(cli, "destroyNetwork() failed",
+                                  status.serviceSpecificErrorCode());
+        }
+        mDnsResolver->destroyNetworkCache(netId);
+        return success(cli);
+    }
+
+    //    0       1      2      3
+    // network default  set  <netId>
+    // network default clear
+    if (!strcmp(argv[1], "default")) {
+        if (argc < 3) {
+            return syntaxError(cli, "Missing argument");
+        }
+        unsigned netId = NETID_UNSET;
+        if (!strcmp(argv[2], "set")) {
+            if (argc < 4) {
+                return syntaxError(cli, "Missing netId");
+            }
+            netId = stringToNetId(argv[3]);
+        } else if (strcmp(argv[2], "clear")) {
+            return syntaxError(cli, "Unknown argument");
+        }
+        if (Status status = mNetd->networkSetDefault(netId); !status.isOk()) {
+            return operationError(cli, "setDefaultNetwork() failed",
+                                  status.serviceSpecificErrorCode());
+        }
+        return success(cli);
+    }
+
+    //    0        1         2      3        4          5
+    // network permission   user   set  <permission>  <uid> ...
+    // network permission   user  clear    <uid> ...
+    // network permission network  set  <permission> <netId> ...
+    // network permission network clear   <netId> ...
+    if (!strcmp(argv[1], "permission")) {
+        if (argc < 5) {
+            return syntaxError(cli, "Missing argument");
+        }
+        int nextArg = 4;
+        int permission = INetd::PERMISSION_NONE;
+        if (!strcmp(argv[3], "set")) {
+            permission = stringToINetdPermission(argv[4]);
+            if (permission == INetd::PERMISSION_NONE) {
+                return syntaxError(cli, "Unknown permission");
+            }
+            nextArg = 5;
+        } else if (strcmp(argv[3], "clear")) {
+            return syntaxError(cli, "Unknown argument");
+        }
+        if (nextArg == argc) {
+            return syntaxError(cli, "Missing id");
+        }
+
+        bool userPermissions = !strcmp(argv[2], "user");
+        bool networkPermissions = !strcmp(argv[2], "network");
+        if (!userPermissions && !networkPermissions) {
+            return syntaxError(cli, "Unknown argument");
+        }
+
+        std::vector<int32_t> ids;
+        for (; nextArg < argc; ++nextArg) {
+            if (userPermissions) {
+                char* endPtr;
+                unsigned id = strtoul(argv[nextArg], &endPtr, 0);
+                if (!*argv[nextArg] || *endPtr) {
+                    return syntaxError(cli, "Invalid id");
+                }
+                ids.push_back(id);
+            } else {
+                // networkPermissions
+                ids.push_back(stringToNetId(argv[nextArg]));
+            }
+        }
+        if (userPermissions) {
+            mNetd->networkSetPermissionForUser(permission, ids);
+        } else {
+            // networkPermissions
+            for (auto netId : ids) {
+                Status status = mNetd->networkSetPermissionForNetwork(netId, permission);
+                if (!status.isOk())
+                    return operationError(cli, "setPermissionForNetworks() failed",
+                                          status.serviceSpecificErrorCode());
+            }
+        }
+
+        return success(cli);
+    }
+
+    //    0      1     2       3           4
+    // network users  add   <netId> [<uid>[-<uid>]] ...
+    // network users remove <netId> [<uid>[-<uid>]] ...
+    if (!strcmp(argv[1], "users")) {
+        if (argc < 4) {
+            return syntaxError(cli, "Missing argument");
+        }
+        unsigned netId = stringToNetId(argv[3]);
+        UidRanges uidRanges;
+        if (!uidRanges.parseFrom(argc - 4, argv + 4)) {
+            return syntaxError(cli, "Invalid UIDs");
+        }
+        if (!strcmp(argv[2], "add")) {
+            if (Status status = mNetd->networkAddUidRanges(netId, uidRanges.getRanges());
+                !status.isOk()) {
+                return operationError(cli, "addUsersToNetwork() failed",
+                                      status.serviceSpecificErrorCode());
+            }
+        } else if (!strcmp(argv[2], "remove")) {
+            if (Status status = mNetd->networkRemoveUidRanges(netId, uidRanges.getRanges());
+                !status.isOk()) {
+                return operationError(cli, "removeUsersFromNetwork() failed",
+                                      status.serviceSpecificErrorCode());
+            }
+        } else {
+            return syntaxError(cli, "Unknown argument");
+        }
+        return success(cli);
+    }
+
+    //    0       1      2     3
+    // network protect allow <uid> ...
+    // network protect  deny <uid> ...
+    if (!strcmp(argv[1], "protect")) {
+        if (argc < 4) {
+            return syntaxError(cli, "Missing argument");
+        }
+        std::vector<uid_t> uids;
+        for (int i = 3; i < argc; ++i) {
+            uid_t uid = 0;
+            PARSE_UINT_RETURN_IF_FAIL(cli, argv[i], uid, "Unknown argument", false);
+            uids.push_back(uid);
+        }
+        if (!strcmp(argv[2], "allow")) {
+            for (auto uid : uids) {
+                mNetd->networkSetProtectAllow(uid);
+            }
+        } else if (!strcmp(argv[2], "deny")) {
+            for (auto uid : uids) {
+                mNetd->networkSetProtectDeny(uid);
+            }
+        } else {
+            return syntaxError(cli, "Unknown argument");
+        }
+        return success(cli);
+    }
+
+    return syntaxError(cli, "Unknown argument");
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/NdcDispatcher.h b/server/NdcDispatcher.h
new file mode 100644
index 0000000..5732e22
--- /dev/null
+++ b/server/NdcDispatcher.h
@@ -0,0 +1,174 @@
+/*
+ * 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 _NDC_DISPATCHER_H__
+#define _NDC_DISPATCHER_H__
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android/net/IDnsResolver.h>
+#include <android/net/INetd.h>
+#include "binder/IServiceManager.h"
+
+#include "NetdConstants.h"
+
+namespace android {
+namespace net {
+
+class NdcClient {
+  public:
+    NdcClient() = default;
+    ~NdcClient() = default;
+
+    int sendMsg(int code, const char* msg, bool addErrno) {
+        if (addErrno) {
+            printf("%d 0 %s (%s)\n", code, msg, strerror(errno));
+        } else {
+            printf("%d 0 %s\n", code, msg);
+        }
+        return 0;
+    }
+};
+
+class NdcNetdCommand {
+  public:
+    NdcNetdCommand(std::string cmd) : mCommand(std::move(cmd)) {}
+    virtual ~NdcNetdCommand() {}
+
+    virtual int runCommand(NdcClient* c, int argc, char** argv) const = 0;
+
+    const std::string& getCommand() const { return mCommand; }
+
+  private:
+    std::string mCommand;
+};
+
+class NdcDispatcher {
+  public:
+    // Matches the restrictions previously imposed by CommandListener.cpp.
+    static const int CMD_ARGS_MAX = 26;
+    // Default log level is set to minimum one.
+    static const android::base::LogSeverity LOGLEVEL = android::base::VERBOSE;
+
+    NdcDispatcher();
+    ~NdcDispatcher() = default;
+
+    static sp<INetd> mNetd;
+    static sp<IDnsResolver> mDnsResolver;
+    NdcClient mNdc;
+
+    int dispatchCommand(int argc, char** argv);
+    void registerCmd(NdcNetdCommand* cmd);
+
+  private:
+    std::vector<NdcNetdCommand*> mCommands;
+
+    class InterfaceCmd : public NdcNetdCommand {
+      public:
+        InterfaceCmd();
+        virtual ~InterfaceCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+    };
+
+    class IpFwdCmd : public NdcNetdCommand {
+      public:
+        IpFwdCmd();
+        virtual ~IpFwdCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+    };
+
+    class TetherCmd : public NdcNetdCommand {
+      public:
+        TetherCmd();
+        virtual ~TetherCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+    };
+
+    class NatCmd : public NdcNetdCommand {
+      public:
+        NatCmd();
+        virtual ~NatCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+    };
+
+    class BandwidthControlCmd : public NdcNetdCommand {
+      public:
+        BandwidthControlCmd();
+        virtual ~BandwidthControlCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+
+      protected:
+        void sendGenericOkFail(NdcClient* cli, int cond) const;
+        void sendGenericOpFailed(NdcClient* cli, const char* errMsg) const;
+        void sendGenericSyntaxError(NdcClient* cli, const char* usageMsg) const;
+    };
+
+    class IdletimerControlCmd : public NdcNetdCommand {
+      public:
+        IdletimerControlCmd();
+        virtual ~IdletimerControlCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+    };
+
+    class FirewallCmd : public NdcNetdCommand {
+      public:
+        FirewallCmd();
+        virtual ~FirewallCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+
+      protected:
+        int sendGenericOkFail(NdcClient* cli, int cond) const;
+        static int parseRule(const char* arg);
+        static int parseFirewallType(const char* arg);
+        static int parseChildChain(const char* arg);
+    };
+
+    class ClatdCmd : public NdcNetdCommand {
+      public:
+        ClatdCmd();
+        virtual ~ClatdCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+    };
+
+    class StrictCmd : public NdcNetdCommand {
+      public:
+        StrictCmd();
+        virtual ~StrictCmd() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+
+      protected:
+        int sendGenericOkFail(NdcClient* cli, int cond) const;
+        static int parsePenalty(const char* arg);
+    };
+
+    class NetworkCommand : public NdcNetdCommand {
+      public:
+        NetworkCommand();
+        virtual ~NetworkCommand() {}
+        int runCommand(NdcClient* cli, int argc, char** argv) const;
+
+      private:
+        int syntaxError(NdcClient* cli, const char* message) const;
+        int operationError(NdcClient* cli, const char* message, int ret) const;
+        int success(NdcClient* cli) const;
+    };
+};
+
+}  // namespace net
+}  // namespace android
+
+#endif
diff --git a/server/NetdConstants.cpp b/server/NetdConstants.cpp
index 82db842..40f78f6 100644
--- a/server/NetdConstants.cpp
+++ b/server/NetdConstants.cpp
@@ -20,7 +20,6 @@
 #include <netdb.h>
 #include <net/if.h>
 #include <netinet/in.h>
-#include <openssl/ssl.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/wait.h>
@@ -28,20 +27,14 @@
 #define LOG_TAG "Netd"
 
 #include <android-base/stringprintf.h>
-#include <cutils/log.h>
 #include <cutils/sockets.h>
+#include <log/log.h>
 #include <logwrap/logwrap.h>
 
 #include "Controllers.h"
 #include "NetdConstants.h"
 #include "IptablesRestoreController.h"
 
-const size_t SHA256_SIZE = EVP_MD_size(EVP_sha256());
-
-const char * const OEM_SCRIPT_PATH = "/system/bin/oem-iptables-init.sh";
-const char * const ADD = "add";
-const char * const DEL = "del";
-
 int execIptablesRestoreWithOutput(IptablesTarget target, const std::string& commands,
                                   std::string *output) {
     return android::net::gCtls->iptablesRestoreCtrl.execute(target, commands, output);
@@ -112,7 +105,7 @@
     addrinfo hints = {
         .ai_flags = AI_NUMERICHOST,
     };
-    int ret = getaddrinfo(addressString.c_str(), NULL, &hints, &res);
+    int ret = getaddrinfo(addressString.c_str(), nullptr, &hints, &res);
     if (ret || !res) {
         return -EINVAL;  // getaddrinfo return values are not errno values.
     }
@@ -162,7 +155,7 @@
 
     sigemptyset(&mask);
     sigaddset(&mask, SIGPIPE);
-    if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
+    if (sigprocmask(SIG_BLOCK, &mask, nullptr) != 0)
         ALOGW("WARNING: SIGPIPE not blocked\n");
 }
 
diff --git a/server/NetdConstants.h b/server/NetdConstants.h
index f929219..97e75bd 100644
--- a/server/NetdConstants.h
+++ b/server/NetdConstants.h
@@ -17,26 +17,19 @@
 #ifndef _NETD_CONSTANTS_H
 #define _NETD_CONSTANTS_H
 
-#include <string>
-#include <list>
 #include <ifaddrs.h>
 #include <netdb.h>
-#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
 
-#include <chrono>
+#include <mutex>
+#include <string>
 
+#include <netdutils/UidConstants.h>
 #include <private/android_filesystem_config.h>
 
-#include "utils/RWLock.h"
-
-const int PROTECT_MARK = 0x1;
-const int MAX_SYSTEM_UID = AID_APP - 1;
-
-extern const size_t SHA256_SIZE;
-
-extern const char * const OEM_SCRIPT_PATH;
-extern const char * const ADD;
-extern const char * const DEL;
+// Referred from SHA256_DIGEST_LENGTH in boringssl
+constexpr size_t SHA256_SIZE = 32;
 
 enum IptablesTarget { V4, V6, V4V6 };
 
@@ -50,6 +43,7 @@
 void blockSigpipe();
 void setCloseOnExec(const char *sock);
 
+// TODO: use std::size() instead.
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 
 #define __INT_STRLEN(i) sizeof(#i)
@@ -63,6 +57,8 @@
 
 const uid_t INVALID_UID = static_cast<uid_t>(-1);
 
+constexpr char TCP_RMEM_PROC_FILE[] = "/proc/sys/net/ipv4/tcp_rmem";
+constexpr char TCP_WMEM_PROC_FILE[] = "/proc/sys/net/ipv4/tcp_wmem";
 
 struct AddrinfoDeleter {
     void operator()(struct addrinfo* p) const {
@@ -94,7 +90,7 @@
  * CommandListener has only one user (NetworkManagementService), which is connected through a
  * FrameworkListener that passes in commands one at a time.
  */
-extern android::RWLock gBigNetdLock;
+extern std::mutex gBigNetdLock;
 
 }  // namespace net
 }  // namespace android
diff --git a/server/NetdHwService.cpp b/server/NetdHwService.cpp
index 65afe9b..b209aca 100644
--- a/server/NetdHwService.cpp
+++ b/server/NetdHwService.cpp
@@ -16,7 +16,6 @@
 
 #include <binder/IPCThreadState.h>
 #include <hidl/HidlTransportSupport.h>
-#include <hwbinder/IPCThreadState.h>
 #include "Controllers.h"
 #include "Fwmark.h"
 #include "NetdHwService.h"
@@ -24,7 +23,6 @@
 #include "TetherController.h"
 
 using android::hardware::configureRpcThreadpool;
-using android::hardware::IPCThreadState;
 using android::hardware::Void;
 
 // Tells TetherController::enableForwarding who is requesting forwarding, so that TetherController
@@ -140,7 +138,7 @@
 }
 
 Return <StatusCode> NetdHwService::setIpForwardEnable(bool enable) {
-    android::RWLock::AutoWLock _lock(gCtls->tetherCtrl.lock);
+    std::lock_guard _lock(gCtls->tetherCtrl.lock);
 
     bool success = enable ? gCtls->tetherCtrl.enableForwarding(FORWARDING_REQUESTER) :
                             gCtls->tetherCtrl.disableForwarding(FORWARDING_REQUESTER);
@@ -150,7 +148,7 @@
 
 Return <StatusCode> NetdHwService::setForwardingBetweenInterfaces(
         const hidl_string& inputIfName, const hidl_string& outputIfName, bool enable) {
-    android::RWLock::AutoWLock _lock(gCtls->tetherCtrl.lock);
+    std::lock_guard _lock(gCtls->tetherCtrl.lock);
 
     // TODO: check that one interface is an OEM interface and the other is another OEM interface, an
     // IPsec interface or a dummy interface.
diff --git a/server/NetdHwService.h b/server/NetdHwService.h
index a814fe4..458c6fa 100644
--- a/server/NetdHwService.h
+++ b/server/NetdHwService.h
@@ -19,16 +19,16 @@
 
 #include <android/system/net/netd/1.1/INetd.h>
 
-using android::hardware::Return;
-using android::hardware::hidl_string;
-using android::system::net::netd::V1_1::INetd;
-using StatusCode = android::system::net::netd::V1_1::INetd::StatusCode;
-
 namespace android {
 namespace net {
 
-class NetdHwService : public INetd {
-public:
+using android::hardware::Return;
+using android::hardware::hidl_string;
+using INetdHw = android::system::net::netd::V1_1::INetd;
+using StatusCode = android::system::net::netd::V1_1::INetd::StatusCode;
+
+class NetdHwService : INetdHw {
+  public:
     // 1.0
     status_t start();
     Return<void> createOemNetwork(createOemNetwork_cb _hidl_cb) override;
diff --git a/server/NetdNativeService.cpp b/server/NetdNativeService.cpp
index c1e3ffa..65d4727 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -16,87 +16,123 @@
 
 #define LOG_TAG "Netd"
 
+#include <cinttypes>
+#include <numeric>
 #include <set>
+#include <string>
+#include <tuple>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <cutils/log.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
 #include <cutils/properties.h>
+#include <json/value.h>
+#include <json/writer.h>
+#include <log/log.h>
+#include <netdutils/DumpWriter.h>
 #include <utils/Errors.h>
 #include <utils/String16.h>
 
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include "android/net/BnNetd.h"
-
-#include <openssl/base64.h>
-
+#include "BinderUtil.h"
 #include "Controllers.h"
-#include "DumpWriter.h"
-#include "EventReporter.h"
 #include "InterfaceController.h"
-#include "NetdConstants.h"
+#include "NetdConstants.h"  // SHA256_SIZE
 #include "NetdNativeService.h"
+#include "NetdPermissions.h"
+#include "OemNetdListener.h"
+#include "Permission.h"
+#include "Process.h"
 #include "RouteController.h"
 #include "SockDiag.h"
 #include "UidRanges.h"
+#include "android/net/BnNetd.h"
+#include "netid_client.h"  // NETID_UNSET
 
 using android::base::StringPrintf;
-using android::os::PersistableBundle;
+using android::base::WriteStringToFile;
+using android::net::TetherStatsParcel;
+using android::net::UidRangeParcel;
+using android::netdutils::DumpWriter;
+using android::netdutils::ScopedIndent;
+using android::os::ParcelFileDescriptor;
 
 namespace android {
 namespace net {
 
 namespace {
+const char OPT_SHORT[] = "--short";
 
-const char CONNECTIVITY_INTERNAL[] = "android.permission.CONNECTIVITY_INTERNAL";
-const char NETWORK_STACK[] = "android.permission.NETWORK_STACK";
-const char DUMP[] = "android.permission.DUMP";
+binder::Status checkAnyPermission(const std::vector<const char*>& permissions) {
+    pid_t pid = IPCThreadState::self()->getCallingPid();
+    uid_t uid = IPCThreadState::self()->getCallingUid();
 
-binder::Status toBinderStatus(const netdutils::Status s) {
-    if (isOk(s)) {
+    // If the caller is the system UID, don't check permissions.
+    // Otherwise, if the system server's binder thread pool is full, and all the threads are
+    // blocked on a thread that's waiting for us to complete, we deadlock. http://b/69389492
+    //
+    // From a security perspective, there is currently no difference, because:
+    // 1. The only permissions we check in netd's binder interface are CONNECTIVITY_INTERNAL
+    //    and NETWORK_STACK, which the system server always has (or MAINLINE_NETWORK_STACK, which
+    //    is equivalent to having both CONNECTIVITY_INTERNAL and NETWORK_STACK).
+    // 2. AID_SYSTEM always has all permissions. See ActivityManager#checkComponentPermission.
+    if (uid == AID_SYSTEM) {
         return binder::Status::ok();
     }
-    return binder::Status::fromServiceSpecificError(s.code(), s.msg().c_str());
-}
 
-binder::Status checkPermission(const char *permission) {
-    pid_t pid;
-    uid_t uid;
-
-    if (checkCallingPermission(String16(permission), (int32_t *) &pid, (int32_t *) &uid)) {
-        return binder::Status::ok();
-    } else {
-        auto err = StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission);
-        return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, String8(err.c_str()));
+    for (const char* permission : permissions) {
+        if (checkPermission(String16(permission), pid, uid)) {
+            return binder::Status::ok();
+        }
     }
+
+    auto err = StringPrintf("UID %d / PID %d does not have any of the following permissions: %s",
+                            uid, pid, android::base::Join(permissions, ',').c_str());
+    return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, err.c_str());
 }
 
-#define ENFORCE_DEBUGGABLE() {                              \
-    char value[PROPERTY_VALUE_MAX + 1];                     \
-    if (property_get("ro.debuggable", value, NULL) != 1     \
-            || value[0] != '1') {                           \
-        return binder::Status::fromExceptionCode(           \
-            binder::Status::EX_SECURITY,                    \
-            String8("Not available in production builds.")  \
-        );                                                  \
-    }                                                       \
+#define ENFORCE_ANY_PERMISSION(...)                                \
+    do {                                                           \
+        binder::Status status = checkAnyPermission({__VA_ARGS__}); \
+        if (!status.isOk()) {                                      \
+            return status;                                         \
+        }                                                          \
+    } while (0)
+
+#define NETD_LOCKING_RPC(lock, ... /* permissions */) \
+    ENFORCE_ANY_PERMISSION(__VA_ARGS__);              \
+    std::lock_guard _lock(lock);
+
+#define NETD_BIG_LOCK_RPC(... /* permissions */) NETD_LOCKING_RPC(gBigNetdLock, __VA_ARGS__)
+
+#define RETURN_BINDER_STATUS_IF_NOT_OK(logEntry, res) \
+    do {                                              \
+        if (!isOk((res))) {                           \
+            logErrorStatus((logEntry), (res));        \
+            return asBinderStatus((res));             \
+        }                                             \
+    } while (0)
+
+#define ENFORCE_INTERNAL_PERMISSIONS() \
+    ENFORCE_ANY_PERMISSION(PERM_CONNECTIVITY_INTERNAL, PERM_MAINLINE_NETWORK_STACK)
+
+#define ENFORCE_NETWORK_STACK_PERMISSIONS() \
+    ENFORCE_ANY_PERMISSION(PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK)
+
+void logErrorStatus(netdutils::LogEntry& logEntry, const netdutils::Status& status) {
+    gLog.log(logEntry.returns(status.code()).withAutomaticDuration());
 }
 
-#define ENFORCE_PERMISSION(permission) {                    \
-    binder::Status status = checkPermission((permission));  \
-    if (!status.isOk()) {                                   \
-        return status;                                      \
-    }                                                       \
+binder::Status asBinderStatus(const netdutils::Status& status) {
+    if (isOk(status)) {
+        return binder::Status::ok();
+    }
+    return binder::Status::fromServiceSpecificError(status.code(), status.msg().c_str());
 }
 
-#define NETD_LOCKING_RPC(permission, lock)                  \
-    ENFORCE_PERMISSION(permission);                         \
-    android::RWLock::AutoWLock _lock(lock);
-
-#define NETD_BIG_LOCK_RPC(permission) NETD_LOCKING_RPC((permission), gBigNetdLock)
-
 inline binder::Status statusFromErrcode(int ret) {
     if (ret) {
         return binder::Status::fromServiceSpecificError(-ret, strerror(-ret));
@@ -104,23 +140,37 @@
     return binder::Status::ok();
 }
 
+bool contains(const Vector<String16>& words, const String16& word) {
+    for (const auto& w : words) {
+        if (w == word) return true;
+    }
+
+    return false;
+}
+
 }  // namespace
 
+NetdNativeService::NetdNativeService() {
+    // register log callback to BnNetd::logFunc
+    BnNetd::logFunc = std::bind(binderCallLogFn, std::placeholders::_1,
+                                [](const std::string& msg) { gLog.info("%s", msg.c_str()); });
+}
 
 status_t NetdNativeService::start() {
     IPCThreadState::self()->disableBackgroundScheduling(true);
-    status_t ret = BinderService<NetdNativeService>::publish();
+    const status_t ret = BinderService<NetdNativeService>::publish();
     if (ret != android::OK) {
         return ret;
     }
     sp<ProcessState> ps(ProcessState::self());
     ps->startThreadPool();
     ps->giveThreadPoolName();
+
     return android::OK;
 }
 
 status_t NetdNativeService::dump(int fd, const Vector<String16> &args) {
-    const binder::Status dump_permission = checkPermission(DUMP);
+    const binder::Status dump_permission = checkAnyPermission({PERM_DUMP});
     if (!dump_permission.isOk()) {
         const String8 msg(dump_permission.toString8());
         write(fd, msg.string(), msg.size());
@@ -146,6 +196,7 @@
         return NO_ERROR;
     }
 
+    process::dump(dw);
     dw.blankline();
     gCtls->netCtrl.dump(dw);
     dw.blankline();
@@ -153,92 +204,180 @@
     gCtls->trafficCtrl.dump(dw, false);
     dw.blankline();
 
+    gCtls->xfrmCtrl.dump(dw);
+    dw.blankline();
+
+    gCtls->clatdCtrl.dump(dw);
+    dw.blankline();
+
+    {
+        ScopedIndent indentLog(dw);
+        if (contains(args, String16(OPT_SHORT))) {
+            dw.println("Log: <omitted>");
+        } else {
+            dw.println("Log:");
+            ScopedIndent indentLogEntries(dw);
+            gLog.forEachEntry([&dw](const std::string& entry) mutable { dw.println(entry); });
+        }
+        dw.blankline();
+    }
+
+    {
+        ScopedIndent indentLog(dw);
+        if (contains(args, String16(OPT_SHORT))) {
+            dw.println("UnsolicitedLog: <omitted>");
+        } else {
+            dw.println("UnsolicitedLog:");
+            ScopedIndent indentLogEntries(dw);
+            gUnsolicitedLog.forEachEntry(
+                    [&dw](const std::string& entry) mutable { dw.println(entry); });
+        }
+        dw.blankline();
+    }
+
     return NO_ERROR;
 }
 
 binder::Status NetdNativeService::isAlive(bool *alive) {
-    NETD_BIG_LOCK_RPC(CONNECTIVITY_INTERNAL);
+    NETD_BIG_LOCK_RPC(PERM_CONNECTIVITY_INTERNAL, PERM_MAINLINE_NETWORK_STACK);
 
     *alive = true;
+
     return binder::Status::ok();
 }
 
-binder::Status NetdNativeService::firewallReplaceUidChain(const android::String16& chainName,
+binder::Status NetdNativeService::firewallReplaceUidChain(const std::string& chainName,
         bool isWhitelist, const std::vector<int32_t>& uids, bool *ret) {
-    NETD_LOCKING_RPC(CONNECTIVITY_INTERNAL, gCtls->firewallCtrl.lock);
-
-    android::String8 name = android::String8(chainName);
-    int err = gCtls->firewallCtrl.replaceUidChain(name.string(), isWhitelist, uids);
+    NETD_LOCKING_RPC(gCtls->firewallCtrl.lock, PERM_CONNECTIVITY_INTERNAL,
+                     PERM_MAINLINE_NETWORK_STACK);
+    int err = gCtls->firewallCtrl.replaceUidChain(chainName, isWhitelist, uids);
     *ret = (err == 0);
     return binder::Status::ok();
 }
 
 binder::Status NetdNativeService::bandwidthEnableDataSaver(bool enable, bool *ret) {
-    NETD_LOCKING_RPC(CONNECTIVITY_INTERNAL, gCtls->bandwidthCtrl.lock);
-
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_CONNECTIVITY_INTERNAL,
+                     PERM_MAINLINE_NETWORK_STACK);
     int err = gCtls->bandwidthCtrl.enableDataSaver(enable);
     *ret = (err == 0);
     return binder::Status::ok();
 }
 
-binder::Status NetdNativeService::networkCreatePhysical(int32_t netId,
-        const std::string& permission) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    int ret = gCtls->netCtrl.createPhysicalNetwork(netId, stringToPermission(permission.c_str()));
+binder::Status NetdNativeService::bandwidthSetInterfaceQuota(const std::string& ifName,
+                                                             int64_t bytes) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->bandwidthCtrl.setInterfaceQuota(ifName, bytes);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthRemoveInterfaceQuota(const std::string& ifName) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->bandwidthCtrl.removeInterfaceQuota(ifName);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthSetInterfaceAlert(const std::string& ifName,
+                                                             int64_t bytes) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->bandwidthCtrl.setInterfaceAlert(ifName, bytes);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthRemoveInterfaceAlert(const std::string& ifName) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->bandwidthCtrl.removeInterfaceAlert(ifName);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthSetGlobalAlert(int64_t bytes) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->bandwidthCtrl.setGlobalAlert(bytes);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthAddNaughtyApp(int32_t uid) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.addNaughtyApps(appStrUids);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthRemoveNaughtyApp(int32_t uid) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.removeNaughtyApps(appStrUids);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthAddNiceApp(int32_t uid) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.addNiceApps(appStrUids);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::bandwidthRemoveNiceApp(int32_t uid) {
+    NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.removeNiceApps(appStrUids);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkCreatePhysical(int32_t netId, int32_t permission) {
+    ENFORCE_INTERNAL_PERMISSIONS();
+    int ret = gCtls->netCtrl.createPhysicalNetwork(netId, convertPermission(permission));
     return statusFromErrcode(ret);
 }
 
-binder::Status NetdNativeService::networkCreateVpn(int32_t netId, bool hasDns, bool secure) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    int ret = gCtls->netCtrl.createVirtualNetwork(netId, hasDns, secure);
+binder::Status NetdNativeService::networkCreateVpn(int32_t netId, bool secure) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    int ret = gCtls->netCtrl.createVirtualNetwork(netId, secure);
     return statusFromErrcode(ret);
 }
 
 binder::Status NetdNativeService::networkDestroy(int32_t netId) {
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    // Both of these functions manage their own locking internally.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    // NetworkController::destroyNetwork is thread-safe.
     const int ret = gCtls->netCtrl.destroyNetwork(netId);
-    gCtls->resolverCtrl.clearDnsServers(netId);
     return statusFromErrcode(ret);
 }
 
 binder::Status NetdNativeService::networkAddInterface(int32_t netId, const std::string& iface) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ENFORCE_INTERNAL_PERMISSIONS();
     int ret = gCtls->netCtrl.addInterfaceToNetwork(netId, iface.c_str());
     return statusFromErrcode(ret);
 }
 
 binder::Status NetdNativeService::networkRemoveInterface(int32_t netId, const std::string& iface) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ENFORCE_INTERNAL_PERMISSIONS();
     int ret = gCtls->netCtrl.removeInterfaceFromNetwork(netId, iface.c_str());
     return statusFromErrcode(ret);
 }
 
-binder::Status NetdNativeService::networkAddUidRanges(int32_t netId,
-        const std::vector<UidRange>& uidRangeArray) {
+binder::Status NetdNativeService::networkAddUidRanges(
+        int32_t netId, const std::vector<UidRangeParcel>& uidRangeArray) {
     // NetworkController::addUsersToNetwork is thread-safe.
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ENFORCE_INTERNAL_PERMISSIONS();
     int ret = gCtls->netCtrl.addUsersToNetwork(netId, UidRanges(uidRangeArray));
     return statusFromErrcode(ret);
 }
 
-binder::Status NetdNativeService::networkRemoveUidRanges(int32_t netId,
-        const std::vector<UidRange>& uidRangeArray) {
+binder::Status NetdNativeService::networkRemoveUidRanges(
+        int32_t netId, const std::vector<UidRangeParcel>& uidRangeArray) {
     // NetworkController::removeUsersFromNetwork is thread-safe.
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ENFORCE_INTERNAL_PERMISSIONS();
     int ret = gCtls->netCtrl.removeUsersFromNetwork(netId, UidRanges(uidRangeArray));
     return statusFromErrcode(ret);
 }
 
-binder::Status NetdNativeService::networkRejectNonSecureVpn(bool add,
-        const std::vector<UidRange>& uidRangeArray) {
+binder::Status NetdNativeService::networkRejectNonSecureVpn(
+        bool add, const std::vector<UidRangeParcel>& uidRangeArray) {
     // TODO: elsewhere RouteController is only used from the tethering and network controllers, so
     // it should be possible to use the same lock as NetworkController. However, every call through
     // the CommandListener "network" command will need to hold this lock too, not just the ones that
     // read/modify network internal state (that is sufficient for ::dump() because it doesn't
     // look at routes, but it's not enough here).
-    NETD_BIG_LOCK_RPC(CONNECTIVITY_INTERNAL);
-
+    NETD_BIG_LOCK_RPC(PERM_CONNECTIVITY_INTERNAL, PERM_MAINLINE_NETWORK_STACK);
     UidRanges uidRanges(uidRangeArray);
 
     int err;
@@ -247,14 +386,12 @@
     } else {
         err = RouteController::removeUsersFromRejectNonSecureNetworkRule(uidRanges);
     }
-
     return statusFromErrcode(err);
 }
 
-binder::Status NetdNativeService::socketDestroy(const std::vector<UidRange>& uids,
-        const std::vector<int32_t>& skipUids) {
-
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+binder::Status NetdNativeService::socketDestroy(const std::vector<UidRangeParcel>& uids,
+                                                const std::vector<int32_t>& skipUids) {
+    ENFORCE_INTERNAL_PERMISSIONS();
 
     SockDiag sd;
     if (!sd.open()) {
@@ -265,7 +402,6 @@
     UidRanges uidRanges(uids);
     int err = sd.destroySockets(uidRanges, std::set<uid_t>(skipUids.begin(), skipUids.end()),
                                 true /* excludeLoopback */);
-
     if (err) {
         return binder::Status::fromServiceSpecificError(-err,
                 String8::format("destroySockets: %s", strerror(-err)));
@@ -273,117 +409,78 @@
     return binder::Status::ok();
 }
 
-// Parse a base64 encoded string into a vector of bytes.
-// On failure, return an empty vector.
-static std::vector<uint8_t> parseBase64(const std::string& input) {
-    std::vector<uint8_t> decoded;
-    size_t out_len;
-    if (EVP_DecodedLength(&out_len, input.size()) != 1) {
-        return decoded;
-    }
-    // out_len is now an upper bound on the output length.
-    decoded.resize(out_len);
-    if (EVP_DecodeBase64(decoded.data(), &out_len, decoded.size(),
-            reinterpret_cast<const uint8_t*>(input.data()), input.size()) == 1) {
-        // Possibly shrink the vector if the actual output was smaller than the bound.
-        decoded.resize(out_len);
-    } else {
-        decoded.clear();
-    }
-    if (out_len != SHA256_SIZE) {
-        decoded.clear();
-    }
-    return decoded;
-}
-
-binder::Status NetdNativeService::setResolverConfiguration(int32_t netId,
-        const std::vector<std::string>& servers, const std::vector<std::string>& domains,
-        const std::vector<int32_t>& params, const std::string& tlsName,
-        const std::vector<std::string>& tlsServers,
-        const std::vector<std::string>& tlsFingerprints) {
-    // This function intentionally does not lock within Netd, as Bionic is thread-safe.
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-
-    std::set<std::vector<uint8_t>> decoded_fingerprints;
-    for (const std::string& fingerprint : tlsFingerprints) {
-        std::vector<uint8_t> decoded = parseBase64(fingerprint);
-        if (decoded.empty()) {
-            return binder::Status::fromServiceSpecificError(EINVAL,
-                    String8::format("ResolverController error: bad fingerprint"));
-        }
-        decoded_fingerprints.emplace(decoded);
-    }
-
-    int err = gCtls->resolverCtrl.setResolverConfiguration(netId, servers, domains, params,
-            tlsName, tlsServers, decoded_fingerprints);
-    if (err != 0) {
-        return binder::Status::fromServiceSpecificError(-err,
-                String8::format("ResolverController error: %s", strerror(-err)));
-    }
-    return binder::Status::ok();
-}
-
-binder::Status NetdNativeService::getResolverInfo(int32_t netId,
-        std::vector<std::string>* servers, std::vector<std::string>* domains,
-        std::vector<int32_t>* params, std::vector<int32_t>* stats) {
-    // This function intentionally does not lock within Netd, as Bionic is thread-safe.
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-
-    int err = gCtls->resolverCtrl.getResolverInfo(netId, servers, domains, params, stats);
-    if (err != 0) {
-        return binder::Status::fromServiceSpecificError(-err,
-                String8::format("ResolverController error: %s", strerror(-err)));
-    }
-    return binder::Status::ok();
-}
-
 binder::Status NetdNativeService::tetherApplyDnsInterfaces(bool *ret) {
-    NETD_LOCKING_RPC(NETWORK_STACK, gCtls->tetherCtrl.lock)
-
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
     *ret = gCtls->tetherCtrl.applyDnsInterfaces();
     return binder::Status::ok();
 }
 
 namespace {
 
-void tetherAddStats(PersistableBundle *bundle, const TetherController::TetherStats& stats) {
-    String16 iface = String16(stats.extIface.c_str());
-    std::vector<int64_t> statsVector(INetd::TETHER_STATS_ARRAY_SIZE);
-
-    bundle->getLongVector(iface, &statsVector);
-    if (statsVector.size() == 0) {
-        for (int i = 0; i < INetd::TETHER_STATS_ARRAY_SIZE; i++) statsVector.push_back(0);
+void tetherAddStatsByInterface(TetherController::TetherStats* tetherStatsParcel,
+                               const TetherController::TetherStats& tetherStats) {
+    if (tetherStatsParcel->extIface == tetherStats.extIface) {
+        tetherStatsParcel->rxBytes += tetherStats.rxBytes;
+        tetherStatsParcel->rxPackets += tetherStats.rxPackets;
+        tetherStatsParcel->txBytes += tetherStats.txBytes;
+        tetherStatsParcel->txPackets += tetherStats.txPackets;
     }
+}
 
-    statsVector[INetd::TETHER_STATS_RX_BYTES]   += stats.rxBytes;
-    statsVector[INetd::TETHER_STATS_RX_PACKETS] += stats.rxPackets;
-    statsVector[INetd::TETHER_STATS_TX_BYTES]   += stats.txBytes;
-    statsVector[INetd::TETHER_STATS_TX_PACKETS] += stats.txPackets;
+TetherStatsParcel toTetherStatsParcel(const TetherController::TetherStats& stats) {
+    TetherStatsParcel result;
+    result.iface = stats.extIface;
+    result.rxBytes = stats.rxBytes;
+    result.rxPackets = stats.rxPackets;
+    result.txBytes = stats.txBytes;
+    result.txPackets = stats.txPackets;
+    return result;
+}
 
-    bundle->putLongVector(iface, statsVector);
+void setTetherStatsParcelVecByInterface(std::vector<TetherStatsParcel>* tetherStatsVec,
+                                        const TetherController::TetherStatsList& statsList) {
+    std::map<std::string, TetherController::TetherStats> statsMap;
+    for (const auto& stats : statsList) {
+        auto iter = statsMap.find(stats.extIface);
+        if (iter != statsMap.end()) {
+            tetherAddStatsByInterface(&(iter->second), stats);
+        } else {
+            statsMap.insert(
+                    std::pair<std::string, TetherController::TetherStats>(stats.extIface, stats));
+        }
+    }
+    for (auto iter = statsMap.begin(); iter != statsMap.end(); iter++) {
+        tetherStatsVec->push_back(toTetherStatsParcel(iter->second));
+    }
+}
+
+std::vector<std::string> tetherStatsParcelVecToStringVec(std::vector<TetherStatsParcel>* tVec) {
+    std::vector<std::string> result;
+    for (const auto& t : *tVec) {
+        result.push_back(StringPrintf("%s:%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64,
+                                      t.iface.c_str(), t.rxBytes, t.rxPackets, t.txBytes,
+                                      t.txPackets));
+    }
+    return result;
 }
 
 }  // namespace
 
-binder::Status NetdNativeService::tetherGetStats(PersistableBundle *bundle) {
-    NETD_LOCKING_RPC(NETWORK_STACK, gCtls->tetherCtrl.lock)
-
+binder::Status NetdNativeService::tetherGetStats(
+        std::vector<TetherStatsParcel>* tetherStatsParcelVec) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
     const auto& statsList = gCtls->tetherCtrl.getTetherStats();
     if (!isOk(statsList)) {
-        return toBinderStatus(statsList);
+        return asBinderStatus(statsList);
     }
-
-    for (const auto& stats : statsList.value()) {
-        tetherAddStats(bundle, stats);
-    }
-
+    setTetherStatsParcelVecByInterface(tetherStatsParcelVec, statsList.value());
+    auto statsResults = tetherStatsParcelVecToStringVec(tetherStatsParcelVec);
     return binder::Status::ok();
 }
 
 binder::Status NetdNativeService::interfaceAddAddress(const std::string &ifName,
         const std::string &addrString, int prefixLength) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-
+    ENFORCE_INTERNAL_PERMISSIONS();
     const int err = InterfaceController::addAddress(
             ifName.c_str(), addrString.c_str(), prefixLength);
     if (err != 0) {
@@ -395,8 +492,7 @@
 
 binder::Status NetdNativeService::interfaceDelAddress(const std::string &ifName,
         const std::string &addrString, int prefixLength) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-
+    ENFORCE_INTERNAL_PERMISSIONS();
     const int err = InterfaceController::delAddress(
             ifName.c_str(), addrString.c_str(), prefixLength);
     if (err != 0) {
@@ -406,25 +502,25 @@
     return binder::Status::ok();
 }
 
-binder::Status NetdNativeService::setProcSysNet(
-        int32_t family, int32_t which, const std::string &ifname, const std::string &parameter,
-        const std::string &value) {
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+namespace {
 
-    const char *familyStr;
-    switch (family) {
+std::tuple<binder::Status, const char*, const char*> getPathComponents(int32_t ipversion,
+                                                                       int32_t category) {
+    const char* ipversionStr = nullptr;
+    switch (ipversion) {
         case INetd::IPV4:
-            familyStr = "ipv4";
+            ipversionStr = "ipv4";
             break;
         case INetd::IPV6:
-            familyStr = "ipv6";
+            ipversionStr = "ipv6";
             break;
         default:
-            return binder::Status::fromServiceSpecificError(EAFNOSUPPORT, String8("Bad family"));
+            return {binder::Status::fromServiceSpecificError(EAFNOSUPPORT, "Bad IP version"),
+                    nullptr, nullptr};
     }
 
-    const char *whichStr;
-    switch (which) {
+    const char* whichStr = nullptr;
+    switch (category) {
         case INetd::CONF:
             whichStr = "conf";
             break;
@@ -432,47 +528,56 @@
             whichStr = "neigh";
             break;
         default:
-            return binder::Status::fromServiceSpecificError(EINVAL, String8("Bad category"));
+            return {binder::Status::fromServiceSpecificError(EINVAL, "Bad category"), nullptr,
+                    nullptr};
     }
 
-    const int err = InterfaceController::setParameter(
-            familyStr, whichStr, ifname.c_str(), parameter.c_str(),
-            value.c_str());
-    if (err != 0) {
-        return binder::Status::fromServiceSpecificError(-err,
-                String8::format("ResolverController error: %s", strerror(-err)));
+    return {binder::Status::ok(), ipversionStr, whichStr};
+}
+
+}  // namespace
+
+binder::Status NetdNativeService::getProcSysNet(int32_t ipversion, int32_t which,
+                                                const std::string& ifname,
+                                                const std::string& parameter, std::string* value) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    const auto pathParts = getPathComponents(ipversion, which);
+    const auto& pathStatus = std::get<0>(pathParts);
+    if (!pathStatus.isOk()) {
+        return pathStatus;
     }
-    return binder::Status::ok();
+
+    const int err = InterfaceController::getParameter(std::get<1>(pathParts),
+                                                      std::get<2>(pathParts), ifname.c_str(),
+                                                      parameter.c_str(), value);
+    return statusFromErrcode(err);
 }
 
-binder::Status NetdNativeService::getMetricsReportingLevel(int *reportingLevel) {
-    // This function intentionally does not lock, since the only thing it does is one read from an
-    // atomic_int.
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ENFORCE_DEBUGGABLE();
+binder::Status NetdNativeService::setProcSysNet(int32_t ipversion, int32_t which,
+                                                const std::string& ifname,
+                                                const std::string& parameter,
+                                                const std::string& value) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    const auto pathParts = getPathComponents(ipversion, which);
+    const auto& pathStatus = std::get<0>(pathParts);
+    if (!pathStatus.isOk()) {
+        return pathStatus;
+    }
 
-    *reportingLevel = gCtls->eventReporter.getMetricsReportingLevel();
-    return binder::Status::ok();
+    const int err = InterfaceController::setParameter(std::get<1>(pathParts),
+                                                      std::get<2>(pathParts), ifname.c_str(),
+                                                      parameter.c_str(), value.c_str());
+    return statusFromErrcode(err);
 }
 
-binder::Status NetdNativeService::setMetricsReportingLevel(const int reportingLevel) {
-    // This function intentionally does not lock, since the only thing it does is one write to an
-    // atomic_int.
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ENFORCE_DEBUGGABLE();
-
-    return (gCtls->eventReporter.setMetricsReportingLevel(reportingLevel) == 0)
-            ? binder::Status::ok()
-            : binder::Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT);
-}
-
-binder::Status NetdNativeService::ipSecSetEncapSocketOwner(const android::base::unique_fd& socket,
-                                                      int newUid) {
-    ENFORCE_PERMISSION(NETWORK_STACK)
-    ALOGD("ipSecSetEncapSocketOwner()");
+binder::Status NetdNativeService::ipSecSetEncapSocketOwner(const ParcelFileDescriptor& socket,
+                                                           int newUid) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gLog.log("ipSecSetEncapSocketOwner()");
 
     uid_t callerUid = IPCThreadState::self()->getCallingUid();
-    return asBinderStatus(gCtls->xfrmCtrl.ipSecSetEncapSocketOwner(socket, newUid, callerUid));
+    return asBinderStatus(
+            gCtls->xfrmCtrl.ipSecSetEncapSocketOwner(socket.get(), newUid, callerUid));
 }
 
 binder::Status NetdNativeService::ipSecAllocateSpi(
@@ -482,8 +587,8 @@
         int32_t inSpi,
         int32_t* outSpi) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ALOGD("ipSecAllocateSpi()");
+    ENFORCE_INTERNAL_PERMISSIONS();
+    gLog.log("ipSecAllocateSpi()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecAllocateSpi(
                     transformId,
                     sourceAddress,
@@ -493,219 +598,591 @@
 }
 
 binder::Status NetdNativeService::ipSecAddSecurityAssociation(
-        int32_t transformId,
-        int32_t mode,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t underlyingNetId,
-        int32_t spi,
-        int32_t markValue,
-        int32_t markMask,
-        const std::string& authAlgo, const std::vector<uint8_t>& authKey, int32_t authTruncBits,
-        const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits,
-        const std::string& aeadAlgo, const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits,
-        int32_t encapType,
-        int32_t encapLocalPort,
-        int32_t encapRemotePort) {
+        int32_t transformId, int32_t mode, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+        int32_t markValue, int32_t markMask, const std::string& authAlgo,
+        const std::vector<uint8_t>& authKey, int32_t authTruncBits, const std::string& cryptAlgo,
+        const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits, const std::string& aeadAlgo,
+        const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+        int32_t encapLocalPort, int32_t encapRemotePort, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ALOGD("ipSecAddSecurityAssociation()");
+    ENFORCE_INTERNAL_PERMISSIONS();
+    gLog.log("ipSecAddSecurityAssociation()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecAddSecurityAssociation(
-              transformId, mode, sourceAddress, destinationAddress,
-              underlyingNetId,
-              spi, markValue, markMask,
-              authAlgo, authKey, authTruncBits,
-              cryptAlgo, cryptKey, cryptTruncBits,
-              aeadAlgo, aeadKey, aeadIcvBits,
-              encapType, encapLocalPort, encapRemotePort));
+            transformId, mode, sourceAddress, destinationAddress, underlyingNetId, spi, markValue,
+            markMask, authAlgo, authKey, authTruncBits, cryptAlgo, cryptKey, cryptTruncBits,
+            aeadAlgo, aeadKey, aeadIcvBits, encapType, encapLocalPort, encapRemotePort,
+            interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecDeleteSecurityAssociation(
-        int32_t transformId,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t spi,
-        int32_t markValue,
-        int32_t markMask) {
+        int32_t transformId, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t spi, int32_t markValue, int32_t markMask,
+        int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ALOGD("ipSecDeleteSecurityAssociation()");
+    ENFORCE_INTERNAL_PERMISSIONS();
+    gLog.log("ipSecDeleteSecurityAssociation()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecDeleteSecurityAssociation(
-                    transformId,
-                    sourceAddress,
-                    destinationAddress,
-                    spi,
-                    markValue,
-                    markMask));
+            transformId, sourceAddress, destinationAddress, spi, markValue, markMask, interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecApplyTransportModeTransform(
-        const android::base::unique_fd& socket,
-        int32_t transformId,
-        int32_t direction,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t spi) {
+        const ParcelFileDescriptor& socket, int32_t transformId, int32_t direction,
+        const std::string& sourceAddress, const std::string& destinationAddress, int32_t spi) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ALOGD("ipSecApplyTransportModeTransform()");
+    ENFORCE_INTERNAL_PERMISSIONS();
+    gLog.log("ipSecApplyTransportModeTransform()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecApplyTransportModeTransform(
-                    socket,
-                    transformId,
-                    direction,
-                    sourceAddress,
-                    destinationAddress,
-                    spi));
+            socket.get(), transformId, direction, sourceAddress, destinationAddress, spi));
 }
 
 binder::Status NetdNativeService::ipSecRemoveTransportModeTransform(
-            const android::base::unique_fd& socket) {
+        const ParcelFileDescriptor& socket) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
-    ALOGD("ipSecRemoveTransportModeTransform()");
-    return asBinderStatus(gCtls->xfrmCtrl.ipSecRemoveTransportModeTransform(
-                    socket));
+    ENFORCE_INTERNAL_PERMISSIONS();
+    gLog.log("ipSecRemoveTransportModeTransform()");
+    return asBinderStatus(gCtls->xfrmCtrl.ipSecRemoveTransportModeTransform(socket.get()));
 }
 
-binder::Status NetdNativeService::ipSecAddSecurityPolicy(
-        int32_t transformId,
-        int32_t direction,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t spi,
-        int32_t markValue,
-        int32_t markMask){
+binder::Status NetdNativeService::ipSecAddSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                                         int32_t direction,
+                                                         const std::string& tmplSrcAddress,
+                                                         const std::string& tmplDstAddress,
+                                                         int32_t spi, int32_t markValue,
+                                                         int32_t markMask, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    ALOGD("ipSecAddSecurityPolicy()");
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gLog.log("ipSecAddSecurityPolicy()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecAddSecurityPolicy(
-                    transformId,
-                    direction,
-                    sourceAddress,
-                    destinationAddress,
-                    spi,
-                    markValue,
-                    markMask));
+            transformId, selAddrFamily, direction, tmplSrcAddress, tmplDstAddress, spi, markValue,
+            markMask, interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecUpdateSecurityPolicy(
-        int32_t transformId,
-        int32_t direction,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t spi,
-        int32_t markValue,
-        int32_t markMask){
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    ALOGD("ipSecAddSecurityPolicy()");
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gLog.log("ipSecAddSecurityPolicy()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecUpdateSecurityPolicy(
-                    transformId,
-                    direction,
-                    sourceAddress,
-                    destinationAddress,
-                    spi,
-                    markValue,
-                    markMask));
+            transformId, selAddrFamily, direction, tmplSrcAddress, tmplDstAddress, spi, markValue,
+            markMask, interfaceId));
 }
 
-binder::Status NetdNativeService::ipSecDeleteSecurityPolicy(
-        int32_t transformId,
-        int32_t direction,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t markValue,
-        int32_t markMask){
+binder::Status NetdNativeService::ipSecDeleteSecurityPolicy(int32_t transformId,
+                                                            int32_t selAddrFamily,
+                                                            int32_t direction, int32_t markValue,
+                                                            int32_t markMask, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    ALOGD("ipSecAddSecurityPolicy()");
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gLog.log("ipSecAddSecurityPolicy()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecDeleteSecurityPolicy(
-                    transformId,
-                    direction,
-                    sourceAddress,
-                    destinationAddress,
-                    markValue,
-                    markMask));
+            transformId, selAddrFamily, direction, markValue, markMask, interfaceId));
 }
 
-binder::Status NetdNativeService::addVirtualTunnelInterface(
-        const std::string& deviceName,
-        const std::string& localAddress,
-        const std::string& remoteAddress,
-        int32_t iKey,
-        int32_t oKey) {
+binder::Status NetdNativeService::ipSecAddTunnelInterface(const std::string& deviceName,
+                                                          const std::string& localAddress,
+                                                          const std::string& remoteAddress,
+                                                          int32_t iKey, int32_t oKey,
+                                                          int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    ALOGD("addVirtualTunnelInterface()");
-    int ret = gCtls->xfrmCtrl.addVirtualTunnelInterface(
-                             deviceName,
-                             localAddress,
-                             remoteAddress,
-                             iKey,
-                             oKey,
-                             false);
-
-    return (ret == 0) ? binder::Status::ok() :
-                        asBinderStatus(netdutils::statusFromErrno(
-                                       ret, "Error in creating virtual tunnel interface."));
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    netdutils::Status result = gCtls->xfrmCtrl.ipSecAddTunnelInterface(
+            deviceName, localAddress, remoteAddress, iKey, oKey, interfaceId, false);
+    return binder::Status::ok();
 }
 
-binder::Status NetdNativeService::updateVirtualTunnelInterface(
-        const std::string& deviceName,
-        const std::string& localAddress,
-        const std::string& remoteAddress,
-        int32_t iKey,
-        int32_t oKey) {
+binder::Status NetdNativeService::ipSecUpdateTunnelInterface(const std::string& deviceName,
+                                                             const std::string& localAddress,
+                                                             const std::string& remoteAddress,
+                                                             int32_t iKey, int32_t oKey,
+                                                             int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    ALOGD("updateVirtualTunnelInterface()");
-    int ret = gCtls->xfrmCtrl.addVirtualTunnelInterface(
-                             deviceName,
-                             localAddress,
-                             remoteAddress,
-                             iKey,
-                             oKey,
-                             true);
-
-    return (ret == 0) ? binder::Status::ok() :
-                        asBinderStatus(netdutils::statusFromErrno(
-                                       ret, "Error in updating virtual tunnel interface."));
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    netdutils::Status result = gCtls->xfrmCtrl.ipSecAddTunnelInterface(
+            deviceName, localAddress, remoteAddress, iKey, oKey, interfaceId, true);
+    return binder::Status::ok();
 }
 
-binder::Status NetdNativeService::removeVirtualTunnelInterface(const std::string& deviceName) {
+binder::Status NetdNativeService::ipSecRemoveTunnelInterface(const std::string& deviceName) {
     // Necessary locking done in IpSecService and kernel
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    ALOGD("removeVirtualTunnelInterface()");
-    int ret = gCtls->xfrmCtrl.removeVirtualTunnelInterface(deviceName);
-
-    return (ret == 0) ? binder::Status::ok() :
-                        asBinderStatus(netdutils::statusFromErrno(
-                                       ret, "Error in removing virtual tunnel interface."));
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    netdutils::Status result = gCtls->xfrmCtrl.ipSecRemoveTunnelInterface(deviceName);
+    return binder::Status::ok();
 }
 
 binder::Status NetdNativeService::setIPv6AddrGenMode(const std::string& ifName,
                                                      int32_t mode) {
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    return toBinderStatus(InterfaceController::setIPv6AddrGenMode(ifName, mode));
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    return asBinderStatus(InterfaceController::setIPv6AddrGenMode(ifName, mode));
 }
 
 binder::Status NetdNativeService::wakeupAddInterface(const std::string& ifName,
                                                      const std::string& prefix, int32_t mark,
                                                      int32_t mask) {
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    return toBinderStatus(gCtls->wakeupCtrl.addInterface(ifName, prefix, mark, mask));
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    return asBinderStatus(gCtls->wakeupCtrl.addInterface(ifName, prefix, mark, mask));
 }
 
 binder::Status NetdNativeService::wakeupDelInterface(const std::string& ifName,
                                                      const std::string& prefix, int32_t mark,
                                                      int32_t mask) {
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    return toBinderStatus(gCtls->wakeupCtrl.delInterface(ifName, prefix, mark, mask));
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    return asBinderStatus(gCtls->wakeupCtrl.delInterface(ifName, prefix, mark, mask));
 }
 
-binder::Status NetdNativeService::trafficCheckBpfStatsEnable(bool* ret) {
-    ENFORCE_PERMISSION(NETWORK_STACK);
-    *ret = gCtls->trafficCtrl.checkBpfStatsEnable();
+binder::Status NetdNativeService::trafficSwapActiveStatsMap() {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    return asBinderStatus(gCtls->trafficCtrl.swapActiveStatsMap());
+}
+
+binder::Status NetdNativeService::idletimerAddInterface(const std::string& ifName, int32_t timeout,
+                                                        const std::string& classLabel) {
+    NETD_LOCKING_RPC(gCtls->idletimerCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res =
+            gCtls->idletimerCtrl.addInterfaceIdletimer(ifName.c_str(), timeout, classLabel.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::idletimerRemoveInterface(const std::string& ifName,
+                                                           int32_t timeout,
+                                                           const std::string& classLabel) {
+    NETD_LOCKING_RPC(gCtls->idletimerCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->idletimerCtrl.removeInterfaceIdletimer(ifName.c_str(), timeout,
+                                                            classLabel.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::strictUidCleartextPenalty(int32_t uid, int32_t policyPenalty) {
+    NETD_LOCKING_RPC(gCtls->strictCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    StrictPenalty penalty;
+    switch (policyPenalty) {
+        case INetd::PENALTY_POLICY_REJECT:
+            penalty = REJECT;
+            break;
+        case INetd::PENALTY_POLICY_LOG:
+            penalty = LOG;
+            break;
+        case INetd::PENALTY_POLICY_ACCEPT:
+            penalty = ACCEPT;
+            break;
+        default:
+            return statusFromErrcode(-EINVAL);
+            break;
+    }
+    int res = gCtls->strictCtrl.setUidCleartextPenalty((uid_t) uid, penalty);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::clatdStart(const std::string& ifName,
+                                             const std::string& nat64Prefix, std::string* v6Addr) {
+    ENFORCE_ANY_PERMISSION(PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->clatdCtrl.startClatd(ifName.c_str(), nat64Prefix, v6Addr);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::clatdStop(const std::string& ifName) {
+    ENFORCE_ANY_PERMISSION(PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->clatdCtrl.stopClatd(ifName.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::ipfwdEnabled(bool* status) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    *status = (gCtls->tetherCtrl.getIpfwdRequesterList().size() > 0) ? true : false;
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::ipfwdGetRequesterList(std::vector<std::string>* requesterList) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    for (const auto& requester : gCtls->tetherCtrl.getIpfwdRequesterList()) {
+        requesterList->push_back(requester);
+    }
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::ipfwdEnableForwarding(const std::string& requester) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = (gCtls->tetherCtrl.enableForwarding(requester.c_str())) ? 0 : -EREMOTEIO;
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::ipfwdDisableForwarding(const std::string& requester) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = (gCtls->tetherCtrl.disableForwarding(requester.c_str())) ? 0 : -EREMOTEIO;
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::ipfwdAddInterfaceForward(const std::string& fromIface,
+                                                           const std::string& toIface) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    int res = RouteController::enableTethering(fromIface.c_str(), toIface.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::ipfwdRemoveInterfaceForward(const std::string& fromIface,
+                                                              const std::string& toIface) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    int res = RouteController::disableTethering(fromIface.c_str(), toIface.c_str());
+    return statusFromErrcode(res);
+}
+
+namespace {
+std::string addCurlyBrackets(const std::string& s) {
+    return "{" + s + "}";
+}
+
+}  // namespace
+
+binder::Status NetdNativeService::interfaceGetList(std::vector<std::string>* interfaceListResult) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    const auto& ifaceList = InterfaceController::getIfaceNames();
+
+    interfaceListResult->clear();
+    interfaceListResult->reserve(ifaceList.value().size());
+    interfaceListResult->insert(end(*interfaceListResult), begin(ifaceList.value()),
+                                end(ifaceList.value()));
+
+    return binder::Status::ok();
+}
+
+std::string interfaceConfigurationParcelToString(const InterfaceConfigurationParcel& cfg) {
+    std::vector<std::string> result{cfg.ifName, cfg.hwAddr, cfg.ipv4Addr,
+                                    std::to_string(cfg.prefixLength)};
+    result.insert(end(result), begin(cfg.flags), end(cfg.flags));
+    return addCurlyBrackets(base::Join(result, ", "));
+}
+
+binder::Status NetdNativeService::interfaceGetCfg(
+        const std::string& ifName, InterfaceConfigurationParcel* interfaceGetCfgResult) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    auto entry = gLog.newEntry().prettyFunction(__PRETTY_FUNCTION__).arg(ifName);
+
+    const auto& cfgRes = InterfaceController::getCfg(ifName);
+    RETURN_BINDER_STATUS_IF_NOT_OK(entry, cfgRes);
+
+    *interfaceGetCfgResult = cfgRes.value();
+    gLog.log(entry.returns(interfaceConfigurationParcelToString(*interfaceGetCfgResult))
+                     .withAutomaticDuration());
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::interfaceSetCfg(const InterfaceConfigurationParcel& cfg) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    auto entry = gLog.newEntry()
+                         .prettyFunction(__PRETTY_FUNCTION__)
+                         .arg(interfaceConfigurationParcelToString(cfg));
+
+    const auto& res = InterfaceController::setCfg(cfg);
+    RETURN_BINDER_STATUS_IF_NOT_OK(entry, res);
+
+    gLog.log(entry.withAutomaticDuration());
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::interfaceSetIPv6PrivacyExtensions(const std::string& ifName,
+                                                                    bool enable) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = InterfaceController::setIPv6PrivacyExtensions(ifName.c_str(), enable);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::interfaceClearAddrs(const std::string& ifName) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = InterfaceController::clearAddrs(ifName.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::interfaceSetEnableIPv6(const std::string& ifName, bool enable) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = InterfaceController::setEnableIPv6(ifName.c_str(), enable);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::interfaceSetMtu(const std::string& ifName, int32_t mtuValue) {
+    NETD_LOCKING_RPC(InterfaceController::mutex, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    std::string mtu = std::to_string(mtuValue);
+    int res = InterfaceController::setMtu(ifName.c_str(), mtu.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherStart(const std::vector<std::string>& dhcpRanges) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    if (dhcpRanges.size() % 2 == 1) {
+        return statusFromErrcode(-EINVAL);
+    }
+    int res = gCtls->tetherCtrl.startTethering(dhcpRanges);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherStop() {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->tetherCtrl.stopTethering();
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherIsEnabled(bool* enabled) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    *enabled = gCtls->tetherCtrl.isTetheringStarted();
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::tetherInterfaceAdd(const std::string& ifName) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->tetherCtrl.tetherInterface(ifName.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherInterfaceRemove(const std::string& ifName) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->tetherCtrl.untetherInterface(ifName.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherInterfaceList(std::vector<std::string>* ifList) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    for (const auto& ifname : gCtls->tetherCtrl.getTetheredInterfaceList()) {
+        ifList->push_back(ifname);
+    }
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::tetherDnsSet(int32_t netId,
+                                               const std::vector<std::string>& dnsAddrs) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->tetherCtrl.setDnsForwarders(netId, dnsAddrs);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherDnsList(std::vector<std::string>* dnsList) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    for (const auto& fwdr : gCtls->tetherCtrl.getDnsForwarders()) {
+        dnsList->push_back(fwdr);
+    }
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::networkAddRoute(int32_t netId, const std::string& ifName,
+                                                  const std::string& destination,
+                                                  const std::string& nextHop) {
+    // Public methods of NetworkController are thread-safe.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    bool legacy = false;
+    uid_t uid = 0;  // UID is only meaningful for legacy routes.
+    int res = gCtls->netCtrl.addRoute(netId, ifName.c_str(), destination.c_str(),
+                                      nextHop.empty() ? nullptr : nextHop.c_str(), legacy, uid);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkRemoveRoute(int32_t netId, const std::string& ifName,
+                                                     const std::string& destination,
+                                                     const std::string& nextHop) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    bool legacy = false;
+    uid_t uid = 0;  // UID is only meaningful for legacy routes.
+    int res = gCtls->netCtrl.removeRoute(netId, ifName.c_str(), destination.c_str(),
+                                         nextHop.empty() ? nullptr : nextHop.c_str(), legacy, uid);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkAddLegacyRoute(int32_t netId, const std::string& ifName,
+                                                        const std::string& destination,
+                                                        const std::string& nextHop, int32_t uid) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    bool legacy = true;
+    int res = gCtls->netCtrl.addRoute(netId, ifName.c_str(), destination.c_str(),
+                                      nextHop.empty() ? nullptr : nextHop.c_str(), legacy,
+                                      (uid_t) uid);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkRemoveLegacyRoute(int32_t netId, const std::string& ifName,
+                                                           const std::string& destination,
+                                                           const std::string& nextHop,
+                                                           int32_t uid) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    bool legacy = true;
+    int res = gCtls->netCtrl.removeRoute(netId, ifName.c_str(), destination.c_str(),
+                                         nextHop.empty() ? nullptr : nextHop.c_str(), legacy,
+                                         (uid_t) uid);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkGetDefault(int32_t* netId) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    *netId = gCtls->netCtrl.getDefaultNetwork();
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::networkSetDefault(int32_t netId) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    int res = gCtls->netCtrl.setDefaultNetwork(netId);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkClearDefault() {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    unsigned netId = NETID_UNSET;
+    int res = gCtls->netCtrl.setDefaultNetwork(netId);
+    return statusFromErrcode(res);
+}
+
+std::vector<uid_t> NetdNativeService::intsToUids(const std::vector<int32_t>& intUids) {
+    return {begin(intUids), end(intUids)};
+}
+
+Permission NetdNativeService::convertPermission(int32_t permission) {
+    switch (permission) {
+        case INetd::PERMISSION_NETWORK:
+            return Permission::PERMISSION_NETWORK;
+        case INetd::PERMISSION_SYSTEM:
+            return Permission::PERMISSION_SYSTEM;
+        default:
+            return Permission::PERMISSION_NONE;
+    }
+}
+
+binder::Status NetdNativeService::networkSetPermissionForNetwork(int32_t netId,
+                                                                 int32_t permission) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    std::vector<unsigned> netIds = {(unsigned) netId};
+    int res = gCtls->netCtrl.setPermissionForNetworks(convertPermission(permission), netIds);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkSetPermissionForUser(int32_t permission,
+                                                              const std::vector<int32_t>& uids) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gCtls->netCtrl.setPermissionForUsers(convertPermission(permission), intsToUids(uids));
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::networkClearPermissionForUser(const std::vector<int32_t>& uids) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    Permission permission = Permission::PERMISSION_NONE;
+    gCtls->netCtrl.setPermissionForUsers(permission, intsToUids(uids));
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::NetdNativeService::networkSetProtectAllow(int32_t uid) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    std::vector<uid_t> uids = {(uid_t) uid};
+    gCtls->netCtrl.allowProtect(uids);
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::networkSetProtectDeny(int32_t uid) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    std::vector<uid_t> uids = {(uid_t) uid};
+    gCtls->netCtrl.denyProtect(uids);
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::networkCanProtect(int32_t uid, bool* ret) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    *ret = gCtls->netCtrl.canProtect((uid_t) uid);
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::trafficSetNetPermForUids(int32_t permission,
+                                                           const std::vector<int32_t>& uids) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gCtls->trafficCtrl.setPermissionForUids(permission, intsToUids(uids));
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::firewallSetFirewallType(int32_t firewallType) {
+    NETD_LOCKING_RPC(gCtls->firewallCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    auto type = static_cast<FirewallType>(firewallType);
+
+    int res = gCtls->firewallCtrl.setFirewallType(type);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::firewallSetInterfaceRule(const std::string& ifName,
+                                                           int32_t firewallRule) {
+    NETD_LOCKING_RPC(gCtls->firewallCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    auto rule = static_cast<FirewallRule>(firewallRule);
+
+    int res = gCtls->firewallCtrl.setInterfaceRule(ifName.c_str(), rule);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::firewallSetUidRule(int32_t childChain, int32_t uid,
+                                                     int32_t firewallRule) {
+    NETD_LOCKING_RPC(gCtls->firewallCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    auto chain = static_cast<ChildChain>(childChain);
+    auto rule = static_cast<FirewallRule>(firewallRule);
+
+    int res = gCtls->firewallCtrl.setUidRule(chain, uid, rule);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::firewallEnableChildChain(int32_t childChain, bool enable) {
+    NETD_LOCKING_RPC(gCtls->firewallCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    auto chain = static_cast<ChildChain>(childChain);
+
+    int res = gCtls->firewallCtrl.enableChildChains(chain, enable);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::firewallAddUidInterfaceRules(const std::string& ifName,
+                                                               const std::vector<int32_t>& uids) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    return asBinderStatus(gCtls->trafficCtrl.addUidInterfaceRules(
+            RouteController::getIfIndex(ifName.c_str()), uids));
+}
+
+binder::Status NetdNativeService::firewallRemoveUidInterfaceRules(
+        const std::vector<int32_t>& uids) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    return asBinderStatus(gCtls->trafficCtrl.removeUidInterfaceRules(uids));
+}
+
+binder::Status NetdNativeService::tetherAddForward(const std::string& intIface,
+                                                   const std::string& extIface) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+
+    int res = gCtls->tetherCtrl.enableNat(intIface.c_str(), extIface.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::tetherRemoveForward(const std::string& intIface,
+                                                      const std::string& extIface) {
+    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+    int res = gCtls->tetherCtrl.disableNat(intIface.c_str(), extIface.c_str());
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::setTcpRWmemorySize(const std::string& rmemValues,
+                                                     const std::string& wmemValues) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    if (!WriteStringToFile(rmemValues, TCP_RMEM_PROC_FILE)) {
+        int ret = -errno;
+        return statusFromErrcode(ret);
+    }
+
+    if (!WriteStringToFile(wmemValues, TCP_WMEM_PROC_FILE)) {
+        int ret = -errno;
+        return statusFromErrcode(ret);
+    }
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::registerUnsolicitedEventListener(
+        const android::sp<android::net::INetdUnsolicitedEventListener>& listener) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    gCtls->eventReporter.registerUnsolEventListener(listener);
+    return binder::Status::ok();
+}
+
+binder::Status NetdNativeService::getOemNetd(android::sp<android::IBinder>* listener) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    *listener = com::android::internal::net::OemNetdListener::getListener();
+
     return binder::Status::ok();
 }
 
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index e7e9299..68c9886 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -20,15 +20,16 @@
 #include <vector>
 
 #include <binder/BinderService.h>
+#include <netdutils/Log.h>
 
 #include "android/net/BnNetd.h"
-#include "android/net/UidRange.h"
 
 namespace android {
 namespace net {
 
 class NetdNativeService : public BinderService<NetdNativeService>, public BnNetd {
   public:
+    NetdNativeService();
     static status_t start();
     static char const* getServiceName() { return "netd"; }
     virtual status_t dump(int fd, const Vector<String16> &args) override;
@@ -37,41 +38,74 @@
 
     // Firewall commands.
     binder::Status firewallReplaceUidChain(
-            const String16& chainName, bool isWhitelist,
+            const std::string& chainName, bool isWhitelist,
             const std::vector<int32_t>& uids, bool *ret) override;
+    binder::Status firewallSetFirewallType(int32_t firewallType) override;
+    binder::Status firewallSetInterfaceRule(const std::string& ifName,
+                                            int32_t firewallRule) override;
+    binder::Status firewallSetUidRule(int32_t childChain, int32_t uid,
+                                      int32_t firewallRule) override;
+    binder::Status firewallEnableChildChain(int32_t childChain, bool enable) override;
+    binder::Status firewallAddUidInterfaceRules(const std::string& ifName,
+                                                const std::vector<int32_t>& uids) override;
+    binder::Status firewallRemoveUidInterfaceRules(const std::vector<int32_t>& uids) override;
 
     // Bandwidth control commands.
     binder::Status bandwidthEnableDataSaver(bool enable, bool *ret) override;
+    binder::Status bandwidthSetInterfaceQuota(const std::string& ifName, int64_t bytes) override;
+    binder::Status bandwidthRemoveInterfaceQuota(const std::string& ifName) override;
+    binder::Status bandwidthSetInterfaceAlert(const std::string& ifName, int64_t bytes) override;
+    binder::Status bandwidthRemoveInterfaceAlert(const std::string& ifName) override;
+    binder::Status bandwidthSetGlobalAlert(int64_t bytes) override;
+    binder::Status bandwidthAddNaughtyApp(int32_t uid) override;
+    binder::Status bandwidthRemoveNaughtyApp(int32_t uid) override;
+    binder::Status bandwidthAddNiceApp(int32_t uid) override;
+    binder::Status bandwidthRemoveNiceApp(int32_t uid) override;
 
     // Network and routing commands.
-    binder::Status networkCreatePhysical(int32_t netId, const std::string& permission)
-            override;
-    binder::Status networkCreateVpn(int32_t netId, bool hasDns, bool secure) override;
+    binder::Status networkCreatePhysical(int32_t netId, int32_t permission) override;
+    binder::Status networkCreateVpn(int32_t netId, bool secure) override;
     binder::Status networkDestroy(int32_t netId) override;
 
     binder::Status networkAddInterface(int32_t netId, const std::string& iface) override;
     binder::Status networkRemoveInterface(int32_t netId, const std::string& iface) override;
 
-    binder::Status networkAddUidRanges(int32_t netId, const std::vector<UidRange>& uids)
-            override;
-    binder::Status networkRemoveUidRanges(int32_t netId, const std::vector<UidRange>& uids)
-            override;
-    binder::Status networkRejectNonSecureVpn(bool enable, const std::vector<UidRange>& uids)
-            override;
+    binder::Status networkAddUidRanges(int32_t netId,
+                                       const std::vector<UidRangeParcel>& uids) override;
+    binder::Status networkRemoveUidRanges(int32_t netId,
+                                          const std::vector<UidRangeParcel>& uids) override;
+    binder::Status networkRejectNonSecureVpn(bool enable,
+                                             const std::vector<UidRangeParcel>& uids) override;
+    binder::Status networkAddRoute(int32_t netId, const std::string& ifName,
+                                   const std::string& destination,
+                                   const std::string& nextHop) override;
+    binder::Status networkRemoveRoute(int32_t netId, const std::string& ifName,
+                                      const std::string& destination,
+                                      const std::string& nextHop) override;
+    binder::Status networkAddLegacyRoute(int32_t netId, const std::string& ifName,
+                                         const std::string& destination, const std::string& nextHop,
+                                         int32_t uid) override;
+    binder::Status networkRemoveLegacyRoute(int32_t netId, const std::string& ifName,
+                                            const std::string& destination,
+                                            const std::string& nextHop, int32_t uid) override;
+    binder::Status networkSetDefault(int32_t netId) override;
+    binder::Status networkClearDefault() override;
+    binder::Status networkSetPermissionForNetwork(int32_t netId, int32_t permission) override;
+    binder::Status networkSetPermissionForUser(int32_t permission,
+                                               const std::vector<int32_t>& uids) override;
+    binder::Status networkClearPermissionForUser(const std::vector<int32_t>& uids) override;
+    binder::Status networkSetProtectAllow(int32_t uid) override;
+    binder::Status networkSetProtectDeny(int32_t uid) override;
+    // For test (internal use only).
+    binder::Status networkGetDefault(int32_t* netId) override;
+    binder::Status networkCanProtect(int32_t uid, bool* ret) override;
+
+    binder::Status trafficSetNetPermForUids(int32_t permission,
+                                            const std::vector<int32_t>& uids) override;
 
     // SOCK_DIAG commands.
-    binder::Status socketDestroy(const std::vector<UidRange>& uids,
-            const std::vector<int32_t>& skipUids) override;
-
-    // Resolver commands.
-    binder::Status setResolverConfiguration(int32_t netId, const std::vector<std::string>& servers,
-            const std::vector<std::string>& domains, const std::vector<int32_t>& params,
-            const std::string& tlsName,
-            const std::vector<std::string>& tlsServers,
-            const std::vector<std::string>& tlsFingerprints) override;
-    binder::Status getResolverInfo(int32_t netId, std::vector<std::string>* servers,
-            std::vector<std::string>* domains, std::vector<int32_t>* params,
-            std::vector<int32_t>* stats) override;
+    binder::Status socketDestroy(const std::vector<UidRangeParcel>& uids,
+                                 const std::vector<int32_t>& skipUids) override;
 
     binder::Status setIPv6AddrGenMode(const std::string& ifName, int32_t mode) override;
 
@@ -84,23 +118,38 @@
 
     // Tethering-related commands.
     binder::Status tetherApplyDnsInterfaces(bool *ret) override;
-    binder::Status tetherGetStats(android::os::PersistableBundle *ret) override;
+    binder::Status tetherGetStats(
+            std::vector<android::net::TetherStatsParcel>* tetherStatsVec) override;
+    binder::Status tetherStart(const std::vector<std::string>& dhcpRanges) override;
+    binder::Status tetherStop() override;
+    binder::Status tetherIsEnabled(bool* enabled) override;
+    binder::Status tetherInterfaceAdd(const std::string& ifName) override;
+    binder::Status tetherInterfaceRemove(const std::string& ifName) override;
+    binder::Status tetherInterfaceList(std::vector<std::string>* ifList) override;
+    binder::Status tetherDnsSet(int32_t netId, const std::vector<std::string>& dnsAddrs) override;
+    binder::Status tetherDnsList(std::vector<std::string>* dnsList) override;
 
     // Interface-related commands.
     binder::Status interfaceAddAddress(const std::string &ifName,
             const std::string &addrString, int prefixLength) override;
     binder::Status interfaceDelAddress(const std::string &ifName,
             const std::string &addrString, int prefixLength) override;
+    binder::Status interfaceGetList(std::vector<std::string>* interfaceListResult) override;
+    binder::Status interfaceGetCfg(const std::string& ifName,
+                                   InterfaceConfigurationParcel* interfaceGetCfgResult) override;
+    binder::Status interfaceSetCfg(const InterfaceConfigurationParcel& cfg) override;
+    binder::Status interfaceSetIPv6PrivacyExtensions(const std::string& ifName,
+                                                     bool enable) override;
+    binder::Status interfaceClearAddrs(const std::string& ifName) override;
+    binder::Status interfaceSetEnableIPv6(const std::string& ifName, bool enable) override;
+    binder::Status interfaceSetMtu(const std::string& ifName, int32_t mtuValue) override;
 
-    binder::Status setProcSysNet(
-            int32_t family, int32_t which, const std::string &ifname, const std::string &parameter,
-            const std::string &value) override;
+    binder::Status getProcSysNet(int32_t ipversion, int32_t which, const std::string& ifname,
+                                 const std::string& parameter, std::string* value) override;
+    binder::Status setProcSysNet(int32_t ipversion, int32_t which, const std::string& ifname,
+                                 const std::string& parameter, const std::string& value) override;
 
-    // Metrics reporting level set / get (internal use only).
-    binder::Status getMetricsReportingLevel(int *reportingLevel) override;
-    binder::Status setMetricsReportingLevel(const int reportingLevel) override;
-
-    binder::Status ipSecSetEncapSocketOwner(const android::base::unique_fd& socket, int newUid);
+    binder::Status ipSecSetEncapSocketOwner(const os::ParcelFileDescriptor& socket, int newUid);
 
     binder::Status ipSecAllocateSpi(
             int32_t transformId,
@@ -110,89 +159,101 @@
             int32_t* outSpi);
 
     binder::Status ipSecAddSecurityAssociation(
-            int32_t transformId,
-            int32_t mode,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t underlyingNetId,
-            int32_t spi,
-            int32_t markValue,
-            int32_t markMask,
-            const std::string& authAlgo,
-            const std::vector<uint8_t>& authKey,
-            int32_t authTruncBits,
-            const std::string& cryptAlgo,
-            const std::vector<uint8_t>& cryptKey,
-            int32_t cryptTruncBits,
-            const std::string& aeadAlgo,
-            const std::vector<uint8_t>& aeadKey,
-            int32_t aeadIcvBits,
-            int32_t encapType,
-            int32_t encapLocalPort,
-            int32_t encapRemotePort);
+            int32_t transformId, int32_t mode, const std::string& sourceAddress,
+            const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+            int32_t markValue, int32_t markMask, const std::string& authAlgo,
+            const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+            const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey,
+            int32_t cryptTruncBits, const std::string& aeadAlgo,
+            const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+            int32_t encapLocalPort, int32_t encapRemotePort, int32_t interfaceId);
 
-    binder::Status ipSecDeleteSecurityAssociation(
-            int32_t transformId,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t spi,
-            int32_t markValue,
-            int32_t markMask);
+    binder::Status ipSecDeleteSecurityAssociation(int32_t transformId,
+                                                  const std::string& sourceAddress,
+                                                  const std::string& destinationAddress,
+                                                  int32_t spi, int32_t markValue, int32_t markMask,
+                                                  int32_t interfaceId);
 
-    binder::Status ipSecApplyTransportModeTransform(
-            const android::base::unique_fd& socket,
-            int32_t transformId,
-            int32_t direction,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t spi);
+    binder::Status ipSecApplyTransportModeTransform(const os::ParcelFileDescriptor& socket,
+                                                    int32_t transformId, int32_t direction,
+                                                    const std::string& sourceAddress,
+                                                    const std::string& destinationAddress,
+                                                    int32_t spi);
 
-    binder::Status ipSecRemoveTransportModeTransform(
-            const android::base::unique_fd& socket);
+    binder::Status ipSecRemoveTransportModeTransform(const os::ParcelFileDescriptor& socket);
 
-    binder::Status ipSecAddSecurityPolicy(
-            int32_t transformId,
-            int32_t direction,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t spi,
-            int32_t markValue,
-            int32_t markMask);
+    binder::Status ipSecAddSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                          int32_t direction, const std::string& tmplSrcAddress,
+                                          const std::string& tmplDstAddress, int32_t spi,
+                                          int32_t markValue, int32_t markMask, int32_t interfaceId);
 
-    binder::Status ipSecUpdateSecurityPolicy(
-            int32_t transformId,
-            int32_t direction,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t spi,
-            int32_t markValue,
-            int32_t markMask);
+    binder::Status ipSecUpdateSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                             int32_t direction, const std::string& tmplSrcAddress,
+                                             const std::string& tmplDstAddress, int32_t spi,
+                                             int32_t markValue, int32_t markMask,
+                                             int32_t interfaceId);
 
-    binder::Status ipSecDeleteSecurityPolicy(
-            int32_t transformId,
-            int32_t direction,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t markValue,
-            int32_t markMask);
+    binder::Status ipSecDeleteSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                             int32_t direction, int32_t markValue, int32_t markMask,
+                                             int32_t interfaceId);
 
-    binder::Status trafficCheckBpfStatsEnable(bool* ret) override;
+    binder::Status trafficSwapActiveStatsMap() override;
 
-    binder::Status addVirtualTunnelInterface(
-            const std::string& deviceName,
-            const std::string& localAddress,
-            const std::string& remoteAddress,
-            int32_t iKey,
-            int32_t oKey);
+    binder::Status ipSecAddTunnelInterface(const std::string& deviceName,
+                                           const std::string& localAddress,
+                                           const std::string& remoteAddress, int32_t iKey,
+                                           int32_t oKey, int32_t interfaceId);
 
-    binder::Status updateVirtualTunnelInterface(
-            const std::string& deviceName,
-            const std::string& localAddress,
-            const std::string& remoteAddress,
-            int32_t iKey,
-            int32_t oKey);
+    binder::Status ipSecUpdateTunnelInterface(const std::string& deviceName,
+                                              const std::string& localAddress,
+                                              const std::string& remoteAddress, int32_t iKey,
+                                              int32_t oKey, int32_t interfaceId);
 
-    binder::Status removeVirtualTunnelInterface(const std::string& deviceName);
+    binder::Status ipSecRemoveTunnelInterface(const std::string& deviceName);
+
+    // Idletimer-related commands
+    binder::Status idletimerAddInterface(const std::string& ifName, int32_t timeout,
+                                         const std::string& classLabel) override;
+    binder::Status idletimerRemoveInterface(const std::string& ifName, int32_t timeout,
+                                            const std::string& classLabel) override;
+
+    // Strict-related commands
+    binder::Status strictUidCleartextPenalty(int32_t uid, int32_t policyPenalty) override;
+
+    // Clatd-related commands
+    binder::Status clatdStart(const std::string& ifName, const std::string& nat64Prefix,
+                              std::string* v6Address) override;
+    binder::Status clatdStop(const std::string& ifName) override;
+
+    // Ipfw-related commands
+    binder::Status ipfwdEnabled(bool* status) override;
+    binder::Status ipfwdGetRequesterList(std::vector<std::string>* requesterList) override;
+    binder::Status ipfwdEnableForwarding(const std::string& requester) override;
+    binder::Status ipfwdDisableForwarding(const std::string& requester) override;
+    binder::Status ipfwdAddInterfaceForward(const std::string& fromIface,
+                                            const std::string& toIface) override;
+    binder::Status ipfwdRemoveInterfaceForward(const std::string& fromIface,
+                                               const std::string& toIface) override;
+    // Tether-forward-related commands
+    binder::Status tetherAddForward(const std::string& intIface,
+                                    const std::string& extIface) override;
+    binder::Status tetherRemoveForward(const std::string& intIface,
+                                       const std::string& extIface) override;
+
+    // tcp_mem-config command
+    binder::Status setTcpRWmemorySize(const std::string& rmemValues,
+                                      const std::string& wmemValues) override;
+
+    binder::Status registerUnsolicitedEventListener(
+            const android::sp<android::net::INetdUnsolicitedEventListener>& listener) override;
+
+    binder::Status getOemNetd(android::sp<android::IBinder>* listener) override;
+
+  private:
+    std::vector<uid_t> intsToUids(const std::vector<int32_t>& intUids);
+    Permission convertPermission(int32_t permission);
+    static FirewallRule parseRule(int32_t firewallRule);
+    static ChildChain parseChildChain(int32_t childChain);
 };
 
 }  // namespace net
diff --git a/server/NetlinkCommands.cpp b/server/NetlinkCommands.cpp
index 9f1bae9..7af33fe 100644
--- a/server/NetlinkCommands.cpp
+++ b/server/NetlinkCommands.cpp
@@ -15,14 +15,15 @@
  */
 
 #include <errno.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <sys/types.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
 
 #define LOG_TAG "Netd"
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include "NetdConstants.h"
 #include "NetlinkCommands.h"
@@ -185,7 +186,7 @@
             .rtm_family = static_cast<uint8_t>(family),
         };
         iovec iov[] = {
-            { NULL,  0 },
+            { nullptr,  0 },
             { &rule, sizeof(rule) },
         };
         uint16_t flags = NETLINK_DUMP_FLAGS;
diff --git a/server/NetlinkHandler.cpp b/server/NetlinkHandler.cpp
index 2bc9c27..7fb3437 100644
--- a/server/NetlinkHandler.cpp
+++ b/server/NetlinkHandler.cpp
@@ -22,22 +22,52 @@
 
 #define LOG_TAG "Netd"
 
-#include <cutils/log.h>
+#include <log/log.h>
 
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
 #include <netutils/ifc.h>
 #include <sysutils/NetlinkEvent.h>
+#include "Controllers.h"
 #include "NetlinkHandler.h"
 #include "NetlinkManager.h"
-#include "ResponseCode.h"
 #include "SockDiag.h"
-#include "Controllers.h"
 
-static const char *kUpdated = "updated";
-static const char *kRemoved = "removed";
+#include <charconv>
+
+#define BINDER_RETRY(exp)                                                       \
+    ({                                                                          \
+        bool res = true;                                                        \
+        for (int attempt = 0; /*nop*/; ++attempt) {                             \
+            auto _rc = (exp);                                                   \
+            if (_rc.exceptionCode() == binder::Status::EX_TRANSACTION_FAILED && \
+                attempt < RETRY_ATTEMPTS) {                                     \
+                usleep(RETRY_INTERVAL_MICRO_S);                                 \
+            } else {                                                            \
+                res = _rc.isOk();                                               \
+                break;                                                          \
+            }                                                                   \
+        }                                                                       \
+        res;                                                                    \
+    })
+
+#define LOG_EVENT_FUNC(retry, func, ...)                                                    \
+    do {                                                                                    \
+        const auto listenerMap = gCtls->eventReporter.getNetdUnsolicitedEventListenerMap(); \
+        for (auto& listener : listenerMap) {                                                \
+            auto entry = gUnsolicitedLog.newEntry().function(#func).args(__VA_ARGS__);      \
+            if (retry(listener.first->func(__VA_ARGS__))) {                                 \
+                gUnsolicitedLog.log(entry.withAutomaticDuration());                         \
+            }                                                                               \
+        }                                                                                   \
+    } while (0)
 
 namespace android {
 namespace net {
 
+constexpr int RETRY_ATTEMPTS = 2;
+constexpr int RETRY_INTERVAL_MICRO_S = 100000;
+
 NetlinkHandler::NetlinkHandler(NetlinkManager *nm, int listenerSocket,
                                int format) :
                         NetlinkListener(listenerSocket, format) {
@@ -59,7 +89,7 @@
     if (ifIndex == nullptr) {
         return 0;
     }
-    long ifaceIndex = strtol(ifIndex, NULL, 10);
+    long ifaceIndex = strtol(ifIndex, nullptr, 10);
     // strtol returns 0 on error, which is fine because 0 is not a valid ifindex.
     if (errno == ERANGE && (ifaceIndex == LONG_MAX || ifaceIndex == LONG_MIN)) {
         return 0;
@@ -117,7 +147,8 @@
             if (!ifaceIndex) {
                 ALOGE("invalid interface index: %s(%s)", iface, ifIndex);
             }
-            if (action == NetlinkEvent::Action::kAddressUpdated) {
+            const bool addrUpdated = (action == NetlinkEvent::Action::kAddressUpdated);
+            if (addrUpdated) {
                 gCtls->netCtrl.addInterfaceAddress(ifaceIndex, address);
             } else {  // action == NetlinkEvent::Action::kAddressRemoved
                 bool shouldDestroy = gCtls->netCtrl.removeInterfaceAddress(ifaceIndex, address);
@@ -135,13 +166,18 @@
             }
             // Note: if this interface was deleted, iface is "" and we don't notify.
             if (iface && iface[0] && address && flags && scope) {
-                notifyAddressChanged(action, address, iface, flags, scope);
+                if (addrUpdated) {
+                    notifyAddressUpdated(address, iface, std::stoi(flags), std::stoi(scope));
+                } else {
+                    notifyAddressRemoved(address, iface, std::stoi(flags), std::stoi(scope));
+                }
             }
         } else if (action == NetlinkEvent::Action::kRdnss) {
             const char *lifetime = evt->findParam("LIFETIME");
             const char *servers = evt->findParam("SERVERS");
             if (lifetime && servers) {
-                notifyInterfaceDnsServers(iface, lifetime, servers);
+                notifyInterfaceDnsServers(iface, strtol(lifetime, nullptr, 10),
+                                          android::base::Split(servers, ","));
             }
         } else if (action == NetlinkEvent::Action::kRouteUpdated ||
                    action == NetlinkEvent::Action::kRouteRemoved) {
@@ -149,28 +185,44 @@
             const char *gateway = evt->findParam("GATEWAY");
             const char *iface = evt->findParam("INTERFACE");
             if (route && (gateway || iface)) {
-                notifyRouteChange(action, route, gateway, iface);
+                notifyRouteChange((action == NetlinkEvent::Action::kRouteUpdated) ? true : false,
+                                  route, (gateway == nullptr) ? "" : gateway,
+                                  (iface == nullptr) ? "" : iface);
             }
         }
 
     } else if (!strcmp(subsys, "qlog") || !strcmp(subsys, "xt_quota2")) {
         const char *alertName = evt->findParam("ALERT_NAME");
         const char *iface = evt->findParam("INTERFACE");
-        notifyQuotaLimitReached(alertName, iface);
+        if (alertName && iface) {
+            notifyQuotaLimitReached(alertName, iface);
+        }
 
     } else if (!strcmp(subsys, "strict")) {
         const char *uid = evt->findParam("UID");
         const char *hex = evt->findParam("HEX");
-        notifyStrictCleartext(uid, hex);
+        if (uid && hex) {
+            notifyStrictCleartext(strtol(uid, nullptr, 10), hex);
+        }
 
     } else if (!strcmp(subsys, "xt_idletimer")) {
         const char *label = evt->findParam("INTERFACE");
         const char *state = evt->findParam("STATE");
         const char *timestamp = evt->findParam("TIME_NS");
         const char *uid = evt->findParam("UID");
-        if (state)
-            notifyInterfaceClassActivity(label, !strcmp("active", state),
-                                         timestamp, uid);
+        if (state) {
+            bool isActive = !strcmp("active", state);
+            int64_t processTimestamp = (timestamp == nullptr) ? 0 : strtoll(timestamp, nullptr, 10);
+            int intLabel;
+            // NMS only accepts interface class activity changes with integer labels, and only ever
+            // creates idletimers with integer labels.
+            if (android::base::ParseInt(label, &intLabel)) {
+                const long reportedUid =
+                        (uid != nullptr && isActive) ? strtol(uid, nullptr, 10) : -1;
+                notifyInterfaceClassActivityChanged(intLabel, isActive, processTimestamp,
+                                                    reportedUid);
+            }
+        }
 
 #if !LOG_NDEBUG
     } else if (strcmp(subsys, "platform") && strcmp(subsys, "backlight")) {
@@ -180,86 +232,54 @@
     }
 }
 
-void NetlinkHandler::notify(int code, const char *format, ...) {
-    char *msg;
-    va_list args;
-    va_start(args, format);
-    if (vasprintf(&msg, format, args) >= 0) {
-        mNm->getBroadcaster()->sendBroadcast(code, msg, false);
-        free(msg);
-    } else {
-        SLOGE("Failed to send notification: vasprintf: %s", strerror(errno));
-    }
-    va_end(args);
+void NetlinkHandler::notifyInterfaceAdded(const std::string& ifName) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceAdded, ifName);
 }
 
-void NetlinkHandler::notifyInterfaceAdded(const char *name) {
-    notify(ResponseCode::InterfaceChange, "Iface added %s", name);
+void NetlinkHandler::notifyInterfaceRemoved(const std::string& ifName) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceRemoved, ifName);
 }
 
-void NetlinkHandler::notifyInterfaceRemoved(const char *name) {
-    notify(ResponseCode::InterfaceChange, "Iface removed %s", name);
+void NetlinkHandler::notifyInterfaceChanged(const std::string& ifName, bool up) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceChanged, ifName, up);
 }
 
-void NetlinkHandler::notifyInterfaceChanged(const char *name, bool isUp) {
-    notify(ResponseCode::InterfaceChange,
-           "Iface changed %s %s", name, (isUp ? "up" : "down"));
+void NetlinkHandler::notifyInterfaceLinkChanged(const std::string& ifName, bool up) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceLinkStateChanged, ifName, up);
 }
 
-void NetlinkHandler::notifyInterfaceLinkChanged(const char *name, bool isUp) {
-    notify(ResponseCode::InterfaceChange,
-           "Iface linkstate %s %s", name, (isUp ? "up" : "down"));
+void NetlinkHandler::notifyQuotaLimitReached(const std::string& labelName,
+                                             const std::string& ifName) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onQuotaLimitReached, labelName, ifName);
 }
 
-void NetlinkHandler::notifyQuotaLimitReached(const char *name, const char *iface) {
-    notify(ResponseCode::BandwidthControl, "limit alert %s %s", name, iface);
+void NetlinkHandler::notifyInterfaceClassActivityChanged(int label, bool isActive,
+                                                         int64_t timestamp, int uid) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceClassActivityChanged, isActive, label, timestamp, uid);
 }
 
-void NetlinkHandler::notifyInterfaceClassActivity(const char *name,
-                                                  bool isActive,
-                                                  const char *timestamp,
-                                                  const char *uid) {
-    if (timestamp == NULL)
-        notify(ResponseCode::InterfaceClassActivity,
-           "IfaceClass %s %s", isActive ? "active" : "idle", name);
-    else if (uid != NULL && isActive)
-        notify(ResponseCode::InterfaceClassActivity,
-           "IfaceClass active %s %s %s", name, timestamp, uid);
-    else
-        notify(ResponseCode::InterfaceClassActivity,
-           "IfaceClass %s %s %s", isActive ? "active" : "idle", name, timestamp);
+void NetlinkHandler::notifyAddressUpdated(const std::string& addr, const std::string& ifName,
+                                          int flags, int scope) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceAddressUpdated, addr, ifName, flags, scope);
 }
 
-void NetlinkHandler::notifyAddressChanged(NetlinkEvent::Action action, const char *addr,
-                                          const char *iface, const char *flags,
-                                          const char *scope) {
-    notify(ResponseCode::InterfaceAddressChange,
-           "Address %s %s %s %s %s",
-           (action == NetlinkEvent::Action::kAddressUpdated) ? kUpdated : kRemoved,
-           addr, iface, flags, scope);
+void NetlinkHandler::notifyAddressRemoved(const std::string& addr, const std::string& ifName,
+                                          int flags, int scope) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceAddressRemoved, addr, ifName, flags, scope);
 }
 
-void NetlinkHandler::notifyInterfaceDnsServers(const char *iface,
-                                               const char *lifetime,
-                                               const char *servers) {
-    notify(ResponseCode::InterfaceDnsInfo, "DnsInfo servers %s %s %s",
-           iface, lifetime, servers);
+void NetlinkHandler::notifyInterfaceDnsServers(const std::string& ifName, int64_t lifetime,
+                                               const std::vector<std::string>& servers) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onInterfaceDnsServerInfo, ifName, lifetime, servers);
 }
 
-void NetlinkHandler::notifyRouteChange(NetlinkEvent::Action action, const char *route,
-                                       const char *gateway, const char *iface) {
-    notify(ResponseCode::RouteChange,
-           "Route %s %s%s%s%s%s",
-           (action == NetlinkEvent::Action::kRouteUpdated) ? kUpdated : kRemoved,
-           route,
-           (gateway && *gateway) ? " via " : "",
-           gateway,
-           (iface && *iface) ? " dev " : "",
-           iface);
+void NetlinkHandler::notifyRouteChange(bool updated, const std::string& route,
+                                       const std::string& gateway, const std::string& ifName) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onRouteChanged, updated, route, gateway, ifName);
 }
 
-void NetlinkHandler::notifyStrictCleartext(const char* uid, const char* hex) {
-    notify(ResponseCode::StrictCleartext, "%s %s", uid, hex);
+void NetlinkHandler::notifyStrictCleartext(uid_t uid, const std::string& hex) {
+    LOG_EVENT_FUNC(BINDER_RETRY, onStrictCleartextDetected, uid, hex);
 }
 
 }  // namespace net
diff --git a/server/NetlinkHandler.h b/server/NetlinkHandler.h
index 2f4c01c..5d0f167 100644
--- a/server/NetlinkHandler.h
+++ b/server/NetlinkHandler.h
@@ -17,40 +17,45 @@
 #ifndef _NETLINKHANDLER_H
 #define _NETLINKHANDLER_H
 
+#include <string>
+#include <vector>
+
 #include <sysutils/NetlinkEvent.h>
+// TODO: stop depending on sysutils/NetlinkListener.h
 #include <sysutils/NetlinkListener.h>
 #include "NetlinkManager.h"
 
 namespace android {
 namespace net {
 
-class NetlinkHandler: public NetlinkListener {
+class NetlinkHandler : public ::NetlinkListener {
     NetlinkManager *mNm;
 
 public:
     NetlinkHandler(NetlinkManager *nm, int listenerSocket, int format);
     virtual ~NetlinkHandler();
 
-    int start(void);
-    int stop(void);
+    int start();
+    int stop();
 
-protected:
+  protected:
     virtual void onEvent(NetlinkEvent *evt);
 
-    void notify(int code, const char *format, ...);
-    void notifyInterfaceAdded(const char *name);
-    void notifyInterfaceRemoved(const char *name);
-    void notifyInterfaceChanged(const char *name, bool isUp);
-    void notifyInterfaceLinkChanged(const char *name, bool isUp);
-    void notifyQuotaLimitReached(const char *name, const char *iface);
-    void notifyInterfaceClassActivity(const char *name, bool isActive,
-                                      const char *timestamp, const char *uid);
-    void notifyAddressChanged(NetlinkEvent::Action action, const char *addr, const char *iface,
-                              const char *flags, const char *scope);
-    void notifyInterfaceDnsServers(const char *iface, const char *lifetime,
-                                   const char *servers);
-    void notifyRouteChange(NetlinkEvent::Action action, const char *route, const char *gateway, const char *iface);
-    void notifyStrictCleartext(const char* uid, const char* hex);
+    void notifyInterfaceAdded(const std::string& ifName);
+    void notifyInterfaceRemoved(const std::string& ifName);
+    void notifyInterfaceChanged(const std::string& ifName, bool isUp);
+    void notifyInterfaceLinkChanged(const std::string& ifName, bool isUp);
+    void notifyQuotaLimitReached(const std::string& labelName, const std::string& ifName);
+    void notifyInterfaceClassActivityChanged(int label, bool isActive, int64_t timestamp, int uid);
+    void notifyAddressUpdated(const std::string& addr, const std::string& ifName, int flags,
+                              int scope);
+    void notifyAddressRemoved(const std::string& addr, const std::string& ifName, int flags,
+                              int scope);
+    void notifyInterfaceDnsServers(const std::string& ifName, int64_t lifetime,
+                                   const std::vector<std::string>& servers);
+    void notifyRouteChange(bool updated, const std::string& route, const std::string& gateway,
+                           const std::string& ifName);
+    void notifyStrictCleartext(uid_t uid, const std::string& hex);
 };
 
 }  // namespace net
diff --git a/server/NetlinkListener.cpp b/server/NetlinkListener.cpp
index 82ed6d8..a6e427d 100644
--- a/server/NetlinkListener.cpp
+++ b/server/NetlinkListener.cpp
@@ -16,17 +16,17 @@
 
 #define LOG_TAG "NetlinkListener"
 
+#include "NetlinkListener.h"
+
 #include <sstream>
 #include <vector>
 
 #include <linux/netfilter/nfnetlink.h>
 
-#include <cutils/log.h>
+#include <log/log.h>
 #include <netdutils/Misc.h>
 #include <netdutils/Syscalls.h>
 
-#include "NetlinkListener.h"
-
 namespace android {
 namespace net {
 
@@ -57,14 +57,21 @@
 
 }  // namespace
 
-NetlinkListener::NetlinkListener(UniqueFd event, UniqueFd sock)
-    : mEvent(std::move(event)), mSock(std::move(sock)), mWorker([this]() { run(); }) {
+NetlinkListener::NetlinkListener(UniqueFd event, UniqueFd sock, const std::string& name)
+    : mEvent(std::move(event)), mSock(std::move(sock)), mThreadName(name) {
     const auto rxErrorHandler = [](const nlmsghdr& nlmsg, const Slice msg) {
         std::stringstream ss;
         ss << nlmsg << " " << msg << " " << netdutils::toHex(msg, 32);
         ALOGE("unhandled netlink message: %s", ss.str().c_str());
     };
     expectOk(NetlinkListener::subscribe(kNetlinkMsgErrorType, rxErrorHandler));
+
+    mErrorHandler = [& name = mThreadName](const int fd, const int err) {
+        ALOGE("Error on NetlinkListener(%s) fd=%d: %s", name.c_str(), fd, strerror(err));
+    };
+
+    // Start the thread
+    mWorker = std::thread([this]() { run().ignoreError(); });
 }
 
 NetlinkListener::~NetlinkListener() {
@@ -85,29 +92,39 @@
 }
 
 Status NetlinkListener::subscribe(uint16_t type, const DispatchFn& fn) {
-    std::lock_guard<std::mutex> guard(mMutex);
+    std::lock_guard guard(mMutex);
     mDispatchMap[type] = fn;
     return ok;
 }
 
 Status NetlinkListener::unsubscribe(uint16_t type) {
-    std::lock_guard<std::mutex> guard(mMutex);
+    std::lock_guard guard(mMutex);
     mDispatchMap.erase(type);
     return ok;
 }
 
+void NetlinkListener::registerSkErrorHandler(const SkErrorHandler& handler) {
+    mErrorHandler = handler;
+}
+
 Status NetlinkListener::run() {
     std::vector<char> rxbuf(4096);
 
     const auto rxHandler = [this](const nlmsghdr& nlmsg, const Slice& buf) {
-        std::lock_guard<std::mutex> guard(mMutex);
+        std::lock_guard guard(mMutex);
         const auto& fn = findWithDefault(mDispatchMap, nlmsg.nlmsg_type, kDefaultDispatchFn);
         fn(nlmsg, buf);
     };
 
+    if (mThreadName.length() > 0) {
+        int ret = pthread_setname_np(pthread_self(), mThreadName.c_str());
+        if (ret) {
+            ALOGE("thread name set failed, name: %s, ret: %s", mThreadName.c_str(), strerror(ret));
+        }
+    }
     const auto& sys = sSyscalls.get();
     const std::array<Fd, 2> fds{{{mEvent}, {mSock}}};
-    const int events = POLLIN | POLLRDHUP | POLLERR | POLLHUP;
+    const int events = POLLIN;
     const double timeout = 3600;
     while (true) {
         ASSIGN_OR_RETURN(auto revents, sys.ppoll(fds, events, timeout));
@@ -115,11 +132,15 @@
         if (revents[0] & POLLIN) {
             break;
         }
-        if (revents[1] & POLLIN) {
+        if (revents[1] & (POLLIN|POLLERR)) {
             auto rx = sys.recvfrom(mSock, makeSlice(rxbuf), 0);
-            if (rx.status().code() == ENOBUFS) {
-                // Ignore ENOBUFS - the socket is still usable
-                // TODO: Users other than NFLOG may need to know about this
+            int err = rx.status().code();
+            if (err) {
+                // Ignore errors. The only error we expect to see here is ENOBUFS, and there's
+                // nothing we can do about that. The recvfrom above will already have cleared the
+                // error indication and ensured we won't get EPOLLERR again.
+                // TODO: Consider using NETLINK_NO_ENOBUFS.
+                mErrorHandler(((Fd) mSock).get(), err);
                 continue;
             }
             forEachNetlinkMessage(rx.value(), rxHandler);
diff --git a/server/NetlinkListener.h b/server/NetlinkListener.h
index 6e53c34..6f38829 100644
--- a/server/NetlinkListener.h
+++ b/server/NetlinkListener.h
@@ -22,9 +22,10 @@
 #include <mutex>
 #include <thread>
 
+#include <android-base/thread_annotations.h>
 #include <netdutils/Netlink.h>
 #include <netdutils/Slice.h>
-#include <netdutils/StatusOr.h>
+#include <netdutils/Status.h>
 #include <netdutils/UniqueFd.h>
 
 namespace android {
@@ -34,6 +35,8 @@
   public:
     using DispatchFn = std::function<void(const nlmsghdr& nlmsg, const netdutils::Slice msg)>;
 
+    using SkErrorHandler = std::function<void(const int fd, const int err)>;
+
     virtual ~NetlinkListenerInterface() = default;
 
     // Send message to the kernel using the underlying netlink socket
@@ -49,6 +52,8 @@
     // Halt delivery of future messages with nlmsghdr.nlmsg_type == type.
     // Threadsafe.
     virtual netdutils::Status unsubscribe(uint16_t type) = 0;
+
+    virtual void registerSkErrorHandler(const SkErrorHandler& handler) = 0;
 };
 
 // NetlinkListener manages a netlink socket and associated blocking
@@ -70,24 +75,28 @@
 // netfilter extensions that allow batching of events like NFLOG.
 class NetlinkListener : public NetlinkListenerInterface {
   public:
-    NetlinkListener(netdutils::UniqueFd event, netdutils::UniqueFd sock);
+    NetlinkListener(netdutils::UniqueFd event, netdutils::UniqueFd sock, const std::string& name);
 
     ~NetlinkListener() override;
 
     netdutils::Status send(const netdutils::Slice msg) override;
 
-    netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) override;
+    netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) override EXCLUDES(mMutex);
 
-    netdutils::Status unsubscribe(uint16_t type) override;
+    netdutils::Status unsubscribe(uint16_t type) override EXCLUDES(mMutex);
+
+    void registerSkErrorHandler(const SkErrorHandler& handler) override;
 
   private:
     netdutils::Status run();
 
-    netdutils::UniqueFd mEvent;
-    netdutils::UniqueFd mSock;
+    const netdutils::UniqueFd mEvent;
+    const netdutils::UniqueFd mSock;
+    const std::string mThreadName;
     std::mutex mMutex;
-    std::map<uint16_t, DispatchFn> mDispatchMap;  // guarded by mMutex
+    std::map<uint16_t, DispatchFn> mDispatchMap GUARDED_BY(mMutex);
     std::thread mWorker;
+    SkErrorHandler mErrorHandler;
 };
 
 }  // namespace net
diff --git a/server/NetlinkManager.cpp b/server/NetlinkManager.cpp
index ccf433a..d014443 100644
--- a/server/NetlinkManager.cpp
+++ b/server/NetlinkManager.cpp
@@ -19,7 +19,6 @@
 #include <string.h>
 
 #include <sys/socket.h>
-#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/un.h>
@@ -29,7 +28,7 @@
 
 #define LOG_TAG "Netd"
 
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netfilter/nfnetlink_log.h>
@@ -49,7 +48,7 @@
 const int NetlinkManager::NETFILTER_STRICT_GROUP = 2;
 const int NetlinkManager::NFLOG_WAKEUP_GROUP = 3;
 
-NetlinkManager *NetlinkManager::sInstance = NULL;
+NetlinkManager *NetlinkManager::sInstance = nullptr;
 
 NetlinkManager *NetlinkManager::Instance() {
     if (!sInstance)
@@ -58,7 +57,7 @@
 }
 
 NetlinkManager::NetlinkManager() {
-    mBroadcaster = NULL;
+    mBroadcaster = nullptr;
 }
 
 NetlinkManager::~NetlinkManager() {
@@ -78,8 +77,8 @@
     nladdr.nl_groups = groups;
 
     if ((*sock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, netlinkFamily)) < 0) {
-        ALOGE("Unable to create netlink socket: %s", strerror(errno));
-        return NULL;
+        ALOGE("Unable to create netlink socket for family %d: %s", netlinkFamily, strerror(errno));
+        return nullptr;
     }
 
     // When running in a net/user namespace, SO_RCVBUFFORCE will fail because
@@ -89,33 +88,33 @@
         setsockopt(*sock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0) {
         ALOGE("Unable to set uevent socket SO_RCVBUF option: %s", strerror(errno));
         close(*sock);
-        return NULL;
+        return nullptr;
     }
 
     if (setsockopt(*sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
         SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
         close(*sock);
-        return NULL;
+        return nullptr;
     }
 
     if (bind(*sock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
         ALOGE("Unable to bind netlink socket: %s", strerror(errno));
         close(*sock);
-        return NULL;
+        return nullptr;
     }
 
     if (configNflog) {
         if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_PF_UNBIND, AF_INET) < 0) {
             ALOGE("Failed NFULNL_CFG_CMD_PF_UNBIND: %s", strerror(errno));
-            return NULL;
+            return nullptr;
         }
         if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_PF_BIND, AF_INET) < 0) {
             ALOGE("Failed NFULNL_CFG_CMD_PF_BIND: %s", strerror(errno));
-            return NULL;
+            return nullptr;
         }
         if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_BIND, AF_UNSPEC) < 0) {
             ALOGE("Failed NFULNL_CFG_CMD_BIND: %s", strerror(errno));
-            return NULL;
+            return nullptr;
         }
     }
 
@@ -123,7 +122,7 @@
     if (handler->start()) {
         ALOGE("Unable to start NetlinkHandler: %s", strerror(errno));
         close(*sock);
-        return NULL;
+        return nullptr;
     }
 
     return handler;
@@ -131,7 +130,7 @@
 
 int NetlinkManager::start() {
     if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
-         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII, false)) == NULL) {
+         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII, false)) == nullptr) {
         return -1;
     }
 
@@ -141,18 +140,18 @@
                                      RTMGRP_IPV6_IFADDR |
                                      RTMGRP_IPV6_ROUTE |
                                      (1 << (RTNLGRP_ND_USEROPT - 1)),
-         NetlinkListener::NETLINK_FORMAT_BINARY, false)) == NULL) {
+         NetlinkListener::NETLINK_FORMAT_BINARY, false)) == nullptr) {
         return -1;
     }
 
     if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
-            NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY, false)) == NULL) {
+            NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY, false)) == nullptr) {
         ALOGW("Unable to open qlog quota socket, check if xt_quota2 can send via UeventHandler");
         // TODO: return -1 once the emulator gets a new kernel.
     }
 
     if ((mStrictHandler = setupSocket(&mStrictSock, NETLINK_NETFILTER,
-            0, NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST, true)) == NULL) {
+            0, NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST, true)) == nullptr) {
         ALOGE("Unable to open strict socket");
         // TODO: return -1 once the emulator gets a new kernel.
     }
@@ -169,7 +168,7 @@
     }
 
     delete mUeventHandler;
-    mUeventHandler = NULL;
+    mUeventHandler = nullptr;
 
     close(mUeventSock);
     mUeventSock = -1;
@@ -180,7 +179,7 @@
     }
 
     delete mRouteHandler;
-    mRouteHandler = NULL;
+    mRouteHandler = nullptr;
 
     close(mRouteSock);
     mRouteSock = -1;
@@ -192,7 +191,7 @@
         }
 
         delete mQuotaHandler;
-        mQuotaHandler = NULL;
+        mQuotaHandler = nullptr;
 
         close(mQuotaSock);
         mQuotaSock = -1;
@@ -205,7 +204,7 @@
         }
 
         delete mStrictHandler;
-        mStrictHandler = NULL;
+        mStrictHandler = nullptr;
 
         close(mStrictSock);
         mStrictSock = -1;
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 5bbfe3f..1e4894f 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -23,27 +23,30 @@
 // acquiring the lock. Private functions in this file should call xxxLocked() methods and access
 // internal state directly.
 
+#define LOG_TAG "Netd"
+
 #include "NetworkController.h"
 
-#define LOG_TAG "Netd"
-#include "log/log.h"
-
 #include <android-base/strings.h>
-
-#include "cutils/misc.h"
-#include "resolv_netid.h"
+#include <cutils/misc.h>  // FIRST_APPLICATION_UID
+#include <netd_resolv/resolv.h>
+#include <netd_resolv/resolv_stub.h>
+#include "log/log.h"
 
 #include "Controllers.h"
 #include "DummyNetwork.h"
-#include "DumpWriter.h"
 #include "Fwmark.h"
 #include "LocalNetwork.h"
 #include "PhysicalNetwork.h"
 #include "RouteController.h"
 #include "VirtualNetwork.h"
+#include "netdutils/DumpWriter.h"
+#include "netid_client.h"
 
 #define DBG 0
 
+using android::netdutils::DumpWriter;
+
 namespace android {
 namespace net {
 
@@ -55,12 +58,6 @@
 
 }  // namespace
 
-const unsigned NetworkController::MIN_OEM_ID   =  1;
-const unsigned NetworkController::MAX_OEM_ID   = 50;
-const unsigned NetworkController::DUMMY_NET_ID = 51;
-// NetIds 52..98 are reserved for future use.
-const unsigned NetworkController::LOCAL_NET_ID = 99;
-
 // All calls to methods here are made while holding a write lock on mRWLock.
 // They are mostly not called directly from this class, but from methods in PhysicalNetwork.cpp.
 // However, we're the only user of that class, so all calls to those methods come from here and are
@@ -149,12 +146,12 @@
 }
 
 unsigned NetworkController::getDefaultNetwork() const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return mDefaultNetId;
 }
 
 int NetworkController::setDefaultNetwork(unsigned netId) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
 
     if (netId == mDefaultNetId) {
         return 0;
@@ -209,9 +206,8 @@
 
     if (checkUserNetworkAccessLocked(uid, *netId) == 0) {
         // If a non-zero NetId was explicitly specified, and the user has permission for that
-        // network, use that network's DNS servers. Do not fall through to the default network even
-        // if the explicitly selected network is a split tunnel VPN: the explicitlySelected bit
-        // ensures that the VPN fallthrough rule does not match.
+        // network, use that network's DNS servers. (possibly falling through the to the default
+        // network if the VPN doesn't provide a route to them).
         fwmark.explicitlySelected = true;
 
         // If the network is a VPN and it doesn't have DNS servers, use the default network's DNS
@@ -219,16 +215,16 @@
         // http://b/29498052
         Network *network = getNetworkLocked(*netId);
         if (network && network->getType() == Network::VIRTUAL &&
-                !static_cast<VirtualNetwork *>(network)->getHasDns()) {
+            !RESOLV_STUB.resolv_has_nameservers(*netId)) {
             *netId = mDefaultNetId;
         }
     } else {
         // If the user is subject to a VPN and the VPN provides DNS servers, use those servers
         // (possibly falling through to the default network if the VPN doesn't provide a route to
-        // them). Otherwise, use the default network's DNS servers. We cannot set the explicit bit
-        // because we need to be able to fall through a split tunnel to the default network.
+        // them). Otherwise, use the default network's DNS servers.
+        // TODO: Consider if we should set the explicit bit here.
         VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
-        if (virtualNetwork && virtualNetwork->getHasDns()) {
+        if (virtualNetwork && RESOLV_STUB.resolv_has_nameservers(virtualNetwork->getNetId())) {
             *netId = virtualNetwork->getNetId();
         } else {
             // TODO: return an error instead of silently doing the DNS lookup on the wrong network.
@@ -241,14 +237,14 @@
 }
 
 uint32_t NetworkController::getNetworkForDns(unsigned* netId, uid_t uid) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return getNetworkForDnsLocked(netId, uid);
 }
 
 // Returns the NetId that a given UID would use if no network is explicitly selected. Specifically,
 // the VPN that applies to the UID if any; otherwise, the default network.
 unsigned NetworkController::getNetworkForUser(uid_t uid) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     if (VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid)) {
         return virtualNetwork->getNetId();
     }
@@ -278,13 +274,13 @@
 }
 
 unsigned NetworkController::getNetworkForConnect(uid_t uid) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return getNetworkForConnectLocked(uid);
 }
 
 void NetworkController::getNetworkContext(
         unsigned netId, uid_t uid, struct android_net_context* netcontext) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
 
     struct android_net_context nc = {
             .app_netid = netId,
@@ -341,12 +337,12 @@
 }
 
 unsigned NetworkController::getNetworkForInterface(const char* interface) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return getNetworkForInterfaceLocked(interface);
 }
 
 bool NetworkController::isVirtualNetwork(unsigned netId) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return isVirtualNetworkLocked(netId);
 }
 
@@ -382,16 +378,16 @@
 }
 
 int NetworkController::createPhysicalNetwork(unsigned netId, Permission permission) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     return createPhysicalNetworkLocked(netId, permission);
 }
 
 int NetworkController::createPhysicalOemNetwork(Permission permission, unsigned *pNetId) {
-    if (pNetId == NULL) {
+    if (pNetId == nullptr) {
         return -EINVAL;
     }
 
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     for (*pNetId = MIN_OEM_ID; *pNetId <= MAX_OEM_ID; (*pNetId)++) {
         if (!isValidNetworkLocked(*pNetId)) {
             break;
@@ -412,8 +408,8 @@
     return ret;
 }
 
-int NetworkController::createVirtualNetwork(unsigned netId, bool hasDns, bool secure) {
-    android::RWLock::AutoWLock lock(mRWLock);
+int NetworkController::createVirtualNetwork(unsigned netId, bool secure) {
+    ScopedWLock lock(mRWLock);
 
     if (!(MIN_NET_ID <= netId && netId <= MAX_NET_ID)) {
         ALOGE("invalid netId %u", netId);
@@ -428,12 +424,12 @@
     if (int ret = modifyFallthroughLocked(netId, true)) {
         return ret;
     }
-    mNetworks[netId] = new VirtualNetwork(netId, hasDns, secure);
+    mNetworks[netId] = new VirtualNetwork(netId, secure);
     return 0;
 }
 
 int NetworkController::destroyNetwork(unsigned netId) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
 
     if (netId == LOCAL_NET_ID) {
         ALOGE("cannot destroy local network");
@@ -470,7 +466,6 @@
     }
     mNetworks.erase(netId);
     delete network;
-    _resolv_delete_cache_for_net(netId);
 
     for (auto iter = mIfindexToLastNetId.begin(); iter != mIfindexToLastNetId.end();) {
         if (iter->second == netId) {
@@ -486,7 +481,7 @@
 }
 
 int NetworkController::addInterfaceToNetwork(unsigned netId, const char* interface) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
 
     if (!isValidNetworkLocked(netId)) {
         ALOGE("no such netId %u", netId);
@@ -513,7 +508,7 @@
 }
 
 int NetworkController::removeInterfaceFromNetwork(unsigned netId, const char* interface) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
 
     if (!isValidNetworkLocked(netId)) {
         ALOGE("no such netId %u", netId);
@@ -524,26 +519,26 @@
 }
 
 Permission NetworkController::getPermissionForUser(uid_t uid) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return getPermissionForUserLocked(uid);
 }
 
 void NetworkController::setPermissionForUsers(Permission permission,
                                               const std::vector<uid_t>& uids) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     for (uid_t uid : uids) {
         mUsers[uid] = permission;
     }
 }
 
 int NetworkController::checkUserNetworkAccess(uid_t uid, unsigned netId) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return checkUserNetworkAccessLocked(uid, netId);
 }
 
 int NetworkController::setPermissionForNetworks(Permission permission,
                                                 const std::vector<unsigned>& netIds) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     for (unsigned netId : netIds) {
         Network* network = getNetworkLocked(netId);
         if (!network) {
@@ -563,7 +558,7 @@
 }
 
 int NetworkController::addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     Network* network = getNetworkLocked(netId);
     if (!network) {
         ALOGE("no such netId %u", netId);
@@ -580,7 +575,7 @@
 }
 
 int NetworkController::removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     Network* network = getNetworkLocked(netId);
     if (!network) {
         ALOGE("no such netId %u", netId);
@@ -608,8 +603,7 @@
 }
 
 void NetworkController::addInterfaceAddress(unsigned ifIndex, const char* address) {
-    android::RWLock::AutoWLock lock(mRWLock);
-
+    ScopedWLock lock(mRWLock);
     if (ifIndex == 0) {
         ALOGE("Attempting to add address %s without ifindex", address);
         return;
@@ -619,7 +613,7 @@
 
 // Returns whether we should call SOCK_DESTROY on the removed address.
 bool NetworkController::removeInterfaceAddress(unsigned ifindex, const char* address) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     // First, update mAddressToIfindices map
     auto ifindicesIter = mAddressToIfindices.find(address);
     if (ifindicesIter == mAddressToIfindices.end()) {
@@ -629,7 +623,9 @@
     std::unordered_set<unsigned>& ifindices = ifindicesIter->second;
     if (ifindices.erase(ifindex) > 0) {
         if (ifindices.size() == 0) {
-            mAddressToIfindices.erase(ifindicesIter);
+            mAddressToIfindices.erase(ifindicesIter);  // Invalidates ifindices
+            // The address is no longer configured on any interface.
+            return true;
         }
     } else {
         ALOGE("No record of address %s on interface %u", address, ifindex);
@@ -660,24 +656,24 @@
 }
 
 bool NetworkController::canProtect(uid_t uid) const {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
     return canProtectLocked(uid);
 }
 
 void NetworkController::allowProtect(const std::vector<uid_t>& uids) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     mProtectableUsers.insert(uids.begin(), uids.end());
 }
 
 void NetworkController::denyProtect(const std::vector<uid_t>& uids) {
-    android::RWLock::AutoWLock lock(mRWLock);
+    ScopedWLock lock(mRWLock);
     for (uid_t uid : uids) {
         mProtectableUsers.erase(uid);
     }
 }
 
 void NetworkController::dump(DumpWriter& dw) {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
 
     dw.incIndent();
     dw.println("NetworkController");
@@ -690,14 +686,13 @@
     dw.incIndent();
     for (const auto& i : mNetworks) {
         Network* network = i.second;
-        dw.println(network->toString().c_str());
+        dw.println(network->toString());
         if (network->getType() == Network::PHYSICAL) {
             dw.incIndent();
             Permission permission = reinterpret_cast<PhysicalNetwork*>(network)->getPermission();
             dw.println("Required permission: %s", permissionToName(permission));
             dw.decIndent();
         }
-        android::net::gCtls->resolverCtrl.dump(dw, i.first);
         dw.blankline();
     }
     dw.decIndent();
@@ -730,7 +725,7 @@
 
 Network* NetworkController::getNetworkLocked(unsigned netId) const {
     auto iter = mNetworks.find(netId);
-    return iter == mNetworks.end() ? NULL : iter->second;
+    return iter == mNetworks.end() ? nullptr : iter->second;
 }
 
 VirtualNetwork* NetworkController::getVirtualNetworkForUserLocked(uid_t uid) const {
@@ -742,7 +737,7 @@
             }
         }
     }
-    return NULL;
+    return nullptr;
 }
 
 Permission NetworkController::getPermissionForUserLocked(uid_t uid) const {
@@ -782,7 +777,7 @@
 
 int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
                                    const char* nexthop, bool add, bool legacy, uid_t uid) {
-    android::RWLock::AutoRLock lock(mRWLock);
+    ScopedRLock lock(mRWLock);
 
     if (!isValidNetworkLocked(netId)) {
         ALOGE("no such netId %u", netId);
diff --git a/server/NetworkController.h b/server/NetworkController.h
index 5e7af80..4a363b3 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -17,16 +17,20 @@
 #ifndef NETD_SERVER_NETWORK_CONTROLLER_H
 #define NETD_SERVER_NETWORK_CONTROLLER_H
 
+#include <android-base/thread_annotations.h>
 #include <android/multinetwork.h>
+
+
 #include "NetdConstants.h"
 #include "Permission.h"
+#include "android/net/INetd.h"
+#include "netdutils/DumpWriter.h"
 
-#include "utils/RWLock.h"
-
+#include <sys/types.h>
 #include <list>
 #include <map>
 #include <set>
-#include <sys/types.h>
+#include <shared_mutex>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
@@ -36,6 +40,9 @@
 namespace android {
 namespace net {
 
+typedef std::shared_lock<std::shared_mutex> ScopedRLock;
+typedef std::lock_guard<std::shared_mutex> ScopedWLock;
+
 constexpr uint32_t kHandleMagic = 0xcafed00d;
 
 // Utility to convert from netId to net_handle_t. Doing this here as opposed to exporting
@@ -65,7 +72,6 @@
     return (((net_handle_t)fromNetId << 32) | kHandleMagic);
 }
 
-class DumpWriter;
 class Network;
 class UidRanges;
 class VirtualNetwork;
@@ -78,10 +84,11 @@
  */
 class NetworkController {
 public:
-    static const unsigned MIN_OEM_ID;
-    static const unsigned MAX_OEM_ID;
-    static const unsigned LOCAL_NET_ID;
-    static const unsigned DUMMY_NET_ID;
+    // NetIds 52..98 are reserved for future use.
+    static constexpr int MIN_OEM_ID = 1;
+    static constexpr int MAX_OEM_ID = 50;
+    static constexpr int LOCAL_NET_ID = INetd::LOCAL_NET_ID;
+    static constexpr int DUMMY_NET_ID = 51;
 
     NetworkController();
 
@@ -100,7 +107,7 @@
 
     int createPhysicalNetwork(unsigned netId, Permission permission) WARN_UNUSED_RESULT;
     int createPhysicalOemNetwork(Permission permission, unsigned *netId) WARN_UNUSED_RESULT;
-    int createVirtualNetwork(unsigned netId, bool hasDns, bool secure) WARN_UNUSED_RESULT;
+    int createVirtualNetwork(unsigned netId, bool secure) WARN_UNUSED_RESULT;
     int destroyNetwork(unsigned netId) WARN_UNUSED_RESULT;
 
     int addInterfaceToNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
@@ -135,9 +142,9 @@
     void allowProtect(const std::vector<uid_t>& uids);
     void denyProtect(const std::vector<uid_t>& uids);
 
-    void dump(DumpWriter& dw);
+    void dump(netdutils::DumpWriter& dw);
 
-private:
+  private:
     bool isValidNetworkLocked(unsigned netId) const;
     Network* getNetworkLocked(unsigned netId) const;
     uint32_t getNetworkForDnsLocked(unsigned* netId, uid_t uid) const;
@@ -146,7 +153,6 @@
     unsigned getNetworkForInterfaceLocked(const char* interface) const;
     bool canProtectLocked(uid_t uid) const;
     bool isVirtualNetworkLocked(unsigned netId) const;
-
     VirtualNetwork* getVirtualNetworkForUserLocked(uid_t uid) const;
     Permission getPermissionForUserLocked(uid_t uid) const;
     int checkUserNetworkAccessLocked(uid_t uid, unsigned netId) const;
@@ -162,7 +168,7 @@
 
     // mRWLock guards all accesses to mDefaultNetId, mNetworks, mUsers, mProtectableUsers,
     // mIfindexToLastNetId and mAddressToIfindices.
-    mutable android::RWLock mRWLock;
+    mutable std::shared_mutex mRWLock;
     unsigned mDefaultNetId;
     std::map<unsigned, Network*> mNetworks;  // Map keys are NetIds.
     std::map<uid_t, Permission> mUsers;
diff --git a/server/OemNetdListener.cpp b/server/OemNetdListener.cpp
new file mode 100644
index 0000000..fb07a80
--- /dev/null
+++ b/server/OemNetdListener.cpp
@@ -0,0 +1,87 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "OemNetd"
+
+#include "OemNetdListener.h"
+
+namespace com {
+namespace android {
+namespace internal {
+namespace net {
+
+::android::sp<::android::IBinder> OemNetdListener::getListener() {
+    static OemNetdListener listener;
+    return listener.getIBinder();
+}
+
+::android::sp<::android::IBinder> OemNetdListener::getIBinder() {
+    std::lock_guard lock(mMutex);
+    if (mIBinder == nullptr) {
+        mIBinder = ::android::IInterface::asBinder(this);
+    }
+    return mIBinder;
+}
+
+::android::binder::Status OemNetdListener::isAlive(bool* alive) {
+    *alive = true;
+    return ::android::binder::Status::ok();
+}
+
+::android::binder::Status OemNetdListener::registerOemUnsolicitedEventListener(
+        const ::android::sp<IOemNetdUnsolicitedEventListener>& listener) {
+    registerOemUnsolicitedEventListenerInternal(listener);
+    listener->onRegistered();
+    return ::android::binder::Status::ok();
+}
+
+void OemNetdListener::registerOemUnsolicitedEventListenerInternal(
+        const ::android::sp<IOemNetdUnsolicitedEventListener>& listener) {
+    std::lock_guard lock(mOemUnsolicitedMutex);
+
+    // Create the death listener.
+    class DeathRecipient : public ::android::IBinder::DeathRecipient {
+      public:
+        DeathRecipient(OemNetdListener* oemNetdListener,
+                       ::android::sp<IOemNetdUnsolicitedEventListener> listener)
+            : mOemNetdListener(oemNetdListener), mListener(std::move(listener)) {}
+        ~DeathRecipient() override = default;
+        void binderDied(const ::android::wp<::android::IBinder>& /* who */) override {
+            mOemNetdListener->unregisterOemUnsolicitedEventListenerInternal(mListener);
+        }
+
+      private:
+        OemNetdListener* mOemNetdListener;
+        ::android::sp<IOemNetdUnsolicitedEventListener> mListener;
+    };
+    ::android::sp<::android::IBinder::DeathRecipient> deathRecipient =
+            new DeathRecipient(this, listener);
+
+    ::android::IInterface::asBinder(listener)->linkToDeath(deathRecipient);
+
+    mOemUnsolListenerMap.insert({listener, deathRecipient});
+}
+
+void OemNetdListener::unregisterOemUnsolicitedEventListenerInternal(
+        const ::android::sp<IOemNetdUnsolicitedEventListener>& listener) {
+    std::lock_guard lock(mOemUnsolicitedMutex);
+    mOemUnsolListenerMap.erase(listener);
+}
+
+}  // namespace net
+}  // namespace internal
+}  // namespace android
+}  // namespace com
diff --git a/server/OemNetdListener.h b/server/OemNetdListener.h
new file mode 100644
index 0000000..4fb4fb7
--- /dev/null
+++ b/server/OemNetdListener.h
@@ -0,0 +1,67 @@
+/**
+ * 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 NETD_SERVER_OEM_NETD_LISTENER_H
+#define NETD_SERVER_OEM_NETD_LISTENER_H
+
+#include <map>
+#include <mutex>
+
+#include <android-base/thread_annotations.h>
+#include "com/android/internal/net/BnOemNetd.h"
+#include "com/android/internal/net/IOemNetdUnsolicitedEventListener.h"
+
+namespace com {
+namespace android {
+namespace internal {
+namespace net {
+
+class OemNetdListener : public BnOemNetd {
+  public:
+    using OemUnsolListenerMap = std::map<const ::android::sp<IOemNetdUnsolicitedEventListener>,
+                                         const ::android::sp<::android::IBinder::DeathRecipient>>;
+
+    OemNetdListener() = default;
+    ~OemNetdListener() = default;
+    static ::android::sp<::android::IBinder> getListener();
+
+    ::android::binder::Status isAlive(bool* alive) override;
+    ::android::binder::Status registerOemUnsolicitedEventListener(
+            const ::android::sp<IOemNetdUnsolicitedEventListener>& listener) override;
+
+  private:
+    std::mutex mMutex;
+    std::mutex mOemUnsolicitedMutex;
+
+    ::android::sp<::android::IBinder> mIBinder GUARDED_BY(mMutex);
+    OemUnsolListenerMap mOemUnsolListenerMap GUARDED_BY(mOemUnsolicitedMutex);
+
+    ::android::sp<::android::IBinder> getIBinder() EXCLUDES(mMutex);
+
+    void registerOemUnsolicitedEventListenerInternal(
+            const ::android::sp<IOemNetdUnsolicitedEventListener>& listener)
+            EXCLUDES(mOemUnsolicitedMutex);
+    void unregisterOemUnsolicitedEventListenerInternal(
+            const ::android::sp<IOemNetdUnsolicitedEventListener>& listener)
+            EXCLUDES(mOemUnsolicitedMutex);
+};
+
+}  // namespace net
+}  // namespace internal
+}  // namespace android
+}  // namespace com
+
+#endif  // NETD_SERVER_OEM_NETD_LISTENER_H
\ No newline at end of file
diff --git a/server/PhysicalNetwork.cpp b/server/PhysicalNetwork.cpp
index 579d0bd..7a1f305 100644
--- a/server/PhysicalNetwork.cpp
+++ b/server/PhysicalNetwork.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "Netd"
+
 #include "PhysicalNetwork.h"
 
 #include "RouteController.h"
 #include "SockDiag.h"
 
-#define LOG_TAG "Netd"
 #include "log/log.h"
 
 namespace android {
diff --git a/server/PppController.cpp b/server/PppController.cpp
index 581b9c6..80a36e9 100644
--- a/server/PppController.cpp
+++ b/server/PppController.cpp
@@ -30,7 +30,7 @@
 #include <arpa/inet.h>
 
 #define LOG_TAG "PppController"
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include "PppController.h"
 
@@ -93,7 +93,7 @@
         // TODO: Deal with pppd bailing out after 99999 seconds of being started
         // but not getting a connection
         if (execl("/system/bin/pppd", "/system/bin/pppd", "-detach", dev, "115200",
-                  lr, "ms-dns", d1, "ms-dns", d2, "lcp-max-configure", "99999", (char *) NULL)) {
+                  lr, "ms-dns", d1, "ms-dns", d2, "lcp-max-configure", "99999", (char *) nullptr)) {
             ALOGE("execl failed (%s)", strerror(errno));
         }
         free(lr);
@@ -116,7 +116,7 @@
 
     ALOGD("Stopping PPPD services on port %s", tty);
     kill(mPid, SIGTERM);
-    waitpid(mPid, NULL, 0);
+    waitpid(mPid, nullptr, 0);
     mPid = 0;
     ALOGD("PPPD services on port %s stopped", tty);
     return 0;
diff --git a/server/Process.cpp b/server/Process.cpp
new file mode 100644
index 0000000..f43e82d
--- /dev/null
+++ b/server/Process.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 "Process.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <sstream>
+
+#include "android-base/stringprintf.h"
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+#include "netdutils/Misc.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Syscalls.h"
+#include "netdutils/UniqueFd.h"
+
+namespace android {
+
+using base::StringPrintf;
+using netdutils::DumpWriter;
+using netdutils::Fd;
+using netdutils::isOk;
+using netdutils::makeCleanup;
+using netdutils::makeSlice;
+using netdutils::UniqueFd;
+
+namespace net {
+namespace process {
+namespace {
+
+const int PID_FILE_FLAGS = O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
+const mode_t PID_FILE_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;  // mode 0644, rw-r--r--
+
+// Set up during static initialization.
+const std::chrono::steady_clock::time_point sStartTime = std::chrono::steady_clock::now();
+
+std::chrono::milliseconds totalRunningTime() {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - sStartTime);
+}
+
+std::string formatDuration(std::chrono::milliseconds duration) {
+    auto hrs = std::chrono::duration_cast<std::chrono::hours>(duration);
+    duration -= hrs;
+    const auto mins = std::chrono::duration_cast<std::chrono::minutes>(duration);
+    duration -= mins;
+    const auto secs = std::chrono::duration_cast<std::chrono::seconds>(duration);
+    duration -= secs;
+
+    // No std::chrono::days until C++20.
+    const unsigned days = hrs.count() / 24;
+    hrs -= std::chrono::hours(days * 24);
+
+    return StringPrintf("%ud%luh%lum%llus%llums",
+            days, hrs.count(), mins.count(), secs.count(), duration.count());
+}
+
+}  // unnamed namespace
+
+void blockSigPipe() {
+    sigset_t mask;
+
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGPIPE);
+    if (sigprocmask(SIG_BLOCK, &mask, nullptr) != 0) {
+        ALOGW("WARNING: SIGPIPE not blocked\n");
+    }
+}
+
+void writePidFile(const std::string& pidFile) {
+    const std::string pid_buf(StringPrintf("%d\n", (int) getpid()));
+
+    Fd pidFd = open(pidFile.c_str(), PID_FILE_FLAGS, PID_FILE_MODE);
+    if (pidFd.get() == -1) {
+        ALOGE("Unable to create pid file (%s)", strerror(errno));
+        return;
+    }
+
+    const UniqueFd autoClosePidFile(pidFd);
+    auto rmFile = makeCleanup([pidFile] { removePidFile(pidFile); });
+
+    // File creation is affected by umask, so make sure the right mode bits are set.
+    if (fchmod(pidFd.get(), PID_FILE_MODE) == -1) {
+        ALOGE("failed to set mode 0%o on %s (%s)", PID_FILE_MODE, pidFile.c_str(), strerror(errno));
+        return;
+    }
+
+    auto& sys = netdutils::sSyscalls.get();
+    const auto rval = sys.write(pidFd, makeSlice(pid_buf));
+    if (!isOk(rval.status()) || rval.value() != pid_buf.size()) {
+        ALOGE("Unable to write to pid file (%s)", strerror(errno));
+        return;
+    }
+
+    rmFile.release();  // Don't delete the pid file :-)
+}
+
+void removePidFile(const std::string& pidFile) {
+    unlink(pidFile.c_str());
+}
+
+void dump(DumpWriter& dw) {
+    std::stringstream out;
+    out << "ppid:" << getppid() << " -> pid:" << getpid() << " -> tid:" << gettid() << '\n';
+    const auto runningTime = totalRunningTime();
+    out << "total running time: " << formatDuration(runningTime)
+        << " (" << totalRunningTime().count() << "ms)" << '\n';
+
+    struct rusage ru{};
+    if (getrusage(RUSAGE_SELF, &ru) == 0) {
+        out << "user: " << ru.ru_utime.tv_sec << "s" << (ru.ru_utime.tv_usec/1000) << "ms"
+            << " sys: " << ru.ru_stime.tv_sec << "s" << (ru.ru_stime.tv_usec/1000) << "ms"
+            << '\n';
+        out << "maxrss: " << ru.ru_maxrss << "kB" << '\n';
+    }
+
+    dw.println(out.str());
+}
+
+}  // namespace process
+}  // namespace net
+}  // namespace android
diff --git a/server/Process.h b/server/Process.h
new file mode 100644
index 0000000..317da44
--- /dev/null
+++ b/server/Process.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 NETD_SERVER_PROCESS_H_
+#define NETD_SERVER_PROCESS_H_
+
+#include "netdutils/DumpWriter.h"
+
+#include <string>
+
+namespace android {
+namespace net {
+namespace process {
+
+// Does what is says on the tin.
+void blockSigPipe();
+
+void writePidFile(const std::string& pidFile);
+void removePidFile(const std::string& pidFile);
+
+class ScopedPidFile {
+  public:
+    ScopedPidFile() = delete;
+    ScopedPidFile(const std::string& filename) : pidFile(filename) {
+        removePidFile(pidFile);
+        writePidFile(pidFile);
+    }
+    ScopedPidFile(const ScopedPidFile&) = delete;
+    ScopedPidFile(ScopedPidFile&&) = delete;
+
+    ~ScopedPidFile() {
+        removePidFile(pidFile);
+    }
+
+    ScopedPidFile& operator=(const ScopedPidFile&) = delete;
+    ScopedPidFile& operator=(ScopedPidFile&&) = delete;
+
+    const std::string pidFile;
+};
+
+void dump(netdutils::DumpWriter& dw);
+
+}  // namespace process
+}  // namespace net
+}  // namespace android
+
+#endif  // NETD_SERVER_PROCESS_H_
diff --git a/server/ResolvStub.cpp b/server/ResolvStub.cpp
new file mode 100644
index 0000000..d84572f
--- /dev/null
+++ b/server/ResolvStub.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOG_TAG "Netd"
+#include <log/log.h>
+
+#include <netd_resolv/resolv_stub.h>
+
+struct ResolvStub RESOLV_STUB;
+
+inline constexpr char APEX_LIB64_DIR[] = "/apex/com.android.resolv/lib64";
+inline constexpr char APEX_LIB_DIR[] = "/apex/com.android.resolv/lib";
+inline constexpr char LIBNAME[] = "libnetd_resolv.so";
+
+template <typename FunctionType>
+static void resolvStubInitFunction(void* handle, const char* symbol, FunctionType** stubPtr) {
+    void* f = dlsym(handle, symbol);
+    if (f == nullptr) {
+        ALOGE("Can't find symbol %s in %s", symbol, LIBNAME);
+        abort();
+    }
+    *stubPtr = reinterpret_cast<FunctionType*>(f);
+}
+
+int resolv_stub_init() {
+    void* netdResolvHandle;
+
+    for (const auto& dir : {APEX_LIB64_DIR, APEX_LIB_DIR}) {
+        std::string path = std::string(dir) + "/" + LIBNAME;
+        netdResolvHandle = dlopen(path.c_str(), RTLD_NOW);
+        if (netdResolvHandle != nullptr) {
+            ALOGI("Loaded resolver library from %s", path.c_str());
+            break;
+        }
+        ALOGW("dlopen(%s) failed: %s", path.c_str(), strerror(errno));
+    }
+
+    if (netdResolvHandle == nullptr) {
+        ALOGE("Fatal: couldn't open libnetd_resolv");
+        abort();
+    }
+
+#define STR(x) #x
+#define RESOLV_STUB_LOAD_SYMBOL(x) resolvStubInitFunction(netdResolvHandle, STR(x), &RESOLV_STUB.x)
+    RESOLV_STUB_LOAD_SYMBOL(resolv_has_nameservers);
+    RESOLV_STUB_LOAD_SYMBOL(resolv_init);
+#undef RESOLV_STUB_LOAD_SYMBOL
+#undef STR
+
+    return 0;
+}
diff --git a/server/ResolverController.cpp b/server/ResolverController.cpp
deleted file mode 100644
index f8e1fb3..0000000
--- a/server/ResolverController.cpp
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2011 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 "ResolverController"
-#define DBG 0
-
-#include <algorithm>
-#include <cstdlib>
-#include <map>
-#include <mutex>
-#include <set>
-#include <string>
-#include <thread>
-#include <utility>
-#include <vector>
-#include <cutils/log.h>
-#include <net/if.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
-#include <arpa/inet.h>
-// NOTE: <resolv_netid.h> is a private C library header that provides
-//       declarations for _resolv_set_nameservers_for_net and
-//       _resolv_flush_cache_for_net
-#include <resolv_netid.h>
-#include <resolv_params.h>
-#include <resolv_stats.h>
-
-#include <android-base/strings.h>
-#include <android-base/thread_annotations.h>
-#include <android/net/INetd.h>
-#include <android/net/metrics/INetdEventListener.h>
-
-#include "DumpWriter.h"
-#include "EventReporter.h"
-#include "NetdConstants.h"
-#include "ResolverController.h"
-#include "ResolverStats.h"
-#include "dns/DnsTlsTransport.h"
-#include "dns/DnsTlsServer.h"
-#include "netdutils/BackoffSequence.h"
-
-namespace android {
-
-using netdutils::BackoffSequence;
-
-namespace net {
-namespace {
-
-std::string addrToString(const sockaddr_storage* addr) {
-    char out[INET6_ADDRSTRLEN] = {0};
-    getnameinfo((const sockaddr*)addr, sizeof(sockaddr_storage), out,
-            INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);
-    return std::string(out);
-}
-
-bool parseServer(const char* server, sockaddr_storage* parsed) {
-    addrinfo hints = {
-        .ai_family = AF_UNSPEC,
-        .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV
-    };
-    addrinfo* res;
-
-    int err = getaddrinfo(server, "853", &hints, &res);
-    if (err != 0) {
-        ALOGW("Failed to parse server address (%s): %s", server, gai_strerror(err));
-        return false;
-    }
-
-    memcpy(parsed, res->ai_addr, res->ai_addrlen);
-    freeaddrinfo(res);
-    return true;
-}
-
-const char* getPrivateDnsModeString(PrivateDnsMode mode) {
-    switch (mode) {
-        case PrivateDnsMode::OFF: return "OFF";
-        case PrivateDnsMode::OPPORTUNISTIC: return "OPPORTUNISTIC";
-        case PrivateDnsMode::STRICT: return "STRICT";
-    }
-}
-
-constexpr const char* validationStatusToString(ResolverController::Validation value) {
-    switch (value) {
-        case ResolverController::Validation::in_process:     return "in_process";
-        case ResolverController::Validation::success:        return "success";
-        case ResolverController::Validation::fail:           return "fail";
-        case ResolverController::Validation::unknown_server: return "unknown_server";
-        case ResolverController::Validation::unknown_netid:  return "unknown_netid";
-        default: return "unknown_status";
-    }
-}
-
-
-class PrivateDnsConfiguration {
-  public:
-    typedef ResolverController::PrivateDnsStatus PrivateDnsStatus;
-    typedef ResolverController::Validation Validation;
-    typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
-
-    int set(int32_t netId, const std::vector<std::string>& servers, const std::string& name,
-            const std::set<std::vector<uint8_t>>& fingerprints) {
-        if (DBG) {
-            ALOGD("PrivateDnsConfiguration::set(%u, %zu, %s, %zu)",
-                    netId, servers.size(), name.c_str(), fingerprints.size());
-        }
-
-        const bool explicitlyConfigured = !name.empty() || !fingerprints.empty();
-
-        // Parse the list of servers that has been passed in
-        std::set<DnsTlsServer> tlsServers;
-        for (size_t i = 0; i < servers.size(); ++i) {
-            sockaddr_storage parsed;
-            if (!parseServer(servers[i].c_str(), &parsed)) {
-                return -EINVAL;
-            }
-            DnsTlsServer server(parsed);
-            server.name = name;
-            server.fingerprints = fingerprints;
-            tlsServers.insert(server);
-        }
-
-        std::lock_guard<std::mutex> guard(mPrivateDnsLock);
-        if (explicitlyConfigured) {
-            mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
-        } else if (!tlsServers.empty()) {
-            mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
-        } else {
-            mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
-            mPrivateDnsTransports.erase(netId);
-            return 0;
-        }
-
-        // Create the tracker if it was not present
-        auto netPair = mPrivateDnsTransports.find(netId);
-        if (netPair == mPrivateDnsTransports.end()) {
-            // No TLS tracker yet for this netId.
-            bool added;
-            std::tie(netPair, added) = mPrivateDnsTransports.emplace(netId, PrivateDnsTracker());
-            if (!added) {
-                ALOGE("Memory error while recording private DNS for netId %d", netId);
-                return -ENOMEM;
-            }
-        }
-        auto& tracker = netPair->second;
-
-        // Remove any servers from the tracker that are not in |servers| exactly.
-        for (auto it = tracker.begin(); it != tracker.end();) {
-            if (tlsServers.count(it->first) == 0) {
-                it = tracker.erase(it);
-            } else {
-                ++it;
-            }
-        }
-
-        // Add any new or changed servers to the tracker, and initiate async checks for them.
-        for (const auto& server : tlsServers) {
-            // Don't probe a server more than once.  This means that the only way to
-            // re-check a failed server is to remove it and re-add it from the netId.
-            if (tracker.count(server) == 0) {
-                validatePrivateDnsProvider(server, tracker, netId);
-            }
-        }
-        return 0;
-    }
-
-    PrivateDnsStatus getStatus(unsigned netId) {
-        PrivateDnsStatus status{PrivateDnsMode::OFF, {}};
-
-        // This mutex is on the critical path of every DNS lookup.
-        //
-        // If the overhead of mutex acquisition proves too high, we could reduce
-        // it by maintaining an atomic_int32_t counter of TLS-enabled netids, or
-        // by using an RWLock.
-        std::lock_guard<std::mutex> guard(mPrivateDnsLock);
-
-        const auto mode = mPrivateDnsModes.find(netId);
-        if (mode == mPrivateDnsModes.end()) return status;
-        status.mode = mode->second;
-
-        const auto netPair = mPrivateDnsTransports.find(netId);
-        if (netPair != mPrivateDnsTransports.end()) {
-            for (const auto& serverPair : netPair->second) {
-                if (serverPair.second == Validation::success) {
-                    status.validatedServers.push_back(serverPair.first);
-                }
-            }
-        }
-
-        return status;
-    }
-
-    void clear(unsigned netId) {
-        if (DBG) {
-            ALOGD("PrivateDnsConfiguration::clear(%u)", netId);
-        }
-        std::lock_guard<std::mutex> guard(mPrivateDnsLock);
-        mPrivateDnsModes.erase(netId);
-        mPrivateDnsTransports.erase(netId);
-    }
-
-    void dump(DumpWriter& dw, unsigned netId) {
-        std::lock_guard<std::mutex> guard(mPrivateDnsLock);
-
-        const auto& mode = mPrivateDnsModes.find(netId);
-        dw.println("Private DNS mode: %s", getPrivateDnsModeString(
-                (mode != mPrivateDnsModes.end()) ? mode->second : PrivateDnsMode::OFF));
-        const auto& netPair = mPrivateDnsTransports.find(netId);
-        if (netPair == mPrivateDnsTransports.end()) {
-            dw.println("No Private DNS servers configured");
-        } else {
-            const auto& tracker = netPair->second;
-            dw.println("Private DNS configuration (%zu entries)", tracker.size());
-            dw.incIndent();
-            for (const auto& kv : tracker) {
-                const auto& server = kv.first;
-                const auto status = kv.second;
-                dw.println("%s name{%s} status{%s}",
-                        addrToString(&(server.ss)).c_str(),
-                        server.name.c_str(),
-                        validationStatusToString(status));
-            }
-            dw.decIndent();
-        }
-    }
-
-  private:
-    void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
-            unsigned netId) REQUIRES(mPrivateDnsLock) {
-        if (DBG) {
-            ALOGD("validatePrivateDnsProvider(%s, %u)", addrToString(&(server.ss)).c_str(), netId);
-        }
-
-        tracker[server] = Validation::in_process;
-        if (DBG) {
-            ALOGD("Server %s marked as in_process.  Tracker now has size %zu",
-                    addrToString(&(server.ss)).c_str(), tracker.size());
-        }
-        // Note that capturing |server| and |netId| in this lambda create copies.
-        std::thread validate_thread([this, server, netId] {
-            // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
-            //
-            // Start with a 1 minute delay and backoff to once per hour.
-            //
-            // Assumptions:
-            //     [1] Each TLS validation is ~10KB of certs+handshake+payload.
-            //     [2] Network typically provision clients with <=4 nameservers.
-            //     [3] Average month has 30 days.
-            //
-            // Each validation pass in a given hour is ~1.2MB of data. And 24
-            // such validation passes per day is about ~30MB per month, in the
-            // worst case. Otherwise, this will cost ~600 SYNs per month
-            // (6 SYNs per ip, 4 ips per validation pass, 24 passes per day).
-            auto backoff = BackoffSequence<>::Builder()
-                    .withInitialRetransmissionTime(std::chrono::seconds(60))
-                    .withMaximumRetransmissionTime(std::chrono::seconds(3600))
-                    .build();
-
-            while (true) {
-                // ::validate() is a blocking call that performs network operations.
-                // It can take milliseconds to minutes, up to the SYN retry limit.
-                const bool success = DnsTlsTransport::validate(server, netId);
-                if (DBG) {
-                    ALOGD("validateDnsTlsServer returned %d for %s", success,
-                            addrToString(&(server.ss)).c_str());
-                }
-
-                const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
-                if (!needs_reeval) {
-                    break;
-                }
-
-                if (backoff.hasNextTimeout()) {
-                    std::this_thread::sleep_for(backoff.getNextTimeout());
-                } else {
-                    break;
-                }
-            }
-        });
-        validate_thread.detach();
-    }
-
-    bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success) {
-        constexpr bool NEEDS_REEVALUATION = true;
-        constexpr bool DONT_REEVALUATE = false;
-
-        std::lock_guard<std::mutex> guard(mPrivateDnsLock);
-
-        auto netPair = mPrivateDnsTransports.find(netId);
-        if (netPair == mPrivateDnsTransports.end()) {
-            ALOGW("netId %u was erased during private DNS validation", netId);
-            return DONT_REEVALUATE;
-        }
-
-        bool reevaluationStatus = success ? DONT_REEVALUATE : NEEDS_REEVALUATION;
-
-        auto& tracker = netPair->second;
-        auto serverPair = tracker.find(server);
-        if (serverPair == tracker.end()) {
-            ALOGW("Server %s was removed during private DNS validation",
-                    addrToString(&(server.ss)).c_str());
-            success = false;
-            reevaluationStatus = DONT_REEVALUATE;
-        } else if (!(serverPair->first == server)) {
-            // TODO: It doesn't seem correct to overwrite the tracker entry for
-            // |server| down below in this circumstance... Fix this.
-            ALOGW("Server %s was changed during private DNS validation",
-                    addrToString(&(server.ss)).c_str());
-            success = false;
-            reevaluationStatus = DONT_REEVALUATE;
-        }
-
-        // Send a validation event to NetdEventListenerService.
-        if (mNetdEventListener == nullptr) {
-            mNetdEventListener = mEventReporter.getNetdEventListener();
-        }
-        if (mNetdEventListener != nullptr) {
-            const String16 ipLiteral(addrToString(&(server.ss)).c_str());
-            const String16 hostname(server.name.empty() ? "" : server.name.c_str());
-            mNetdEventListener->onPrivateDnsValidationEvent(netId, ipLiteral, hostname, success);
-            if (DBG) {
-                ALOGD("Sending validation %s event on netId %u for %s with hostname %s",
-                        success ? "success" : "failure", netId,
-                        addrToString(&(server.ss)).c_str(), server.name.c_str());
-            }
-        } else {
-            ALOGE("Validation event not sent since NetdEventListenerService is unavailable.");
-        }
-
-        if (success) {
-            tracker[server] = Validation::success;
-            if (DBG) {
-                ALOGD("Validation succeeded for %s! Tracker now has %zu entries.",
-                        addrToString(&(server.ss)).c_str(), tracker.size());
-            }
-        } else {
-            // Validation failure is expected if a user is on a captive portal.
-            // TODO: Trigger a second validation attempt after captive portal login
-            // succeeds.
-            tracker[server] = Validation::fail;
-            if (DBG) {
-                ALOGD("Validation failed for %s!", addrToString(&(server.ss)).c_str());
-            }
-        }
-
-        return reevaluationStatus;
-    }
-
-    EventReporter mEventReporter;
-
-    std::mutex mPrivateDnsLock;
-    std::map<unsigned, PrivateDnsMode> mPrivateDnsModes GUARDED_BY(mPrivateDnsLock);
-    // Structure for tracking the validation status of servers on a specific netId.
-    // Using the AddressComparator ensures at most one entry per IP address.
-    std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
-    android::sp<android::net::metrics::INetdEventListener>
-            mNetdEventListener GUARDED_BY(mPrivateDnsLock);
-} privateDnsConfiguration;
-
-}  // namespace
-
-int ResolverController::setDnsServers(unsigned netId, const char* searchDomains,
-        const char** servers, int numservers, const __res_params* params) {
-    if (DBG) {
-        ALOGD("setDnsServers netId = %u, numservers = %d", netId, numservers);
-    }
-    return -_resolv_set_nameservers_for_net(netId, servers, numservers, searchDomains, params);
-}
-
-ResolverController::PrivateDnsStatus
-ResolverController::getPrivateDnsStatus(unsigned netId) const {
-    return privateDnsConfiguration.getStatus(netId);
-}
-
-int ResolverController::clearDnsServers(unsigned netId) {
-    _resolv_set_nameservers_for_net(netId, NULL, 0, "", NULL);
-    if (DBG) {
-        ALOGD("clearDnsServers netId = %u\n", netId);
-    }
-    privateDnsConfiguration.clear(netId);
-    return 0;
-}
-
-int ResolverController::flushDnsCache(unsigned netId) {
-    if (DBG) {
-        ALOGD("flushDnsCache netId = %u\n", netId);
-    }
-
-    _resolv_flush_cache_for_net(netId);
-
-    return 0;
-}
-
-int ResolverController::getDnsInfo(unsigned netId, std::vector<std::string>* servers,
-        std::vector<std::string>* domains, __res_params* params,
-        std::vector<android::net::ResolverStats>* stats) {
-    using android::net::ResolverStats;
-    using android::net::INetd;
-    static_assert(ResolverStats::STATS_SUCCESSES == INetd::RESOLVER_STATS_SUCCESSES &&
-            ResolverStats::STATS_ERRORS == INetd::RESOLVER_STATS_ERRORS &&
-            ResolverStats::STATS_TIMEOUTS == INetd::RESOLVER_STATS_TIMEOUTS &&
-            ResolverStats::STATS_INTERNAL_ERRORS == INetd::RESOLVER_STATS_INTERNAL_ERRORS &&
-            ResolverStats::STATS_RTT_AVG == INetd::RESOLVER_STATS_RTT_AVG &&
-            ResolverStats::STATS_LAST_SAMPLE_TIME == INetd::RESOLVER_STATS_LAST_SAMPLE_TIME &&
-            ResolverStats::STATS_USABLE == INetd::RESOLVER_STATS_USABLE &&
-            ResolverStats::STATS_COUNT == INetd::RESOLVER_STATS_COUNT,
-            "AIDL and ResolverStats.h out of sync");
-    int nscount = -1;
-    sockaddr_storage res_servers[MAXNS];
-    int dcount = -1;
-    char res_domains[MAXDNSRCH][MAXDNSRCHPATH];
-    __res_stats res_stats[MAXNS];
-    servers->clear();
-    domains->clear();
-    *params = __res_params{};
-    stats->clear();
-    int revision_id = android_net_res_stats_get_info_for_net(netId, &nscount, res_servers, &dcount,
-            res_domains, params, res_stats);
-
-    // If the netId is unknown (which can happen for valid net IDs for which no DNS servers have
-    // yet been configured), there is no revision ID. In this case there is no data to return.
-    if (revision_id < 0) {
-        return 0;
-    }
-
-    // Verify that the returned data is sane.
-    if (nscount < 0 || nscount > MAXNS || dcount < 0 || dcount > MAXDNSRCH) {
-        ALOGE("%s: nscount=%d, dcount=%d", __FUNCTION__, nscount, dcount);
-        return -ENOTRECOVERABLE;
-    }
-
-    // Determine which servers are considered usable by the resolver.
-    bool valid_servers[MAXNS];
-    std::fill_n(valid_servers, MAXNS, false);
-    android_net_res_stats_get_usable_servers(params, res_stats, nscount, valid_servers);
-
-    // Convert the server sockaddr structures to std::string.
-    stats->resize(nscount);
-    for (int i = 0 ; i < nscount ; ++i) {
-        char hbuf[NI_MAXHOST];
-        int rv = getnameinfo(reinterpret_cast<const sockaddr*>(&res_servers[i]),
-                sizeof(res_servers[i]), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST);
-        std::string server_str;
-        if (rv == 0) {
-            server_str.assign(hbuf);
-        } else {
-            ALOGE("getnameinfo() failed for server #%d: %s", i, gai_strerror(rv));
-            server_str.assign("<invalid>");
-        }
-        servers->push_back(std::move(server_str));
-        android::net::ResolverStats& cur_stats = (*stats)[i];
-        android_net_res_stats_aggregate(&res_stats[i], &cur_stats.successes, &cur_stats.errors,
-                &cur_stats.timeouts, &cur_stats.internal_errors, &cur_stats.rtt_avg,
-                &cur_stats.last_sample_time);
-        cur_stats.usable = valid_servers[i];
-    }
-
-    // Convert the stack-allocated search domain strings to std::string.
-    for (int i = 0 ; i < dcount ; ++i) {
-        domains->push_back(res_domains[i]);
-    }
-    return 0;
-}
-
-int ResolverController::setResolverConfiguration(int32_t netId,
-        const std::vector<std::string>& servers, const std::vector<std::string>& domains,
-        const std::vector<int32_t>& params, const std::string& tlsName,
-        const std::vector<std::string>& tlsServers,
-        const std::set<std::vector<uint8_t>>& tlsFingerprints) {
-    using android::net::INetd;
-    if (params.size() != INetd::RESOLVER_PARAMS_COUNT) {
-        ALOGE("%s: params.size()=%zu", __FUNCTION__, params.size());
-        return -EINVAL;
-    }
-
-    const int err = privateDnsConfiguration.set(netId, tlsServers, tlsName, tlsFingerprints);
-    if (err != 0) {
-        return err;
-    }
-
-    // Convert network-assigned server list to bionic's format.
-    auto server_count = std::min<size_t>(MAXNS, servers.size());
-    std::vector<const char*> server_ptrs;
-    for (size_t i = 0 ; i < server_count ; ++i) {
-        server_ptrs.push_back(servers[i].c_str());
-    }
-
-    std::string domains_str;
-    if (!domains.empty()) {
-        domains_str = domains[0];
-        for (size_t i = 1 ; i < domains.size() ; ++i) {
-            domains_str += " " + domains[i];
-        }
-    }
-
-    __res_params res_params;
-    res_params.sample_validity = params[INetd::RESOLVER_PARAMS_SAMPLE_VALIDITY];
-    res_params.success_threshold = params[INetd::RESOLVER_PARAMS_SUCCESS_THRESHOLD];
-    res_params.min_samples = params[INetd::RESOLVER_PARAMS_MIN_SAMPLES];
-    res_params.max_samples = params[INetd::RESOLVER_PARAMS_MAX_SAMPLES];
-
-    return setDnsServers(netId, domains_str.c_str(), server_ptrs.data(), server_ptrs.size(),
-            &res_params);
-}
-
-int ResolverController::getResolverInfo(int32_t netId, std::vector<std::string>* servers,
-        std::vector<std::string>* domains, std::vector<int32_t>* params,
-        std::vector<int32_t>* stats) {
-    using android::net::ResolverStats;
-    using android::net::INetd;
-    __res_params res_params;
-    std::vector<ResolverStats> res_stats;
-    int ret = getDnsInfo(netId, servers, domains, &res_params, &res_stats);
-    if (ret != 0) {
-        return ret;
-    }
-
-    // Serialize the information for binder.
-    ResolverStats::encodeAll(res_stats, stats);
-
-    params->resize(INetd::RESOLVER_PARAMS_COUNT);
-    (*params)[INetd::RESOLVER_PARAMS_SAMPLE_VALIDITY] = res_params.sample_validity;
-    (*params)[INetd::RESOLVER_PARAMS_SUCCESS_THRESHOLD] = res_params.success_threshold;
-    (*params)[INetd::RESOLVER_PARAMS_MIN_SAMPLES] = res_params.min_samples;
-    (*params)[INetd::RESOLVER_PARAMS_MAX_SAMPLES] = res_params.max_samples;
-    return 0;
-}
-
-void ResolverController::dump(DumpWriter& dw, unsigned netId) {
-    // No lock needed since Bionic's resolver locks all accessed data structures internally.
-    using android::net::ResolverStats;
-    std::vector<std::string> servers;
-    std::vector<std::string> domains;
-    __res_params params;
-    std::vector<ResolverStats> stats;
-    time_t now = time(nullptr);
-    int rv = getDnsInfo(netId, &servers, &domains, &params, &stats);
-    dw.incIndent();
-    if (rv != 0) {
-        dw.println("getDnsInfo() failed for netid %u", netId);
-    } else {
-        if (servers.empty()) {
-            dw.println("No DNS servers defined");
-        } else {
-            dw.println("DNS servers: # IP (total, successes, errors, timeouts, internal errors, "
-                    "RTT avg, last sample)");
-            dw.incIndent();
-            for (size_t i = 0 ; i < servers.size() ; ++i) {
-                if (i < stats.size()) {
-                    const ResolverStats& s = stats[i];
-                    int total = s.successes + s.errors + s.timeouts + s.internal_errors;
-                    if (total > 0) {
-                        int time_delta = (s.last_sample_time > 0) ? now - s.last_sample_time : -1;
-                        dw.println("%s (%d, %d, %d, %d, %d, %dms, %ds)%s", servers[i].c_str(),
-                                total, s.successes, s.errors, s.timeouts, s.internal_errors,
-                                s.rtt_avg, time_delta, s.usable ? "" : " BROKEN");
-                    } else {
-                        dw.println("%s <no data>", servers[i].c_str());
-                    }
-                } else {
-                    dw.println("%s <no stats>", servers[i].c_str());
-                }
-            }
-            dw.decIndent();
-        }
-        if (domains.empty()) {
-            dw.println("No search domains defined");
-        } else {
-            std::string domains_str = android::base::Join(domains, ", ");
-            dw.println("search domains: %s", domains_str.c_str());
-        }
-        if (params.sample_validity != 0) {
-            dw.println("DNS parameters: sample validity = %us, success threshold = %u%%, "
-                    "samples (min, max) = (%u, %u)", params.sample_validity,
-                    static_cast<unsigned>(params.success_threshold),
-                    static_cast<unsigned>(params.min_samples),
-                    static_cast<unsigned>(params.max_samples));
-        }
-
-        privateDnsConfiguration.dump(dw, netId);
-    }
-    dw.decIndent();
-}
-
-}  // namespace net
-}  // namespace android
diff --git a/server/ResolverController.h b/server/ResolverController.h
deleted file mode 100644
index 287e199..0000000
--- a/server/ResolverController.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2011 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 _RESOLVER_CONTROLLER_H_
-#define _RESOLVER_CONTROLLER_H_
-
-#include <list>
-#include <vector>
-
-struct __res_params;
-struct sockaddr_storage;
-
-namespace android {
-namespace net {
-
-struct DnsTlsServer;
-class DumpWriter;
-struct ResolverStats;
-
-enum class PrivateDnsMode {
-    OFF,
-    OPPORTUNISTIC,
-    STRICT,
-};
-
-
-class ResolverController {
-public:
-    ResolverController() {};
-
-    virtual ~ResolverController() {};
-
-    // TODO: delete this function
-    int setDnsServers(unsigned netId, const char* searchDomains, const char** servers,
-            int numservers, const __res_params* params);
-
-    // Validation status of a DNS over TLS server (on a specific netId).
-    enum class Validation : uint8_t { in_process, success, fail, unknown_server, unknown_netid };
-
-    struct PrivateDnsStatus {
-        PrivateDnsMode mode;
-        std::list<DnsTlsServer> validatedServers;
-    };
-
-    // Retrieve the Private DNS status for the given |netid|.
-    //
-    // If the requested |netid| is not known, the PrivateDnsStatus's mode has a
-    // default value of PrivateDnsMode::OFF, and validatedServers is empty.
-    PrivateDnsStatus getPrivateDnsStatus(unsigned netid) const;
-
-    int clearDnsServers(unsigned netid);
-
-    int flushDnsCache(unsigned netid);
-
-    int getDnsInfo(unsigned netId, std::vector<std::string>* servers,
-            std::vector<std::string>* domains, __res_params* params,
-            std::vector<android::net::ResolverStats>* stats);
-
-    // Binder specific functions, which convert between the binder int/string arrays and the
-    // actual data structures, and call setDnsServer() / getDnsInfo() for the actual processing.
-    int setResolverConfiguration(int32_t netId, const std::vector<std::string>& servers,
-            const std::vector<std::string>& domains, const std::vector<int32_t>& params,
-            const std::string& tlsName, const std::vector<std::string>& tlsServers,
-            const std::set<std::vector<uint8_t>>& tlsFingerprints);
-
-    int getResolverInfo(int32_t netId, std::vector<std::string>* servers,
-            std::vector<std::string>* domains, std::vector<int32_t>* params,
-            std::vector<int32_t>* stats);
-
-    void dump(DumpWriter& dw, unsigned netId);
-
-};
-
-}  // namespace net
-}  // namespace android
-
-#endif /* _RESOLVER_CONTROLLER_H_ */
diff --git a/server/ResponseCode.h b/server/ResponseCode.h
deleted file mode 100644
index 92230e1..0000000
--- a/server/ResponseCode.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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 _RESPONSECODE_H
-#define _RESPONSECODE_H
-
-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.
-    static const int ActionInitiated           = 100;
-    static const int InterfaceListResult       = 110;
-    static const int TetherInterfaceListResult = 111;
-    static const int TetherDnsFwdTgtListResult = 112;
-    static const int TtyListResult             = 113;
-    static const int TetheringStatsListResult  = 114;
-    static const int TetherDnsFwdNetIdResult   = 115;
-
-    // 200 series - Requested action has been successfully completed
-    static const int CommandOkay               = 200;
-    static const int TetherStatusResult        = 210;
-    static const int IpFwdStatusResult         = 211;
-    static const int InterfaceGetCfgResult     = 213;
-    // Formerly: int SoftapStatusResult        = 214;
-    static const int UsbRNDISStatusResult      = 215;
-    static const int InterfaceRxCounterResult  = 216;
-    static const int InterfaceTxCounterResult  = 217;
-    static const int InterfaceRxThrottleResult = 218;
-    static const int InterfaceTxThrottleResult = 219;
-    static const int QuotaCounterResult        = 220;
-    static const int TetheringStatsResult      = 221;
-    static const int DnsProxyQueryResult       = 222;
-    static const int ClatdStatusResult         = 223;
-
-    // 400 series - The command was accepted but the requested action
-    // did not take place.
-    static const int OperationFailed           = 400;
-    static const int DnsProxyOperationFailed   = 401;
-    static const int ServiceStartFailed        = 402;
-    static const int ServiceStopFailed         = 403;
-
-    // 500 series - The command was not accepted and the requested
-    // action did not take place.
-    static const int CommandSyntaxError = 500;
-    static const int CommandParameterError = 501;
-
-    // 600 series - Unsolicited broadcasts
-    static const int InterfaceChange                = 600;
-    static const int BandwidthControl               = 601;
-    static const int ServiceDiscoveryFailed         = 602;
-    static const int ServiceDiscoveryServiceAdded   = 603;
-    static const int ServiceDiscoveryServiceRemoved = 604;
-    static const int ServiceRegistrationFailed      = 605;
-    static const int ServiceRegistrationSucceeded   = 606;
-    static const int ServiceResolveFailed           = 607;
-    static const int ServiceResolveSuccess          = 608;
-    static const int ServiceSetHostnameFailed       = 609;
-    static const int ServiceSetHostnameSuccess      = 610;
-    static const int ServiceGetAddrInfoFailed       = 611;
-    static const int ServiceGetAddrInfoSuccess      = 612;
-    static const int InterfaceClassActivity         = 613;
-    static const int InterfaceAddressChange         = 614;
-    static const int InterfaceDnsInfo               = 615;
-    static const int RouteChange                    = 616;
-    static const int StrictCleartext                = 617;
-};
-#endif
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index c78854d..6722372 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -27,23 +27,24 @@
 
 #include <map>
 
+#define LOG_TAG "Netd"
+
 #include "DummyNetwork.h"
 #include "Fwmark.h"
 #include "NetdConstants.h"
 #include "NetlinkCommands.h"
 #include "UidRanges.h"
 
-#include "android-base/file.h"
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
-#define LOG_TAG "Netd"
 #include "log/log.h"
 #include "logwrap/logwrap.h"
+#include "netid_client.h"
 #include "netutils/ifc.h"
-#include "resolv_netid.h"
 
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
-using android::net::UidRange;
+using android::net::UidRangeParcel;
 
 namespace android {
 namespace net {
@@ -92,8 +93,8 @@
 const uint32_t FWMARK_NONE = 0;
 const uint32_t MASK_NONE = 0;
 const char* const IIF_LOOPBACK = "lo";
-const char* const IIF_NONE = NULL;
-const char* const OIF_NONE = NULL;
+const char* const IIF_NONE = nullptr;
+const char* const OIF_NONE = nullptr;
 const bool ACTION_ADD = true;
 const bool ACTION_DEL = false;
 const bool MODIFY_NON_UID_BASED_RULES = true;
@@ -155,7 +156,7 @@
 }
 
 uint32_t RouteController::getIfIndex(const char* interface) {
-    android::RWLock::AutoRLock lock(sInterfaceToTableLock);
+    std::lock_guard lock(sInterfaceToTableLock);
 
     auto iter = sInterfaceToTable.find(interface);
     if (iter == sInterfaceToTable.end()) {
@@ -167,7 +168,7 @@
 }
 
 uint32_t RouteController::getRouteTableForInterface(const char* interface) {
-    android::RWLock::AutoRLock lock(sInterfaceToTableLock);
+    std::lock_guard lock(sInterfaceToTableLock);
     return getRouteTableForInterfaceLocked(interface);
 }
 
@@ -191,7 +192,7 @@
     addTableName(ROUTE_TABLE_LEGACY_NETWORK, ROUTE_TABLE_NAME_LEGACY_NETWORK, &contents);
     addTableName(ROUTE_TABLE_LEGACY_SYSTEM,  ROUTE_TABLE_NAME_LEGACY_SYSTEM,  &contents);
 
-    android::RWLock::AutoRLock lock(sInterfaceToTableLock);
+    std::lock_guard lock(sInterfaceToTableLock);
     for (const auto& entry : sInterfaceToTable) {
         addTableName(entry.second, entry.first, &contents);
     }
@@ -279,7 +280,7 @@
     struct fib_rule_uid_range uidRange = { uidStart, uidEnd };
 
     iovec iov[] = {
-        { NULL,              0 },
+        { nullptr,              0 },
         { &rule,             sizeof(rule) },
         { &FRATTR_PRIORITY,  sizeof(FRATTR_PRIORITY) },
         { &priority,         sizeof(priority) },
@@ -365,11 +366,11 @@
         // the table number. But it's an error to specify an interface ("dev ...") or a nexthop for
         // unreachable routes, so nuke them. (IPv6 allows them to be specified; IPv4 doesn't.)
         interface = OIF_NONE;
-        nexthop = NULL;
+        nexthop = nullptr;
     } else if (nexthop && !strcmp(nexthop, "throw")) {
         type = RTN_THROW;
         interface = OIF_NONE;
-        nexthop = NULL;
+        nexthop = nullptr;
     } else {
         // If an interface was specified, find the ifindex.
         if (interface != OIF_NONE) {
@@ -402,7 +403,7 @@
     rtattr rtaGateway = { U16_RTA_LENGTH(rawLength), RTA_GATEWAY };
 
     iovec iov[] = {
-        { NULL,          0 },
+        { nullptr,          0 },
         { &route,        sizeof(route) },
         { &RTATTR_TABLE, sizeof(RTATTR_TABLE) },
         { &table,        sizeof(table) },
@@ -417,6 +418,13 @@
     };
 
     uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_ROUTE_CREATE_FLAGS : NETLINK_REQUEST_FLAGS;
+
+    // Allow creating multiple link-local routes in the same table, so we can make IPv6
+    // work on all interfaces in the local_network table.
+    if (family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(reinterpret_cast<in6_addr*>(rawAddress))) {
+        flags &= ~NLM_F_EXCL;
+    }
+
     int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr);
     if (ret) {
         ALOGE("Error %s route %s -> %s %s to table %u: %s",
@@ -594,8 +602,7 @@
 // A rule to enable split tunnel VPNs.
 //
 // If a packet with a VPN's netId doesn't find a route in the VPN's routing table, it's allowed to
-// go over the default network, provided it wasn't explicitly restricted to the VPN and has the
-// permissions required by the default network.
+// go over the default network, provided it has the permissions required by the default network.
 WARN_UNUSED_RESULT int RouteController::modifyVpnFallthroughRule(uint16_t action, unsigned vpnNetId,
                                                                  const char* physicalInterface,
                                                                  Permission permission) {
@@ -610,9 +617,6 @@
     fwmark.netId = vpnNetId;
     mask.netId = FWMARK_NET_ID_MASK;
 
-    fwmark.explicitlySelected = false;
-    mask.explicitlySelected = true;
-
     fwmark.permission = permission;
     mask.permission = permission;
 
@@ -686,11 +690,11 @@
         return ret;
     }
 
-    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "0.0.0.0/0", NULL))) {
+    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "0.0.0.0/0", nullptr))) {
         return ret;
     }
 
-    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "::/0", NULL))) {
+    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "::/0", nullptr))) {
         return ret;
     }
 
@@ -766,11 +770,10 @@
     fwmark.protectedFromVpn = false;
     mask.protectedFromVpn = true;
 
-    for (const UidRange& range : uidRanges.getRanges()) {
-        if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                                   RULE_PRIORITY_PROHIBIT_NON_VPN, FR_ACT_PROHIBIT, RT_TABLE_UNSPEC,
-                                   fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE,
-                                   range.getStart(), range.getStop())) {
+    for (const UidRangeParcel& range : uidRanges.getRanges()) {
+        if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_PROHIBIT_NON_VPN,
+                                   FR_ACT_PROHIBIT, RT_TABLE_UNSPEC, fwmark.intValue, mask.intValue,
+                                   IIF_LOOPBACK, OIF_NONE, range.start, range.stop)) {
             return ret;
         }
     }
@@ -787,17 +790,16 @@
         return -ESRCH;
     }
 
-    for (const UidRange& range : uidRanges.getRanges()) {
-        if (int ret = modifyVpnUidRangeRule(table, range.getStart(), range.getStop(), secure, add))
-                {
+    for (const UidRangeParcel& range : uidRanges.getRanges()) {
+        if (int ret = modifyVpnUidRangeRule(table, range.start, range.stop, secure, add)) {
             return ret;
         }
-        if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.getStart(),
-                                                range.getStop(), add)) {
+        if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.start,
+                                                range.stop, add)) {
             return ret;
         }
-        if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE,
-                                                 range.getStart(), range.getStop(), add)) {
+        if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, range.start,
+                                                 range.stop, add)) {
             return ret;
         }
     }
@@ -927,7 +929,7 @@
 
 // Returns 0 on success or negative errno on failure.
 WARN_UNUSED_RESULT int RouteController::flushRoutes(const char* interface) {
-    android::RWLock::AutoWLock lock(sInterfaceToTableLock);
+    std::lock_guard lock(sInterfaceToTableLock);
 
     uint32_t table = getRouteTableForInterfaceLocked(interface);
     if (table == RT_TABLE_UNSPEC) {
@@ -1089,9 +1091,9 @@
 }
 
 // Protects sInterfaceToTable.
-android::RWLock RouteController::sInterfaceToTableLock;
-
+std::mutex RouteController::sInterfaceToTableLock;
 std::map<std::string, uint32_t> RouteController::sInterfaceToTable;
 
+
 }  // namespace net
 }  // namespace android
diff --git a/server/RouteController.h b/server/RouteController.h
index 6e10cce..d40288b 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -20,9 +20,12 @@
 #include "NetdConstants.h"
 #include "Permission.h"
 
-#include <map>
-#include <sys/types.h>
+#include <android-base/thread_annotations.h>
+
 #include <linux/netlink.h>
+#include <sys/types.h>
+#include <map>
+#include <mutex>
 
 namespace android {
 namespace net {
@@ -52,7 +55,7 @@
     // correspond to different interface indices over time. This way, even if the interface
     // index has changed, we can still free any map entries indexed by the ifindex that was
     // used to add them.
-    static uint32_t getIfIndex(const char* interface);
+    static uint32_t getIfIndex(const char* interface) EXCLUDES(sInterfaceToTableLock);
 
     static int addInterfaceToLocalNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
     static int removeInterfaceFromLocalNetwork(unsigned netId,
@@ -111,15 +114,15 @@
 private:
     friend class RouteControllerTest;
 
-    // Protects access to interfaceToTable.
-    static android::RWLock sInterfaceToTableLock;
-    static std::map<std::string, uint32_t> sInterfaceToTable;
+    static std::mutex sInterfaceToTableLock;
+    static std::map<std::string, uint32_t> sInterfaceToTable GUARDED_BY(sInterfaceToTableLock);
 
     static int configureDummyNetwork();
-    static int flushRoutes(const char* interface);
+    static int flushRoutes(const char* interface) EXCLUDES(sInterfaceToTableLock);
     static int flushRoutes(uint32_t table);
-    static uint32_t getRouteTableForInterfaceLocked(const char *interface);
-    static uint32_t getRouteTableForInterface(const char *interface);
+    static uint32_t getRouteTableForInterfaceLocked(const char *interface)
+            REQUIRES(sInterfaceToTableLock);
+    static uint32_t getRouteTableForInterface(const char *interface) EXCLUDES(sInterfaceToTableLock);
     static int modifyDefaultNetwork(uint16_t action, const char* interface, Permission permission);
     static int modifyPhysicalNetwork(unsigned netId, const char* interface, Permission permission,
                                      bool add);
@@ -132,7 +135,7 @@
     static int modifyVirtualNetwork(unsigned netId, const char* interface,
                                     const UidRanges& uidRanges, bool secure, bool add,
                                     bool modifyNonUidBasedRules);
-    static void updateTableNamesFile();
+    static void updateTableNamesFile() EXCLUDES(sInterfaceToTableLock);
 };
 
 // Public because they are called by by RouteControllerTest.cpp.
diff --git a/server/RouteControllerTest.cpp b/server/RouteControllerTest.cpp
index b6e648a..20b3618 100644
--- a/server/RouteControllerTest.cpp
+++ b/server/RouteControllerTest.cpp
@@ -79,18 +79,18 @@
     static_assert(table2 < RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX,
                   "Test table2 number too large");
 
-    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table1, "lo", "192.0.2.2/32", NULL));
-    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table1, "lo", "192.0.2.3/32", NULL));
-    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table2, "lo", "192.0.2.4/32", NULL));
+    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table1, "lo", "192.0.2.2/32", nullptr));
+    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table1, "lo", "192.0.2.3/32", nullptr));
+    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table2, "lo", "192.0.2.4/32", nullptr));
 
     EXPECT_EQ(0, flushRoutes(table1));
 
     EXPECT_EQ(-ESRCH,
-              modifyIpRoute(RTM_DELROUTE, table1, "lo", "192.0.2.2/32", NULL));
+              modifyIpRoute(RTM_DELROUTE, table1, "lo", "192.0.2.2/32", nullptr));
     EXPECT_EQ(-ESRCH,
-              modifyIpRoute(RTM_DELROUTE, table1, "lo", "192.0.2.3/32", NULL));
+              modifyIpRoute(RTM_DELROUTE, table1, "lo", "192.0.2.3/32", nullptr));
     EXPECT_EQ(0,
-              modifyIpRoute(RTM_DELROUTE, table2, "lo", "192.0.2.4/32", NULL));
+              modifyIpRoute(RTM_DELROUTE, table2, "lo", "192.0.2.4/32", nullptr));
 }
 
 TEST_F(RouteControllerTest, TestModifyIncomingPacketMark) {
diff --git a/server/SockDiag.cpp b/server/SockDiag.cpp
index 7d22b2c..33c523a 100644
--- a/server/SockDiag.cpp
+++ b/server/SockDiag.cpp
@@ -29,14 +29,12 @@
 #define LOG_TAG "Netd"
 
 #include <android-base/strings.h>
-#include <cutils/log.h>
+#include <log/log.h>
+#include <netdutils/Stopwatch.h>
 
 #include "NetdConstants.h"
 #include "Permission.h"
 #include "SockDiag.h"
-#include "Stopwatch.h"
-
-#include <chrono>
 
 #ifndef SOCK_DESTROY
 #define SOCK_DESTROY 21
@@ -45,8 +43,10 @@
 #define INET_DIAG_BC_MARK_COND 10
 
 namespace android {
-namespace net {
 
+using netdutils::Stopwatch;
+
+namespace net {
 namespace {
 
 int checkError(int fd) {
@@ -329,7 +329,7 @@
     return mSocketsDestroyed;
 }
 
-int SockDiag::destroyLiveSockets(DestroyFilter destroyFilter, const char *what,
+int SockDiag::destroyLiveSockets(const DestroyFilter& destroyFilter, const char *what,
                                  iovec *iov, int iovcnt) {
     const int proto = IPPROTO_TCP;
     const uint32_t states = (1 << TCP_ESTABLISHED) | (1 << TCP_SYN_SENT) | (1 << TCP_SYN_RECV);
@@ -423,16 +423,10 @@
         return ret;
     }
 
-    std::vector<uid_t> skipUidStrings;
-    for (uid_t uid : skipUids) {
-        skipUidStrings.push_back(uid);
-    }
-    std::sort(skipUidStrings.begin(), skipUidStrings.end());
-
     if (mSocketsDestroyed > 0) {
         ALOGI("Destroyed %d sockets for %s skip={%s} in %.1f ms",
               mSocketsDestroyed, uidRanges.toString().c_str(),
-              android::base::Join(skipUidStrings, " ").c_str(), s.timeTaken());
+              android::base::Join(skipUids, " ").c_str(), s.timeTaken());
     }
 
     return 0;
diff --git a/server/SockDiag.h b/server/SockDiag.h
index a44c144..af96409 100644
--- a/server/SockDiag.h
+++ b/server/SockDiag.h
@@ -93,7 +93,7 @@
     int sendDumpRequest(uint8_t proto, uint8_t family, uint8_t extensions, uint32_t states,
                         iovec *iov, int iovcnt);
     int destroySockets(uint8_t proto, int family, const char *addrstr);
-    int destroyLiveSockets(DestroyFilter destroy, const char *what, iovec *iov, int iovcnt);
+    int destroyLiveSockets(const DestroyFilter& destroy, const char *what, iovec *iov, int iovcnt);
     bool hasSocks() { return mSock != -1 && mWriteSock != -1; }
     void closeSocks() { close(mSock); close(mWriteSock); mSock = mWriteSock = -1; }
     static bool isLoopbackSocket(const inet_diag_msg *msg);
diff --git a/server/SockDiagTest.cpp b/server/SockDiagTest.cpp
index a7b911d..312f2c8 100644
--- a/server/SockDiagTest.cpp
+++ b/server/SockDiagTest.cpp
@@ -73,11 +73,11 @@
 }
 
 TEST_F(SockDiagTest, TestDump) {
-    int v4socket = socket(AF_INET, SOCK_STREAM, 0);
+    int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_NE(-1, v4socket) << "Failed to open IPv4 socket: " << strerror(errno);
-    int v6socket = socket(AF_INET6, SOCK_STREAM, 0);
+    int v6socket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_NE(-1, v6socket) << "Failed to open IPv6 socket: " << strerror(errno);
-    int listensocket = socket(AF_INET6, SOCK_STREAM, 0);
+    int listensocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_NE(-1, listensocket) << "Failed to open listen socket: " << strerror(errno);
 
     uint16_t port = bindAndListen(listensocket);
@@ -93,8 +93,10 @@
 
     sockaddr_in6 client46, client6;
     socklen_t clientlen = std::max(sizeof(client46), sizeof(client6));
-    int accepted4 = accept(listensocket, (sockaddr *) &client46, &clientlen);
-    int accepted6 = accept(listensocket, (sockaddr *) &client6, &clientlen);
+    int accepted4 = accept4(
+            listensocket, (sockaddr *) &client46, &clientlen, SOCK_CLOEXEC);
+    int accepted6 = accept4(
+            listensocket, (sockaddr *) &client6, &clientlen, SOCK_CLOEXEC);
     ASSERT_NE(-1, accepted4);
     ASSERT_NE(-1, accepted6);
 
@@ -213,8 +215,8 @@
 
 inet_diag_msg makeDiagMessage(const char* srcstr, const char* dststr) {
     addrinfo hints = { .ai_flags = AI_NUMERICHOST }, *src, *dst;
-    EXPECT_EQ(0, getaddrinfo(srcstr, NULL, &hints, &src));
-    EXPECT_EQ(0, getaddrinfo(dststr, NULL, &hints, &dst));
+    EXPECT_EQ(0, getaddrinfo(srcstr, nullptr, &hints, &src));
+    EXPECT_EQ(0, getaddrinfo(dststr, nullptr, &hints, &dst));
     EXPECT_EQ(src->ai_addr->sa_family, dst->ai_addr->sa_family);
     inet_diag_msg msg = makeDiagMessage(src->ai_addr->sa_family, src->ai_addr, dst->ai_addr);
     freeaddrinfo(src);
@@ -457,7 +459,7 @@
     fprintf(stderr, "Benchmarking closing %d sockets based on %s\n",
             numSockets, testTypeName(mode));
 
-    int listensocket = socket(AF_INET6, SOCK_STREAM, 0);
+    int listensocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_NE(-1, listensocket) << "Failed to open listen socket";
 
     uint16_t port = bindAndListen(listensocket);
@@ -473,12 +475,13 @@
 
     auto start = std::chrono::steady_clock::now();
     for (int i = 0; i < numSockets; i++) {
-        int s = socket(AF_INET6, SOCK_STREAM, 0);
+        int s = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
         clientlen = sizeof(client);
         ASSERT_EQ(0, connect(s, (sockaddr *) &server, sizeof(server)))
             << "Connecting socket " << i << " failed " << strerror(errno);
         ASSERT_EQ(0, modifySocketForTest(s, i));
-        serversockets[i] = accept(listensocket, (sockaddr *) &client, &clientlen);
+        serversockets[i] = accept4(
+                listensocket, (sockaddr *) &client, &clientlen, SOCK_CLOEXEC);
         ASSERT_NE(-1, serversockets[i])
             << "Accepting socket " << i << " failed " << strerror(errno);
         clientports[i] = client.sin6_port;
diff --git a/server/StrictController.cpp b/server/StrictController.cpp
index 04c1bfe..855bedb 100644
--- a/server/StrictController.cpp
+++ b/server/StrictController.cpp
@@ -24,7 +24,7 @@
 
 #define LOG_TAG "StrictController"
 #define LOG_NDEBUG 0
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -47,7 +47,7 @@
 StrictController::StrictController(void) {
 }
 
-int StrictController::enableStrict(void) {
+int StrictController::setupIptablesHooks(void) {
     char connmarkFlagAccept[16];
     char connmarkFlagReject[16];
     char connmarkFlagTestAccept[32];
@@ -61,7 +61,7 @@
             ConnmarkFlags::STRICT_RESOLVED_REJECT,
             ConnmarkFlags::STRICT_RESOLVED_REJECT);
 
-    disableStrict();
+    resetChains();
 
     int res = 0;
     std::vector<std::string> v4, v6;
@@ -136,10 +136,10 @@
 #undef CMD_V6
 #undef CMD_V4V6
 
-    return res;
+    return res ? -EREMOTEIO : 0;
 }
 
-int StrictController::disableStrict(void) {
+int StrictController::resetChains(void) {
     // Flush any existing rules
 #define CLEAR_CHAIN(x) StringPrintf(":%s -", (x))
     std::vector<std::string> commandList = {
@@ -152,7 +152,7 @@
         "COMMIT\n"
     };
     const std::string commands = Join(commandList, '\n');
-    return execIptablesRestore(V4V6, commands);
+    return (execIptablesRestore(V4V6, commands) == 0) ? 0 : -EREMOTEIO;
 #undef CLEAR_CHAIN
 }
 
@@ -194,5 +194,5 @@
     }
     commands.push_back("COMMIT\n");
 
-    return execIptablesRestore(V4V6, Join(commands, "\n"));
+    return (execIptablesRestore(V4V6, Join(commands, "\n")) == 0) ? 0 : -EREMOTEIO;
 }
diff --git a/server/StrictController.h b/server/StrictController.h
index aee7c7e..1c2a891 100644
--- a/server/StrictController.h
+++ b/server/StrictController.h
@@ -31,8 +31,8 @@
 public:
     StrictController();
 
-    int enableStrict(void);
-    int disableStrict(void);
+    int setupIptablesHooks(void);
+    int resetChains(void);
 
     int setUidCleartextPenalty(uid_t, StrictPenalty);
 
@@ -41,8 +41,9 @@
     static const char* LOCAL_CLEAR_CAUGHT;
     static const char* LOCAL_PENALTY_LOG;
     static const char* LOCAL_PENALTY_REJECT;
+    std::mutex lock;
 
-protected:
+  protected:
     // For testing.
     friend class StrictControllerTest;
     static int (*execIptablesRestore)(IptablesTarget target, const std::string& commands);
diff --git a/server/StrictControllerTest.cpp b/server/StrictControllerTest.cpp
index 9770352..3025d8c 100644
--- a/server/StrictControllerTest.cpp
+++ b/server/StrictControllerTest.cpp
@@ -34,8 +34,8 @@
     StrictController mStrictCtrl;
 };
 
-TEST_F(StrictControllerTest, TestEnableStrict) {
-    mStrictCtrl.enableStrict();
+TEST_F(StrictControllerTest, TestSetupIptablesHooks) {
+    mStrictCtrl.setupIptablesHooks();
 
     std::vector<std::string> common = {
         "*filter",
@@ -108,8 +108,8 @@
     expectIptablesRestoreCommands(expected);
 }
 
-TEST_F(StrictControllerTest, TestDisableStrict) {
-    mStrictCtrl.disableStrict();
+TEST_F(StrictControllerTest, TestResetChains) {
+    mStrictCtrl.resetChains();
 
     const std::string expected =
         "*filter\n"
diff --git a/server/TcpSocketMonitor.cpp b/server/TcpSocketMonitor.cpp
index 57f1fda..f4b505d 100644
--- a/server/TcpSocketMonitor.cpp
+++ b/server/TcpSocketMonitor.cpp
@@ -16,7 +16,8 @@
 
 #define LOG_TAG "TcpSocketMonitor"
 
-#include <iomanip>
+#include <chrono>
+#include <cinttypes>
 #include <thread>
 #include <vector>
 
@@ -25,9 +26,12 @@
 #include <linux/tcp.h>
 
 #include "Controllers.h"
-#include "DumpWriter.h"
 #include "SockDiag.h"
 #include "TcpSocketMonitor.h"
+#include "netdutils/DumpWriter.h"
+
+using android::netdutils::DumpWriter;
+using android::netdutils::ScopedIndent;
 
 namespace android {
 namespace net {
@@ -97,10 +101,10 @@
 const milliseconds TcpSocketMonitor::kDefaultPollingInterval = milliseconds(30000);
 
 void TcpSocketMonitor::dump(DumpWriter& dw) {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
 
     dw.println("TcpSocketMonitor");
-    dw.incIndent();
+    ScopedIndent tcpSocketMonitorDetails(dw);
 
     const auto now = steady_clock::now();
     const auto d = duration_cast<milliseconds>(now - mLastPoll);
@@ -110,25 +114,25 @@
     if (!mNetworkStats.empty()) {
         dw.blankline();
         dw.println("Network stats:");
-        for (auto const& stats : mNetworkStats) {
+        for (const std::pair<const uint32_t, TcpStats>& stats : mNetworkStats) {
             if (stats.second.nSockets == 0) {
                 continue;
             }
-            dw.println("netId=%d sent=%d lost=%d rttMs=%gms sentAckDiff=%gms",
-                    stats.first,
-                    stats.second.sent,
-                    stats.second.lost,
-                    stats.second.rttUs / 1000.0 / stats.second.nSockets,
-                    stats.second.sentAckDiffMs / stats.second.nSockets);
+            dw.println("netId=%d sent=%d lost=%d rttMs=%gms sentAckDiff=%dms",
+                       stats.first,
+                       stats.second.sent,
+                       stats.second.lost,
+                       stats.second.rttUs / 1000.0 / stats.second.nSockets,
+                       stats.second.sentAckDiffMs / stats.second.nSockets);
         }
     }
 
     if (!mSocketEntries.empty()) {
         dw.blankline();
         dw.println("Socket entries:");
-        for (auto const& stats : mSocketEntries) {
-            dw.println("netId=%u uid=%u cookie=%ld",
-                    stats.second.mark.netId, stats.second.uid, stats.first);
+        for (const std::pair<const uint64_t, SocketEntry>& stats : mSocketEntries) {
+            dw.println("netId=%u uid=%u cookie=%" PRIu64, stats.second.mark.netId, stats.second.uid,
+                       stats.first);
         }
     }
 
@@ -147,12 +151,10 @@
     } else {
         ALOGE("Error opening sock diag for dumping TCP socket info");
     }
-
-    dw.decIndent();
 }
 
 void TcpSocketMonitor::setPollingInterval(milliseconds nextSleepDurationMs) {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
 
     mNextSleepDurationMs = nextSleepDurationMs;
 
@@ -162,7 +164,7 @@
 void TcpSocketMonitor::resumePolling() {
     bool wasSuspended;
     {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
 
         wasSuspended = mIsSuspended;
         mIsSuspended = false;
@@ -175,7 +177,7 @@
 }
 
 void TcpSocketMonitor::suspendPolling() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
 
     bool wasSuspended = mIsSuspended;
     mIsSuspended = true;
@@ -187,7 +189,7 @@
 }
 
 void TcpSocketMonitor::poll() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
 
     if (mIsSuspended) {
         return;
@@ -254,7 +256,7 @@
     bool isSuspended;
     milliseconds nextSleepDurationMs;
     {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         isSuspended = mIsSuspended;
         nextSleepDurationMs= mNextSleepDurationMs;
     }
@@ -268,7 +270,7 @@
 }
 
 bool TcpSocketMonitor::isRunning() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
     return mIsRunning;
 }
 
@@ -315,7 +317,7 @@
 }
 
 TcpSocketMonitor::TcpSocketMonitor() {
-    std::lock_guard<std::mutex> guard(mLock);
+    std::lock_guard guard(mLock);
 
     mNextSleepDurationMs = kDefaultPollingInterval;
     mIsRunning = true;
@@ -331,7 +333,7 @@
 
 TcpSocketMonitor::~TcpSocketMonitor() {
     {
-        std::lock_guard<std::mutex> guard(mLock);
+        std::lock_guard guard(mLock);
         mIsRunning = false;
         mIsSuspended = true;
     }
diff --git a/server/TcpSocketMonitor.h b/server/TcpSocketMonitor.h
index 98cd766..785f8ce 100644
--- a/server/TcpSocketMonitor.h
+++ b/server/TcpSocketMonitor.h
@@ -24,6 +24,7 @@
 #include <unordered_map>
 
 #include <android-base/thread_annotations.h>
+#include "netdutils/DumpWriter.h"
 #include "utils/String16.h"
 
 #include "Fwmark.h"
@@ -36,8 +37,6 @@
 
 using std::chrono::milliseconds;
 
-class DumpWriter;
-
 class TcpSocketMonitor {
   public:
     using time_point = std::chrono::time_point<std::chrono::steady_clock>;
@@ -78,7 +77,7 @@
     TcpSocketMonitor();
     ~TcpSocketMonitor();
 
-    void dump(DumpWriter& dw);
+    void dump(netdutils::DumpWriter& dw);
     void setPollingInterval(milliseconds duration);
     void resumePolling();
     void suspendPolling();
@@ -94,7 +93,7 @@
     std::mutex mLock;
     // Used by the polling thread for sleeping between poll operations.
     std::condition_variable mCv;
-    // The thread that polls sock_diag continously.
+    // The thread that polls sock_diag continuously.
     std::thread mPollingThread;
     // The duration of a sleep between polls. Can be updated by the instance owner for dynamically
     // adjusting the polling rate.
@@ -106,7 +105,7 @@
     // True while the polling thread should poll.
     bool mIsRunning GUARDED_BY(mLock);
     // Map of SocketEntry structs keyed by socket cookie. This map tracks per-socket data needed for
-    // computing diffs between sock_diag dumps. Entries for closed sockets are continously cleaned
+    // computing diffs between sock_diag dumps. Entries for closed sockets are continuously cleaned
     // after every dump operation based on timestamps of last updates.
     std::unordered_map<uint64_t, SocketEntry> mSocketEntries GUARDED_BY(mLock);
     // Map of TcpStats entries aggregated per network and keyed per network id.
diff --git a/server/TetherController.cpp b/server/TetherController.cpp
index 6e805f5..0c5b6bf 100644
--- a/server/TetherController.cpp
+++ b/server/TetherController.cpp
@@ -18,6 +18,7 @@
 #include <fcntl.h>
 #include <inttypes.h>
 #include <netdb.h>
+#include <spawn.h>
 #include <string.h>
 
 #include <sys/socket.h>
@@ -30,29 +31,35 @@
 
 #include <array>
 #include <cstdlib>
+#include <regex>
 #include <string>
 #include <vector>
 
 #define LOG_TAG "TetherController"
-#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
-#include <cutils/log.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <cutils/properties.h>
+#include <log/log.h>
 #include <netdutils/StatusOr.h>
 
+#include "Controllers.h"
 #include "Fwmark.h"
-#include "NetdConstants.h"
-#include "Permission.h"
 #include "InterfaceController.h"
+#include "NetdConstants.h"
 #include "NetworkController.h"
-#include "ResponseCode.h"
+#include "Permission.h"
 #include "TetherController.h"
 
+namespace android {
+namespace net {
+
 using android::base::Join;
+using android::base::Pipe;
 using android::base::StringPrintf;
-using android::base::StringAppendF;
-using android::netdutils::StatusOr;
+using android::base::unique_fd;
 using android::netdutils::statusFromErrno;
+using android::netdutils::StatusOr;
 
 namespace {
 
@@ -109,24 +116,54 @@
     return !strcmp(BP_TOOLS_MODE, bootmode);
 }
 
+int setPosixSpawnFileActionsAddDup2(posix_spawn_file_actions_t* fa, int fd, int new_fd) {
+    int res = posix_spawn_file_actions_init(fa);
+    if (res) {
+        return res;
+    }
+    return posix_spawn_file_actions_adddup2(fa, fd, new_fd);
+}
+
+int setPosixSpawnAttrFlags(posix_spawnattr_t* attr, short flags) {
+    int res = posix_spawnattr_init(attr);
+    if (res) {
+        return res;
+    }
+    return posix_spawnattr_setflags(attr, flags);
+}
+
 }  // namespace
 
-namespace android {
-namespace net {
-
 auto TetherController::iptablesRestoreFunction = execIptablesRestoreWithOutput;
 
-const int MAX_IPT_OUTPUT_LINE_LEN = 256;
-
 const std::string GET_TETHER_STATS_COMMAND = StringPrintf(
     "*filter\n"
     "-nvx -L %s\n"
     "COMMIT\n", android::net::TetherController::LOCAL_TETHER_COUNTERS_CHAIN);
 
+int TetherController::DnsmasqState::sendCmd(int daemonFd, const std::string& cmd) {
+    if (cmd.empty()) return 0;
+
+    gLog.log("Sending update msg to dnsmasq [%s]", cmd.c_str());
+    // Send the trailing \0 as well.
+    if (write(daemonFd, cmd.c_str(), cmd.size() + 1) < 0) {
+        gLog.error("Failed to send update command to dnsmasq (%s)", strerror(errno));
+        errno = EREMOTEIO;
+        return -1;
+    }
+    return 0;
+}
+
+void TetherController::DnsmasqState::clear() {
+    update_ifaces_cmd.clear();
+    update_dns_cmd.clear();
+}
+
+int TetherController::DnsmasqState::sendAllState(int daemonFd) const {
+    return sendCmd(daemonFd, update_ifaces_cmd) | sendCmd(daemonFd, update_dns_cmd);
+}
+
 TetherController::TetherController() {
-    mDnsNetId = 0;
-    mDaemonFd = -1;
-    mDaemonPid = 0;
     if (inBpToolsMode()) {
         enableForwarding(BP_TOOLS_MODE);
     } else {
@@ -134,19 +171,20 @@
     }
 }
 
-TetherController::~TetherController() {
-    mInterfaces.clear();
-    mDnsForwarders.clear();
-    mForwardingRequests.clear();
-    mFwdIfaces.clear();
-}
-
 bool TetherController::setIpFwdEnabled() {
     bool success = true;
-    const char* value = mForwardingRequests.empty() ? "0" : "1";
+    bool disable = mForwardingRequests.empty();
+    const char* value = disable ? "0" : "1";
     ALOGD("Setting IP forward enable = %s", value);
     success &= writeToFile(IPV4_FORWARDING_PROC_FILE, value);
     success &= writeToFile(IPV6_FORWARDING_PROC_FILE, value);
+    if (disable) {
+        // Turning off the forwarding sysconf in the kernel has the side effect
+        // of turning on ICMP redirect, which is a security hazard.
+        // Turn ICMP redirect back off immediately.
+        int rv = InterfaceController::disableIcmpRedirects();
+        success &= (rv == 0);
+    }
     return success;
 }
 
@@ -163,57 +201,36 @@
     return setIpFwdEnabled();
 }
 
-size_t TetherController::forwardingRequestCount() {
-    return mForwardingRequests.size();
+const std::set<std::string>& TetherController::getIpfwdRequesterList() const {
+    return mForwardingRequests;
 }
 
 int TetherController::startTethering(int num_addrs, char **dhcp_ranges) {
     if (mDaemonPid != 0) {
         ALOGE("Tethering already started");
         errno = EBUSY;
-        return -1;
+        return -errno;
     }
 
     ALOGD("Starting tethering services");
 
-    pid_t pid;
-    int pipefd[2];
-
-    if (pipe(pipefd) < 0) {
-        ALOGE("pipe failed (%s)", strerror(errno));
-        return -1;
+    unique_fd pipeRead, pipeWrite;
+    if (!Pipe(&pipeRead, &pipeWrite, O_CLOEXEC)) {
+        int res = errno;
+        ALOGE("pipe2() failed (%s)", strerror(errno));
+        return -res;
     }
 
-    /*
-     * TODO: Create a monitoring thread to handle and restart
-     * the daemon if it exits prematurely
-     */
-    if ((pid = fork()) < 0) {
-        ALOGE("fork failed (%s)", strerror(errno));
-        close(pipefd[0]);
-        close(pipefd[1]);
-        return -1;
-    }
+    // Set parameters
+    Fwmark fwmark;
+    fwmark.netId = NetworkController::LOCAL_NET_ID;
+    fwmark.explicitlySelected = true;
+    fwmark.protectedFromVpn = true;
+    fwmark.permission = PERMISSION_SYSTEM;
+    char markStr[UINT32_HEX_STRLEN];
+    snprintf(markStr, sizeof(markStr), "0x%x", fwmark.intValue);
 
-    if (!pid) {
-        close(pipefd[1]);
-        if (pipefd[0] != STDIN_FILENO) {
-            if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
-                ALOGE("dup2 failed (%s)", strerror(errno));
-                return -1;
-            }
-            close(pipefd[0]);
-        }
-
-        Fwmark fwmark;
-        fwmark.netId = NetworkController::LOCAL_NET_ID;
-        fwmark.explicitlySelected = true;
-        fwmark.protectedFromVpn = true;
-        fwmark.permission = PERMISSION_SYSTEM;
-        char markStr[UINT32_HEX_STRLEN];
-        snprintf(markStr, sizeof(markStr), "0x%x", fwmark.intValue);
-
-        std::vector<const std::string> argVector = {
+    std::vector<const std::string> argVector = {
             "/system/bin/dnsmasq",
             "--keep-in-foreground",
             "--no-resolv",
@@ -222,38 +239,84 @@
             // TODO: pipe through metered status from ConnService
             "--dhcp-option-force=43,ANDROID_METERED",
             "--pid-file",
-            "--listen-mark", markStr,
-            "--user", kDnsmasqUsername,
-        };
+            "--listen-mark",
+            markStr,
+            "--user",
+            kDnsmasqUsername,
+    };
 
-        for (int addrIndex = 0; addrIndex < num_addrs; addrIndex += 2) {
-            argVector.push_back(
-                    StringPrintf("--dhcp-range=%s,%s,1h",
-                                 dhcp_ranges[addrIndex], dhcp_ranges[addrIndex+1]));
-        }
-
-        auto args = (char**)std::calloc(argVector.size() + 1, sizeof(char*));
-        for (unsigned i = 0; i < argVector.size(); i++) {
-            args[i] = (char*)argVector[i].c_str();
-        }
-
-        if (execv(args[0], args)) {
-            ALOGE("execv failed (%s)", strerror(errno));
-        }
-        ALOGE("Should never get here!");
-        _exit(-1);
-    } else {
-        close(pipefd[0]);
-        mDaemonPid = pid;
-        mDaemonFd = pipefd[1];
-        configureForTethering(true);
-        applyDnsInterfaces();
-        ALOGD("Tethering services running");
+    // DHCP server will be disabled if num_addrs == 0 and no --dhcp-range is
+    // passed.
+    for (int addrIndex = 0; addrIndex < num_addrs; addrIndex += 2) {
+        argVector.push_back(StringPrintf("--dhcp-range=%s,%s,1h", dhcp_ranges[addrIndex],
+                                         dhcp_ranges[addrIndex + 1]));
     }
 
+    std::vector<char*> args(argVector.size() + 1);
+    for (unsigned i = 0; i < argVector.size(); i++) {
+        args[i] = (char*)argVector[i].c_str();
+    }
+
+    /*
+     * TODO: Create a monitoring thread to handle and restart
+     * the daemon if it exits prematurely
+     */
+
+    // Note that don't modify any memory between vfork and execv.
+    // Changing state of file descriptors would be fine. See posix_spawn_file_actions_add*
+    // dup2 creates fd without CLOEXEC, dnsmasq will receive commands through the
+    // duplicated fd.
+    posix_spawn_file_actions_t fa;
+    int res = setPosixSpawnFileActionsAddDup2(&fa, pipeRead.get(), STDIN_FILENO);
+    if (res) {
+        ALOGE("posix_spawn set fa failed (%s)", strerror(res));
+        return -res;
+    }
+
+    posix_spawnattr_t attr;
+    res = setPosixSpawnAttrFlags(&attr, POSIX_SPAWN_USEVFORK);
+    if (res) {
+        ALOGE("posix_spawn set attr flag failed (%s)", strerror(res));
+        return -res;
+    }
+
+    pid_t pid;
+    res = posix_spawn(&pid, args[0], &fa, &attr, &args[0], nullptr);
+    posix_spawnattr_destroy(&attr);
+    posix_spawn_file_actions_destroy(&fa);
+    if (res) {
+        ALOGE("posix_spawn failed (%s)", strerror(res));
+        return -res;
+    }
+    mDaemonPid = pid;
+    mDaemonFd = pipeWrite.release();
+    configureForTethering(true);
+    applyDnsInterfaces();
+    ALOGD("Tethering services running");
+
     return 0;
 }
 
+std::vector<char*> TetherController::toCstrVec(const std::vector<std::string>& addrs) {
+    std::vector<char*> addrsCstrVec{};
+    addrsCstrVec.reserve(addrs.size());
+    for (const auto& addr : addrs) {
+        addrsCstrVec.push_back(const_cast<char*>(addr.data()));
+    }
+    return addrsCstrVec;
+}
+
+int TetherController::startTethering(const std::vector<std::string>& dhcpRanges) {
+    struct in_addr v4_addr;
+    for (const auto& dhcpRange : dhcpRanges) {
+        if (!inet_aton(dhcpRange.c_str(), &v4_addr)) {
+            return -EINVAL;
+        }
+    }
+    auto dhcp_ranges = toCstrVec(dhcpRanges);
+    return startTethering(dhcp_ranges.size(), dhcp_ranges.data());
+}
+
 int TetherController::stopTethering() {
     configureForTethering(false);
 
@@ -265,10 +328,11 @@
     ALOGD("Stopping tethering services");
 
     kill(mDaemonPid, SIGTERM);
-    waitpid(mDaemonPid, NULL, 0);
+    waitpid(mDaemonPid, nullptr, 0);
     mDaemonPid = 0;
     close(mDaemonFd);
     mDaemonFd = -1;
+    mDnsmasqState.clear();
     ALOGD("Tethering services stopped");
     return 0;
 }
@@ -277,59 +341,62 @@
     return (mDaemonPid == 0 ? false : true);
 }
 
-#define MAX_CMD_SIZE 1024
+// dnsmasq can't parse commands larger than this due to the fixed-size buffer
+// in check_android_listeners(). The receiving buffer is 1024 bytes long, but
+// dnsmasq reads up to 1023 bytes.
+const size_t MAX_CMD_SIZE = 1023;
 
+// TODO: Remove overload function and update this after NDC migration.
 int TetherController::setDnsForwarders(unsigned netId, char **servers, int numServers) {
-    int i;
-    char daemonCmd[MAX_CMD_SIZE];
-
     Fwmark fwmark;
     fwmark.netId = netId;
     fwmark.explicitlySelected = true;
     fwmark.protectedFromVpn = true;
     fwmark.permission = PERMISSION_SYSTEM;
 
-    snprintf(daemonCmd, sizeof(daemonCmd), "update_dns%s0x%x", SEPARATOR, fwmark.intValue);
-    int cmdLen = strlen(daemonCmd);
+    std::string daemonCmd = StringPrintf("update_dns%s0x%x", SEPARATOR, fwmark.intValue);
 
     mDnsForwarders.clear();
-    for (i = 0; i < numServers; i++) {
+    for (int i = 0; i < numServers; i++) {
         ALOGD("setDnsForwarders(0x%x %d = '%s')", fwmark.intValue, i, servers[i]);
 
         addrinfo *res, hints = { .ai_flags = AI_NUMERICHOST };
-        int ret = getaddrinfo(servers[i], NULL, &hints, &res);
+        int ret = getaddrinfo(servers[i], nullptr, &hints, &res);
         freeaddrinfo(res);
         if (ret) {
             ALOGE("Failed to parse DNS server '%s'", servers[i]);
             mDnsForwarders.clear();
             errno = EINVAL;
-            return -1;
+            return -errno;
         }
 
-        cmdLen += (strlen(servers[i]) + 1);
-        if (cmdLen + 1 >= MAX_CMD_SIZE) {
-            ALOGD("Too many DNS servers listed");
+        if (daemonCmd.size() + 1 + strlen(servers[i]) >= MAX_CMD_SIZE) {
+            ALOGE("Too many DNS servers listed");
             break;
         }
 
-        strcat(daemonCmd, SEPARATOR);
-        strcat(daemonCmd, servers[i]);
+        daemonCmd += SEPARATOR;
+        daemonCmd += servers[i];
         mDnsForwarders.push_back(servers[i]);
     }
 
     mDnsNetId = netId;
+    mDnsmasqState.update_dns_cmd = std::move(daemonCmd);
     if (mDaemonFd != -1) {
-        ALOGD("Sending update msg to dnsmasq [%s]", daemonCmd);
-        if (write(mDaemonFd, daemonCmd, strlen(daemonCmd) +1) < 0) {
-            ALOGE("Failed to send update command to dnsmasq (%s)", strerror(errno));
+        if (mDnsmasqState.sendAllState(mDaemonFd) != 0) {
             mDnsForwarders.clear();
             errno = EREMOTEIO;
-            return -1;
+            return -errno;
         }
     }
     return 0;
 }
 
+int TetherController::setDnsForwarders(unsigned netId, const std::vector<std::string>& servers) {
+    auto dnsServers = toCstrVec(servers);
+    return setDnsForwarders(netId, dnsServers.data(), dnsServers.size());
+}
+
 unsigned TetherController::getDnsNetId() {
     return mDnsNetId;
 }
@@ -339,30 +406,25 @@
 }
 
 bool TetherController::applyDnsInterfaces() {
-    char daemonCmd[MAX_CMD_SIZE];
-
-    strcpy(daemonCmd, "update_ifaces");
-    int cmdLen = strlen(daemonCmd);
+    std::string daemonCmd = "update_ifaces";
     bool haveInterfaces = false;
 
-    for (const auto &ifname : mInterfaces) {
-        cmdLen += (ifname.size() + 1);
-        if (cmdLen + 1 >= MAX_CMD_SIZE) {
-            ALOGD("Too many DNS ifaces listed");
+    for (const auto& ifname : mInterfaces) {
+        if (daemonCmd.size() + 1 + ifname.size() >= MAX_CMD_SIZE) {
+            ALOGE("Too many DNS servers listed");
             break;
         }
 
-        strcat(daemonCmd, SEPARATOR);
-        strcat(daemonCmd, ifname.c_str());
+        daemonCmd += SEPARATOR;
+        daemonCmd += ifname;
         haveInterfaces = true;
     }
 
-    if ((mDaemonFd != -1) && haveInterfaces) {
-        ALOGD("Sending update msg to dnsmasq [%s]", daemonCmd);
-        if (write(mDaemonFd, daemonCmd, strlen(daemonCmd) +1) < 0) {
-            ALOGE("Failed to send update command to dnsmasq (%s)", strerror(errno));
-            return false;
-        }
+    if (!haveInterfaces) {
+        mDnsmasqState.update_ifaces_cmd.clear();
+    } else {
+        mDnsmasqState.update_ifaces_cmd = std::move(daemonCmd);
+        if (mDaemonFd != -1) return (mDnsmasqState.sendAllState(mDaemonFd) == 0);
     }
     return true;
 }
@@ -371,19 +433,19 @@
     ALOGD("tetherInterface(%s)", interface);
     if (!isIfaceName(interface)) {
         errno = ENOENT;
-        return -1;
+        return -errno;
     }
 
     if (!configureForIPv6Router(interface)) {
         configureForIPv6Client(interface);
-        return -1;
+        return -EREMOTEIO;
     }
     mInterfaces.push_back(interface);
 
     if (!applyDnsInterfaces()) {
         mInterfaces.pop_back();
         configureForIPv6Client(interface);
-        return -1;
+        return -EREMOTEIO;
     } else {
         return 0;
     }
@@ -397,11 +459,11 @@
             mInterfaces.erase(it);
 
             configureForIPv6Client(interface);
-            return applyDnsInterfaces() ? 0 : -1;
+            return applyDnsInterfaces() ? 0 : -EREMOTEIO;
         }
     }
     errno = ENOENT;
-    return -1;
+    return -errno;
 }
 
 const std::list<std::string> &TetherController::getTetheredInterfaceList() const {
@@ -455,12 +517,13 @@
         "COMMIT\n", LOCAL_FORWARD, LOCAL_FORWARD, LOCAL_NAT_POSTROUTING);
 
     std::string v6Cmd = StringPrintf(
-        "*filter\n"
-        ":%s -\n"
-        "COMMIT\n"
-        "*raw\n"
-        ":%s -\n"
-        "COMMIT\n", LOCAL_FORWARD, LOCAL_RAW_PREROUTING);
+            "*filter\n"
+            ":%s -\n"
+            "COMMIT\n"
+            "*raw\n"
+            ":%s -\n"
+            "COMMIT\n",
+            LOCAL_FORWARD, LOCAL_RAW_PREROUTING);
 
     int res = iptablesRestoreFunction(V4, v4Cmd, nullptr);
     if (res < 0) {
@@ -479,15 +542,13 @@
     ALOGV("enableNat(intIface=<%s>, extIface=<%s>)",intIface, extIface);
 
     if (!isIfaceName(intIface) || !isIfaceName(extIface)) {
-        errno = ENODEV;
-        return -1;
+        return -ENODEV;
     }
 
     /* Bug: b/9565268. "enableNat wlan0 wlan0". For now we fail until java-land is fixed */
     if (!strcmp(intIface, extIface)) {
         ALOGE("Duplicate interface specified: %s %s", intIface, extIface);
-        errno = EINVAL;
-        return -1;
+        return -EINVAL;
     }
 
     if (isForwardingPairEnabled(intIface, extIface)) {
@@ -502,14 +563,14 @@
             "COMMIT\n"
         };
 
-        if (iptablesRestoreFunction(V4, Join(v4Cmds, '\n'), nullptr) ||
-            setupIPv6CountersChain()) {
+        if (iptablesRestoreFunction(V4, Join(v4Cmds, '\n'), nullptr) || setupIPv6CountersChain() ||
+            setTetherGlobalAlertRule()) {
             ALOGE("Error setting postroute rule: iface=%s", extIface);
             if (!isAnyForwardingPairEnabled()) {
                 // unwind what's been done, but don't care about success - what more could we do?
                 setDefaults();
             }
-            return -1;
+            return -EREMOTEIO;
         }
     }
 
@@ -518,13 +579,25 @@
         if (!isAnyForwardingPairEnabled()) {
             setDefaults();
         }
-        errno = ENODEV;
-        return -1;
+        return -ENODEV;
     }
 
     return 0;
 }
 
+int TetherController::setTetherGlobalAlertRule() {
+    // Only add this if we are the first enabled nat
+    if (isAnyForwardingPairEnabled()) {
+        return 0;
+    }
+    const std::string cmds =
+            "*filter\n" +
+            StringPrintf("-I %s -j %s\n", LOCAL_FORWARD, BandwidthController::LOCAL_GLOBAL_ALERT) +
+            "COMMIT\n";
+
+    return iptablesRestoreFunction(V4V6, cmds, nullptr);
+}
+
 int TetherController::setupIPv6CountersChain() {
     // Only add this if we are the first enabled nat
     if (isAnyForwardingPairEnabled()) {
@@ -535,13 +608,11 @@
      * IPv6 tethering doesn't need the state-based conntrack rules, so
      * it unconditionally jumps to the tether counters chain all the time.
      */
-    std::vector<std::string> v6Cmds = {
-        "*filter",
-        StringPrintf("-A %s -g %s", LOCAL_FORWARD, LOCAL_TETHER_COUNTERS_CHAIN),
-        "COMMIT\n"
-    };
+    const std::string v6Cmds =
+            "*filter\n" +
+            StringPrintf("-A %s -g %s\n", LOCAL_FORWARD, LOCAL_TETHER_COUNTERS_CHAIN) + "COMMIT\n";
 
-    return iptablesRestoreFunction(V6, Join(v6Cmds, '\n'), nullptr);
+    return iptablesRestoreFunction(V6, v6Cmds, nullptr);
 }
 
 // Gets a pointer to the ForwardingDownstream for an interface pair in the map, or nullptr
@@ -626,17 +697,23 @@
         "%s %s -i %s -m rpfilter --invert ! -s fe80::/64 -j DROP\n"
         "COMMIT\n", op, LOCAL_RAW_PREROUTING, intIface);
     if (iptablesRestoreFunction(V6, rpfilterCmd, nullptr) == -1 && add) {
-        return -1;
+        return -EREMOTEIO;
     }
 
     std::vector<std::string> v4 = {
-        "*filter",
-        StringPrintf("%s %s -i %s -o %s -m state --state ESTABLISHED,RELATED -g %s",
-                     op, LOCAL_FORWARD, extIface, intIface, LOCAL_TETHER_COUNTERS_CHAIN),
-        StringPrintf("%s %s -i %s -o %s -m state --state INVALID -j DROP",
-                     op, LOCAL_FORWARD, intIface, extIface),
-        StringPrintf("%s %s -i %s -o %s -g %s",
-                     op, LOCAL_FORWARD, intIface, extIface, LOCAL_TETHER_COUNTERS_CHAIN),
+            "*raw",
+            StringPrintf("%s %s -p tcp --dport 21 -i %s -j CT --helper ftp", op,
+                         LOCAL_RAW_PREROUTING, intIface),
+            StringPrintf("%s %s -p tcp --dport 1723 -i %s -j CT --helper pptp", op,
+                         LOCAL_RAW_PREROUTING, intIface),
+            "COMMIT",
+            "*filter",
+            StringPrintf("%s %s -i %s -o %s -m state --state ESTABLISHED,RELATED -g %s", op,
+                         LOCAL_FORWARD, extIface, intIface, LOCAL_TETHER_COUNTERS_CHAIN),
+            StringPrintf("%s %s -i %s -o %s -m state --state INVALID -j DROP", op, LOCAL_FORWARD,
+                         intIface, extIface),
+            StringPrintf("%s %s -i %s -o %s -g %s", op, LOCAL_FORWARD, intIface, extIface,
+                         LOCAL_TETHER_COUNTERS_CHAIN),
     };
 
     std::vector<std::string> v6 = {
@@ -669,7 +746,7 @@
         if (add) {
             setForwardRules(false, intIface, extIface);
         }
-        return -1;
+        return -EREMOTEIO;
     }
 
     if (add) {
@@ -684,7 +761,7 @@
 int TetherController::disableNat(const char* intIface, const char* extIface) {
     if (!isIfaceName(intIface) || !isIfaceName(extIface)) {
         errno = ENODEV;
-        return -1;
+        return -errno;
     }
 
     setForwardRules(false, intIface, extIface);
@@ -722,66 +799,75 @@
 int TetherController::addForwardChainStats(TetherStatsList& statsList,
                                            const std::string& statsOutput,
                                            std::string &extraProcessingInfo) {
-    int res;
-    std::string statsLine;
-    char iface0[MAX_IPT_OUTPUT_LINE_LEN];
-    char iface1[MAX_IPT_OUTPUT_LINE_LEN];
-    char rest[MAX_IPT_OUTPUT_LINE_LEN];
-
+    enum IndexOfIptChain {
+        ORIG_LINE,
+        PACKET_COUNTS,
+        BYTE_COUNTS,
+        HYPHEN,
+        IFACE0_NAME,
+        IFACE1_NAME,
+        SOURCE,
+        DESTINATION
+    };
     TetherStats stats;
     const TetherStats empty;
-    const char *buffPtr;
-    int64_t packets, bytes;
 
-    std::stringstream stream(statsOutput);
+    static const std::string NUM = "(\\d+)";
+    static const std::string IFACE = "([^\\s]+)";
+    static const std::string DST = "(0.0.0.0/0|::/0)";
+    static const std::string COUNTERS = "\\s*" + NUM + "\\s+" + NUM +
+                                        " RETURN     all(  --  |      )" + IFACE + "\\s+" + IFACE +
+                                        "\\s+" + DST + "\\s+" + DST;
+    static const std::regex IP_RE(COUNTERS);
 
-    // Skip headers.
-    for (int i = 0; i < 2; i++) {
-        std::getline(stream, statsLine, '\n');
-        extraProcessingInfo += statsLine + "\n";
-        if (statsLine.empty()) {
-            ALOGE("Empty header while parsing tethering stats");
-            return -EREMOTEIO;
+    const std::vector<std::string> lines = base::Split(statsOutput, "\n");
+    int headerLine = 0;
+    for (const std::string& line : lines) {
+        // Skip headers.
+        if (headerLine < 2) {
+            if (line.empty()) {
+                ALOGV("Empty header while parsing tethering stats");
+                return -EREMOTEIO;
+            }
+            headerLine++;
+            continue;
         }
-    }
 
-    while (std::getline(stream, statsLine, '\n')) {
-        buffPtr = statsLine.c_str();
+        if (line.empty()) continue;
 
-        /* Clean up, so a failed parse can still print info */
-        iface0[0] = iface1[0] = rest[0] = packets = bytes = 0;
-        if (strstr(buffPtr, "0.0.0.0")) {
-            // IPv4 has -- indicating what to do with fragments...
-            //       26     2373 RETURN     all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0
-            res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all -- %s %s 0.%s",
-                    &packets, &bytes, iface0, iface1, rest);
-        } else {
-            // ... but IPv6 does not.
-            //       26     2373 RETURN     all      wlan0  rmnet0  ::/0                 ::/0
-            res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all %s %s ::/%s",
-                    &packets, &bytes, iface0, iface1, rest);
-        }
-        ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%" PRId64" bytes=%" PRId64" rest=<%s> orig line=<%s>", res,
-             iface0, iface1, packets, bytes, rest, buffPtr);
-        extraProcessingInfo += buffPtr;
-        extraProcessingInfo += "\n";
+        extraProcessingInfo = line;
+        std::smatch matches;
+        if (!std::regex_search(line, matches, IP_RE)) return -EREMOTEIO;
+        // Here use IP_RE to distiguish IPv4 and IPv6 iptables.
+        // IPv4 has "--" indicating what to do with fragments...
+        //		 26 	2373 RETURN     all  --  wlan0	rmnet0	0.0.0.0/0			 0.0.0.0/0
+        // ... but IPv6 does not.
+        //		 26 	2373 RETURN 	all      wlan0	rmnet0	::/0				 ::/0
+        // TODO: Replace strtoXX() calls with ParseUint() /ParseInt()
+        int64_t packets = strtoul(matches[PACKET_COUNTS].str().c_str(), nullptr, 10);
+        int64_t bytes = strtoul(matches[BYTE_COUNTS].str().c_str(), nullptr, 10);
+        std::string iface0 = matches[IFACE0_NAME].str();
+        std::string iface1 = matches[IFACE1_NAME].str();
+        std::string rest = matches[SOURCE].str();
 
-        if (res != 5) {
-            return -EREMOTEIO;
-        }
+        ALOGV("parse iface0=<%s> iface1=<%s> pkts=%" PRId64 " bytes=%" PRId64
+              " rest=<%s> orig line=<%s>",
+              iface0.c_str(), iface1.c_str(), packets, bytes, rest.c_str(), line.c_str());
         /*
          * The following assumes that the 1st rule has in:extIface out:intIface,
          * which is what TetherController sets up.
          * The 1st matches rx, and sets up the pair for the tx side.
          */
         if (!stats.intIface[0]) {
-            ALOGV("0Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+            ALOGV("0Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64 " rx_packets=%" PRId64
+                  " ", iface0.c_str(), iface1.c_str(), bytes, packets);
             stats.intIface = iface0;
             stats.extIface = iface1;
             stats.txPackets = packets;
             stats.txBytes = bytes;
         } else if (stats.intIface == iface1 && stats.extIface == iface0) {
-            ALOGV("0Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+            ALOGV("0Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64 " rx_packets=%" PRId64
+                  " ", iface0.c_str(), iface1.c_str(), bytes, packets);
             stats.rxPackets = packets;
             stats.rxBytes = bytes;
         }
diff --git a/server/TetherController.h b/server/TetherController.h
index a34b5b7..0a04874 100644
--- a/server/TetherController.h
+++ b/server/TetherController.h
@@ -26,13 +26,12 @@
 
 #include "NetdConstants.h"
 
+
 namespace android {
 namespace net {
 
-using android::netdutils::StatusOr;
-
 class TetherController {
-private:
+  private:
     struct ForwardingDownstream {
         std::string iface;
         bool active;
@@ -46,27 +45,42 @@
 
     // NetId to use for forwarded DNS queries. This may not be the default
     // network, e.g., in the case where we are tethering to a DUN APN.
-    unsigned               mDnsNetId;
+    unsigned               mDnsNetId = 0;
     std::list<std::string> mDnsForwarders;
-    pid_t                  mDaemonPid;
-    int                    mDaemonFd;
+    pid_t                  mDaemonPid = 0;
+    int                    mDaemonFd = -1;
     std::set<std::string>  mForwardingRequests;
 
-public:
+    struct DnsmasqState {
+        static int sendCmd(int daemonFd, const std::string& cmd);
 
+        // List of downstream interfaces on which to serve. The format used is:
+        //     update_ifaces|<ifname1>|<ifname2>|...
+        std::string update_ifaces_cmd;
+        // Forwarding (upstream) DNS configuration to use. The format used is:
+        //     update_dns|<hex_socket_mark>|<ip1>|<ip2>|...
+        std::string update_dns_cmd;
+
+        void clear();
+        int sendAllState(int daemonFd) const;
+    } mDnsmasqState{};
+
+  public:
     TetherController();
-    virtual ~TetherController();
+    ~TetherController() = default;
 
     bool enableForwarding(const char* requester);
     bool disableForwarding(const char* requester);
-    size_t forwardingRequestCount();
+    const std::set<std::string>& getIpfwdRequesterList() const;
 
     int startTethering(int num_addrs, char **dhcp_ranges);
+    int startTethering(const std::vector<std::string>& dhcpRanges);
     int stopTethering();
     bool isTetheringStarted();
 
     unsigned getDnsNetId();
     int setDnsForwarders(unsigned netId, char **servers, int numServers);
+    int setDnsForwarders(unsigned netId, const std::vector<std::string>& servers);
     const std::list<std::string> &getDnsForwarders() const;
 
     int tetherInterface(const char *interface);
@@ -79,7 +93,7 @@
     int setupIptablesHooks();
 
     class TetherStats {
-    public:
+      public:
         TetherStats() = default;
         TetherStats(std::string intIfn, std::string extIfn,
                 int64_t rxB, int64_t rxP,
@@ -108,7 +122,7 @@
 
     typedef std::vector<TetherStats> TetherStatsList;
 
-    StatusOr<TetherStatsList> getTetherStats();
+    netdutils::StatusOr<TetherStatsList> getTetherStats();
 
     /*
      * extraProcessingInfo: contains raw parsed data, and error info.
@@ -126,11 +140,11 @@
     static constexpr const char* LOCAL_RAW_PREROUTING        = "tetherctrl_raw_PREROUTING";
     static constexpr const char* LOCAL_TETHER_COUNTERS_CHAIN = "tetherctrl_counters";
 
-    android::RWLock lock;
+    std::mutex lock;
 
-private:
+  private:
     bool setIpFwdEnabled();
-
+    std::vector<char*> toCstrVec(const std::vector<std::string>& addrs);
     int setupIPv6CountersChain();
     static std::string makeTetherCountingRule(const char *if1, const char *if2);
     ForwardingDownstream* findForwardingDownstream(const std::string& intIface,
@@ -144,6 +158,7 @@
     bool tetherCountingRuleExists(const std::string& iface1, const std::string& iface2);
 
     int setDefaults();
+    int setTetherGlobalAlertRule();
     int setForwardRules(bool set, const char *intIface, const char *extIface);
     int setTetherCountingRules(bool add, const char *intIface, const char *extIface);
 
diff --git a/server/TetherControllerTest.cpp b/server/TetherControllerTest.cpp
index bcdb106..309a6d5 100644
--- a/server/TetherControllerTest.cpp
+++ b/server/TetherControllerTest.cpp
@@ -56,42 +56,55 @@
     }
 
     const ExpectedIptablesCommands FLUSH_COMMANDS = {
-        { V4,   "*filter\n"
-                ":tetherctrl_FORWARD -\n"
-                "-A tetherctrl_FORWARD -j DROP\n"
-                "COMMIT\n"
-                "*nat\n"
-                ":tetherctrl_nat_POSTROUTING -\n"
-                "COMMIT\n" },
-        { V6,   "*filter\n"
-                ":tetherctrl_FORWARD -\n"
-                "COMMIT\n"
-                "*raw\n"
-                ":tetherctrl_raw_PREROUTING -\n"
-                "COMMIT\n" },
+            {V4,
+             "*filter\n"
+             ":tetherctrl_FORWARD -\n"
+             "-A tetherctrl_FORWARD -j DROP\n"
+             "COMMIT\n"
+             "*nat\n"
+             ":tetherctrl_nat_POSTROUTING -\n"
+             "COMMIT\n"},
+            {V6,
+             "*filter\n"
+             ":tetherctrl_FORWARD -\n"
+             "COMMIT\n"
+             "*raw\n"
+             ":tetherctrl_raw_PREROUTING -\n"
+             "COMMIT\n"},
     };
 
     const ExpectedIptablesCommands SETUP_COMMANDS = {
-        { V4,   "*filter\n"
-                ":tetherctrl_FORWARD -\n"
-                "-A tetherctrl_FORWARD -j DROP\n"
-                "COMMIT\n"
-                "*nat\n"
-                ":tetherctrl_nat_POSTROUTING -\n"
-                "COMMIT\n" },
-        { V6,   "*filter\n"
-                ":tetherctrl_FORWARD -\n"
-                "COMMIT\n"
-                "*raw\n"
-                ":tetherctrl_raw_PREROUTING -\n"
-                "COMMIT\n" },
-        { V4,   "*mangle\n"
-                "-A tetherctrl_mangle_FORWARD -p tcp --tcp-flags SYN SYN "
-                    "-j TCPMSS --clamp-mss-to-pmtu\n"
-                "COMMIT\n" },
-        { V4V6, "*filter\n"
-                ":tetherctrl_counters -\n"
-                "COMMIT\n" },
+            {V4,
+             "*filter\n"
+             ":tetherctrl_FORWARD -\n"
+             "-A tetherctrl_FORWARD -j DROP\n"
+             "COMMIT\n"
+             "*nat\n"
+             ":tetherctrl_nat_POSTROUTING -\n"
+             "COMMIT\n"},
+            {V6,
+             "*filter\n"
+             ":tetherctrl_FORWARD -\n"
+             "COMMIT\n"
+             "*raw\n"
+             ":tetherctrl_raw_PREROUTING -\n"
+             "COMMIT\n"},
+            {V4,
+             "*mangle\n"
+             "-A tetherctrl_mangle_FORWARD -p tcp --tcp-flags SYN SYN "
+             "-j TCPMSS --clamp-mss-to-pmtu\n"
+             "COMMIT\n"},
+            {V4V6,
+             "*filter\n"
+             ":tetherctrl_counters -\n"
+             "COMMIT\n"},
+    };
+
+    const ExpectedIptablesCommands ALERT_ADD_COMMAND = {
+            {V4V6,
+             "*filter\n"
+             "-I tetherctrl_FORWARD -j bw_global_alert\n"
+             "COMMIT\n"},
     };
 
     ExpectedIptablesCommands firstIPv4UpstreamCommands(const char *extIf) {
@@ -106,9 +119,9 @@
 
     ExpectedIptablesCommands firstIPv6UpstreamCommands() {
         std::string v6Cmd =
-            "*filter\n"
-            "-A tetherctrl_FORWARD -g tetherctrl_counters\n"
-            "COMMIT\n";
+                "*filter\n"
+                "-A tetherctrl_FORWARD -g tetherctrl_counters\n"
+                "COMMIT\n";
         return {
             { V6, v6Cmd },
         };
@@ -127,13 +140,22 @@
             "COMMIT\n", intIf);
 
         std::vector<std::string> v4Cmds = {
-            "*filter",
-            StringPrintf("-A tetherctrl_FORWARD -i %s -o %s -m state --state"
-                         " ESTABLISHED,RELATED -g tetherctrl_counters", extIf, intIf),
-            StringPrintf("-A tetherctrl_FORWARD -i %s -o %s -m state --state INVALID -j DROP",
-                         intIf, extIf),
-            StringPrintf("-A tetherctrl_FORWARD -i %s -o %s -g tetherctrl_counters",
-                         intIf, extIf),
+                "*raw",
+                StringPrintf(
+                        "-A tetherctrl_raw_PREROUTING -p tcp --dport 21 -i %s -j CT --helper ftp",
+                        intIf),
+                StringPrintf("-A tetherctrl_raw_PREROUTING -p tcp --dport 1723 -i %s -j CT "
+                             "--helper pptp",
+                             intIf),
+                "COMMIT",
+                "*filter",
+                StringPrintf("-A tetherctrl_FORWARD -i %s -o %s -m state --state"
+                             " ESTABLISHED,RELATED -g tetherctrl_counters",
+                             extIf, intIf),
+                StringPrintf("-A tetherctrl_FORWARD -i %s -o %s -m state --state INVALID -j DROP",
+                             intIf, extIf),
+                StringPrintf("-A tetherctrl_FORWARD -i %s -o %s -g tetherctrl_counters", intIf,
+                             extIf),
         };
 
         std::vector<std::string> v6Cmds = {
@@ -169,10 +191,9 @@
     constexpr static const bool NO_COUNTERS = false;
     constexpr static const bool WITH_IPV6 = true;
     constexpr static const bool NO_IPV6 = false;
-    ExpectedIptablesCommands allNewNatCommands(
-            const char *intIf, const char *extIf, bool withCounterChainRules,
-            bool withIPv6Upstream) {
-
+    ExpectedIptablesCommands allNewNatCommands(const char* intIf, const char* extIf,
+                                               bool withCounterChainRules, bool withIPv6Upstream,
+                                               bool firstEnableNat) {
         ExpectedIptablesCommands commands;
         ExpectedIptablesCommands setupFirstIPv4Commands = firstIPv4UpstreamCommands(extIf);
         ExpectedIptablesCommands startFirstNatCommands = startNatCommands(intIf, extIf,
@@ -183,6 +204,9 @@
             ExpectedIptablesCommands setupFirstIPv6Commands = firstIPv6UpstreamCommands();
             appendAll(commands, setupFirstIPv6Commands);
         }
+        if (firstEnableNat) {
+            appendAll(commands, ALERT_ADD_COMMAND);
+        }
         appendAll(commands, startFirstNatCommands);
 
         return commands;
@@ -195,14 +219,23 @@
             "COMMIT\n", intIf);
 
         std::vector<std::string> v4Cmds = {
-            "*filter",
-            StringPrintf("-D tetherctrl_FORWARD -i %s -o %s -m state --state"
-                         " ESTABLISHED,RELATED -g tetherctrl_counters", extIf, intIf),
-            StringPrintf("-D tetherctrl_FORWARD -i %s -o %s -m state --state INVALID -j DROP",
-                         intIf, extIf),
-            StringPrintf("-D tetherctrl_FORWARD -i %s -o %s -g tetherctrl_counters",
-                         intIf, extIf),
-            "COMMIT\n",
+                "*raw",
+                StringPrintf(
+                        "-D tetherctrl_raw_PREROUTING -p tcp --dport 21 -i %s -j CT --helper ftp",
+                        intIf),
+                StringPrintf("-D tetherctrl_raw_PREROUTING -p tcp --dport 1723 -i %s -j CT "
+                             "--helper pptp",
+                             intIf),
+                "COMMIT",
+                "*filter",
+                StringPrintf("-D tetherctrl_FORWARD -i %s -o %s -m state --state"
+                             " ESTABLISHED,RELATED -g tetherctrl_counters",
+                             extIf, intIf),
+                StringPrintf("-D tetherctrl_FORWARD -i %s -o %s -m state --state INVALID -j DROP",
+                             intIf, extIf),
+                StringPrintf("-D tetherctrl_FORWARD -i %s -o %s -g tetherctrl_counters", intIf,
+                             extIf),
+                "COMMIT\n",
         };
 
         return {
@@ -225,8 +258,8 @@
 
 TEST_F(TetherControllerTest, TestAddAndRemoveNat) {
     // Start first NAT on first upstream interface. Expect the upstream and NAT rules to be created.
-    ExpectedIptablesCommands firstNat = allNewNatCommands(
-            "wlan0", "rmnet0", WITH_COUNTERS, WITH_IPV6);
+    ExpectedIptablesCommands firstNat =
+            allNewNatCommands("wlan0", "rmnet0", WITH_COUNTERS, WITH_IPV6, true);
     mTetherCtrl.enableNat("wlan0", "rmnet0");
     expectIptablesRestoreCommands(firstNat);
 
@@ -249,7 +282,7 @@
     expectIptablesRestoreCommands(stopLastNat);
 
     // Re-add a NAT removed previously: tetherctrl_counters chain rules are not re-added
-    firstNat = allNewNatCommands("wlan0", "rmnet0", NO_COUNTERS, WITH_IPV6);
+    firstNat = allNewNatCommands("wlan0", "rmnet0", NO_COUNTERS, WITH_IPV6, true);
     mTetherCtrl.enableNat("wlan0", "rmnet0");
     expectIptablesRestoreCommands(firstNat);
 
@@ -262,15 +295,15 @@
 
 TEST_F(TetherControllerTest, TestMultipleUpstreams) {
     // Start first NAT on first upstream interface. Expect the upstream and NAT rules to be created.
-    ExpectedIptablesCommands firstNat = allNewNatCommands(
-            "wlan0", "rmnet0", WITH_COUNTERS, WITH_IPV6);
+    ExpectedIptablesCommands firstNat =
+            allNewNatCommands("wlan0", "rmnet0", WITH_COUNTERS, WITH_IPV6, true);
     mTetherCtrl.enableNat("wlan0", "rmnet0");
     expectIptablesRestoreCommands(firstNat);
 
     // Start second NAT, on new upstream. Expect the upstream and NAT rules to be created for IPv4,
     // but no counter rules for IPv6.
-    ExpectedIptablesCommands secondNat = allNewNatCommands(
-            "wlan0", "v4-rmnet0", WITH_COUNTERS, NO_IPV6);
+    ExpectedIptablesCommands secondNat =
+            allNewNatCommands("wlan0", "v4-rmnet0", WITH_COUNTERS, NO_IPV6, false);
     mTetherCtrl.enableNat("wlan0", "v4-rmnet0");
     expectIptablesRestoreCommands(secondNat);
 
@@ -395,7 +428,8 @@
 
     // Token unit test of the fact that we return the stats in the error message which the caller
     // ignores.
-    std::string expectedError = counters;
+    // Skip header since we only saved the last line we parsed.
+    std::string expectedError = counterLines[2];
     std::string err = result.status().msg();
     ASSERT_LE(expectedError.size(), err.size());
     EXPECT_TRUE(std::equal(expectedError.rbegin(), expectedError.rend(), err.rbegin()));
diff --git a/server/TrafficController.cpp b/server/TrafficController.cpp
index d6a6480..8dceb5b 100644
--- a/server/TrafficController.cpp
+++ b/server/TrafficController.cpp
@@ -43,25 +43,26 @@
 
 #include <netdutils/Misc.h>
 #include <netdutils/Syscalls.h>
+#include <processgroup/processgroup.h>
 #include "TrafficController.h"
 #include "bpf/BpfMap.h"
-#include "bpf/bpf_shared.h"
 
-#include "DumpWriter.h"
 #include "FirewallController.h"
 #include "InterfaceController.h"
 #include "NetlinkListener.h"
+#include "netdutils/DumpWriter.h"
 #include "qtaguid/qtaguid.h"
 
-using namespace android::bpf;
+using namespace android::bpf;  // NOLINT(google-build-using-namespace): grandfathered
 
 namespace android {
 namespace net {
 
 using base::StringPrintf;
 using base::unique_fd;
-using base::Join;
+using netdutils::DumpWriter;
 using netdutils::extract;
+using netdutils::ScopedIndent;
 using netdutils::Slice;
 using netdutils::sSyscalls;
 using netdutils::Status;
@@ -71,8 +72,68 @@
 
 constexpr int kSockDiagMsgType = SOCK_DIAG_BY_FAMILY;
 constexpr int kSockDiagDoneMsgType = NLMSG_DONE;
+constexpr int PER_UID_STATS_ENTRIES_LIMIT = 500;
+// At most 90% of the stats map may be used by tagged traffic entries. This ensures
+// that 10% of the map is always available to count untagged traffic, one entry per UID.
+// Otherwise, apps would be able to avoid data usage accounting entirely by filling up the
+// map with tagged traffic entries.
+constexpr int TOTAL_UID_STATS_ENTRIES_LIMIT = STATS_MAP_SIZE * 0.9;
 
-StatusOr<std::unique_ptr<NetlinkListenerInterface>> makeSkDestroyListener() {
+static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
+              "Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
+static_assert(BPF_PERMISSION_UPDATE_DEVICE_STATS == INetd::PERMISSION_UPDATE_DEVICE_STATS,
+              "Mismatch between BPF and AIDL permissions: PERMISSION_UPDATE_DEVICE_STATS");
+static_assert(STATS_MAP_SIZE - TOTAL_UID_STATS_ENTRIES_LIMIT > 100,
+              "The limit for stats map is to high, stats data may be lost due to overflow");
+
+#define FLAG_MSG_TRANS(result, flag, value)            \
+    do {                                               \
+        if (value & flag) {                            \
+            result.append(StringPrintf(" %s", #flag)); \
+            value &= ~flag;                            \
+        }                                              \
+    } while (0)
+
+const std::string uidMatchTypeToString(uint8_t match) {
+    std::string matchType;
+    FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match);
+    FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match);
+    FLAG_MSG_TRANS(matchType, DOZABLE_MATCH, match);
+    FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match);
+    FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match);
+    FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
+    if (match) {
+        return StringPrintf("Unknown match: %u", match);
+    }
+    return matchType;
+}
+
+bool TrafficController::hasUpdateDeviceStatsPermission(uid_t uid) {
+    // This implementation is the same logic as method ActivityManager#checkComponentPermission.
+    // It implies that the calling uid can never be the same as PER_USER_RANGE.
+    uint32_t appId = uid % PER_USER_RANGE;
+    return ((appId == AID_ROOT) || (appId == AID_SYSTEM) ||
+            mPrivilegedUser.find(appId) != mPrivilegedUser.end());
+}
+
+const std::string UidPermissionTypeToString(uint8_t permission) {
+    if (permission == INetd::PERMISSION_NONE) {
+        return "PERMISSION_NONE";
+    }
+    if (permission == INetd::PERMISSION_UNINSTALLED) {
+        // This should never appear in the map, complain loudly if it does.
+        return "PERMISSION_UNINSTALLED error!";
+    }
+    std::string permissionType;
+    FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_INTERNET, permission);
+    FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_UPDATE_DEVICE_STATS, permission);
+    if (permission) {
+        return StringPrintf("Unknown permission: %u", permission);
+    }
+    return permissionType;
+}
+
+StatusOr<std::unique_ptr<NetlinkListenerInterface>> TrafficController::makeSkDestroyListener() {
     const auto& sys = sSyscalls.get();
     ASSIGN_OR_RETURN(auto event, sys.eventfd(0, EFD_CLOEXEC));
     const int domain = AF_NETLINK;
@@ -80,6 +141,17 @@
     const int protocol = NETLINK_INET_DIAG;
     ASSIGN_OR_RETURN(auto sock, sys.socket(domain, type, protocol));
 
+    // TODO: if too many sockets are closed too quickly, we can overflow the socket buffer, and
+    // some entries in mCookieTagMap will not be freed. In order to fix this we would need to
+    // periodically dump all sockets and remove the tag entries for sockets that have been closed.
+    // For now, set a large-enough buffer that we can close hundreds of sockets without getting
+    // ENOBUFS and leaking mCookieTagMap entries.
+    int rcvbuf = 512 * 1024;
+    auto ret = sys.setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
+    if (!ret.ok()) {
+        ALOGW("Failed to set SkDestroyListener buffer size to %d: %s", rcvbuf, ret.msg().c_str());
+    }
+
     sockaddr_nl addr = {
         .nl_family = AF_NETLINK,
         .nl_groups = 1 << (SKNLGRP_INET_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET_UDP_DESTROY - 1) |
@@ -90,7 +162,7 @@
     RETURN_IF_NOT_OK(sys.connect(sock, kernel));
 
     std::unique_ptr<NetlinkListenerInterface> listener =
-        std::make_unique<NetlinkListener>(std::move(event), std::move(sock));
+            std::make_unique<NetlinkListener>(std::move(event), std::move(sock), "SkDestroyListen");
 
     return listener;
 }
@@ -106,97 +178,117 @@
         // chmod doesn't grant permission to all processes in that group to
         // read/write the bpf map. They still need correct sepolicy to
         // read/write the map.
-        ret = chmod(path, S_IRWXU | S_IRGRP);
+        ret = chmod(path, S_IRWXU | S_IRGRP | S_IWGRP);
     }
     if (ret != 0) return statusFromErrno(errno, StringPrintf("change %s mode failed", debugName));
     return netdutils::status::ok;
 }
 
-TrafficController::TrafficController() {
-    ebpfSupported = hasBpfSupport();
-}
+TrafficController::TrafficController()
+    : mBpfLevel(getBpfSupportLevel()),
+      mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
+      mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
 
-Status initialOwnerMap(BpfMap<uint32_t, uint8_t>& map) {
-    // Check and delete all the entries from the map in case it is a runtime
-    // restart
-    const auto deleteAllEntries = [](const uint32_t& key, BpfMap<uint32_t, uint8_t>& map) {
-        Status res = map.deleteValue(key);
-        if (!isOk(res) && (res.code() == ENOENT)) {
-            ALOGE("Failed to delete data(uid=%u): %s\n", key, strerror(res.code()));
-        }
-        return netdutils::status::ok;
-    };
-    // It is safe to delete from this map because nothing will concurrently iterate over it:
-    // - Nothing in netd will iterate over it because we're holding mOwnerMatchMutex.
-    // - Nothing outside netd iterates over it.
-    map.iterate(deleteAllEntries);
-    uint32_t mapSettingKey = UID_MAP_ENABLED;
-    uint8_t defaultMapState = 0;
-    return map.writeValue(mapSettingKey, defaultMapState, BPF_NOEXIST);
-}
+TrafficController::TrafficController(uint32_t perUidLimit, uint32_t totalLimit)
+    : mBpfLevel(getBpfSupportLevel()),
+      mPerUidStatsEntriesLimit(perUidLimit),
+      mTotalUidStatsEntriesLimit(totalLimit) {}
 
 Status TrafficController::initMaps() {
-    std::lock_guard<std::mutex> ownerMapGuard(mOwnerMatchMutex);
-    RETURN_IF_NOT_OK(
-        mCookieTagMap.getOrCreate(COOKIE_UID_MAP_SIZE, COOKIE_TAG_MAP_PATH, BPF_MAP_TYPE_HASH));
-
+    std::lock_guard guard(mMutex);
+    RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
     RETURN_IF_NOT_OK(changeOwnerAndMode(COOKIE_TAG_MAP_PATH, AID_NET_BW_ACCT, "CookieTagMap",
                                         false));
 
-    RETURN_IF_NOT_OK(mUidCounterSetMap.getOrCreate(UID_COUNTERSET_MAP_SIZE, UID_COUNTERSET_MAP_PATH,
-                                                   BPF_MAP_TYPE_HASH));
+    RETURN_IF_NOT_OK(mUidCounterSetMap.init(UID_COUNTERSET_MAP_PATH));
     RETURN_IF_NOT_OK(changeOwnerAndMode(UID_COUNTERSET_MAP_PATH, AID_NET_BW_ACCT,
                                         "UidCounterSetMap", false));
 
-    RETURN_IF_NOT_OK(
-        mAppUidStatsMap.getOrCreate(UID_STATS_MAP_SIZE, APP_UID_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
+    RETURN_IF_NOT_OK(mAppUidStatsMap.init(APP_UID_STATS_MAP_PATH));
     RETURN_IF_NOT_OK(
         changeOwnerAndMode(APP_UID_STATS_MAP_PATH, AID_NET_BW_STATS, "AppUidStatsMap", false));
 
-    RETURN_IF_NOT_OK(
-        mUidStatsMap.getOrCreate(UID_STATS_MAP_SIZE, UID_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
-    RETURN_IF_NOT_OK(changeOwnerAndMode(UID_STATS_MAP_PATH, AID_NET_BW_STATS, "UidStatsMap",
-                                        false));
+    RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
+    RETURN_IF_NOT_OK(changeOwnerAndMode(STATS_MAP_A_PATH, AID_NET_BW_STATS, "StatsMapA", false));
 
-    RETURN_IF_NOT_OK(
-        mTagStatsMap.getOrCreate(TAG_STATS_MAP_SIZE, TAG_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
-    RETURN_IF_NOT_OK(changeOwnerAndMode(TAG_STATS_MAP_PATH, AID_NET_BW_STATS, "TagStatsMap",
-                                        false));
+    RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
+    RETURN_IF_NOT_OK(changeOwnerAndMode(STATS_MAP_B_PATH, AID_NET_BW_STATS, "StatsMapB", false));
 
-    RETURN_IF_NOT_OK(mIfaceIndexNameMap.getOrCreate(IFACE_INDEX_NAME_MAP_SIZE,
-                                                    IFACE_INDEX_NAME_MAP_PATH, BPF_MAP_TYPE_HASH));
+    RETURN_IF_NOT_OK(mIfaceIndexNameMap.init(IFACE_INDEX_NAME_MAP_PATH));
     RETURN_IF_NOT_OK(changeOwnerAndMode(IFACE_INDEX_NAME_MAP_PATH, AID_NET_BW_STATS,
                                         "IfaceIndexNameMap", false));
 
-    RETURN_IF_NOT_OK(
-        mDozableUidMap.getOrCreate(UID_OWNER_MAP_SIZE, DOZABLE_UID_MAP_PATH, BPF_MAP_TYPE_HASH));
-    RETURN_IF_NOT_OK(changeOwnerAndMode(DOZABLE_UID_MAP_PATH, AID_ROOT, "DozableUidMap", true));
-    RETURN_IF_NOT_OK(initialOwnerMap(mDozableUidMap));
-
-    RETURN_IF_NOT_OK(
-        mStandbyUidMap.getOrCreate(UID_OWNER_MAP_SIZE, STANDBY_UID_MAP_PATH, BPF_MAP_TYPE_HASH));
-    RETURN_IF_NOT_OK(changeOwnerAndMode(STANDBY_UID_MAP_PATH, AID_ROOT, "StandbyUidMap", true));
-    RETURN_IF_NOT_OK(initialOwnerMap(mStandbyUidMap));
-
-    RETURN_IF_NOT_OK(mPowerSaveUidMap.getOrCreate(UID_OWNER_MAP_SIZE, POWERSAVE_UID_MAP_PATH,
-                                                  BPF_MAP_TYPE_HASH));
-    RETURN_IF_NOT_OK(changeOwnerAndMode(POWERSAVE_UID_MAP_PATH, AID_ROOT, "PowerSaveUidMap", true));
-    RETURN_IF_NOT_OK(initialOwnerMap(mPowerSaveUidMap));
-
-    RETURN_IF_NOT_OK(
-        mIfaceStatsMap.getOrCreate(IFACE_STATS_MAP_SIZE, IFACE_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
+    RETURN_IF_NOT_OK(mIfaceStatsMap.init(IFACE_STATS_MAP_PATH));
     RETURN_IF_NOT_OK(changeOwnerAndMode(IFACE_STATS_MAP_PATH, AID_NET_BW_STATS, "IfaceStatsMap",
                                         false));
+
+    RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
+    RETURN_IF_NOT_OK(changeOwnerAndMode(CONFIGURATION_MAP_PATH, AID_NET_BW_STATS,
+                                        "ConfigurationMap", false));
+    RETURN_IF_NOT_OK(
+            mConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY, DEFAULT_CONFIG, BPF_ANY));
+    RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
+                                                  BPF_ANY));
+
+    RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
+    RETURN_IF_NOT_OK(changeOwnerAndMode(UID_OWNER_MAP_PATH, AID_ROOT, "UidOwnerMap", true));
+    RETURN_IF_NOT_OK(mUidOwnerMap.clear());
+    RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+    return netdutils::status::ok;
+}
+
+static Status attachProgramToCgroup(const char* programPath, const int cgroupFd,
+                                    bpf_attach_type type) {
+    unique_fd cgroupProg(bpfFdGet(programPath, 0));
+    if (cgroupProg == -1) {
+        int ret = errno;
+        ALOGE("Failed to get program from %s: %s", programPath, strerror(ret));
+        return statusFromErrno(ret, "cgroup program get failed");
+    }
+    if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+        int ret = errno;
+        ALOGE("Program from %s attach failed: %s", programPath, strerror(ret));
+        return statusFromErrno(ret, "program attach failed");
+    }
+    return netdutils::status::ok;
+}
+
+static Status initPrograms() {
+    std::string cg2_path;
+
+    if (!CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cg2_path)) {
+         int ret = errno;
+         ALOGE("Failed to find cgroup v2 root");
+         return statusFromErrno(ret, "Failed to find cgroup v2 root");
+    }
+
+    unique_fd cg_fd(open(cg2_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (cg_fd == -1) {
+        int ret = errno;
+        ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
+        return statusFromErrno(ret, "Open the cgroup directory failed");
+    }
+    RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
+    RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
+
+    // For the devices that support cgroup socket filter, the socket filter
+    // should be loaded successfully by bpfloader. So we attach the filter to
+    // cgroup if the program is pinned properly.
+    // TODO: delete the if statement once all devices should support cgroup
+    // socket filter (ie. the minimum kernel version required is 4.14).
+    if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+        RETURN_IF_NOT_OK(
+                attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+    }
     return netdutils::status::ok;
 }
 
 Status TrafficController::start() {
-
-    if (!ebpfSupported) {
+    if (mBpfLevel == BpfLevel::NONE) {
         return netdutils::status::ok;
     }
 
-    /* When netd restart from a crash without total system reboot, the program
+    /* When netd restarts from a crash without total system reboot, the program
      * is still attached to the cgroup, detach it so the program can be freed
      * and we can load and attach new program into the target cgroup.
      *
@@ -206,6 +298,8 @@
 
     RETURN_IF_NOT_OK(initMaps());
 
+    RETURN_IF_NOT_OK(initPrograms());
+
     // Fetch the list of currently-existing interfaces. At this point NetlinkHandler is
     // already running, so it will call addInterface() when any new interface appears.
     std::map<std::string, uint32_t> ifacePairs;
@@ -214,7 +308,6 @@
         addInterface(ifacePair.first.c_str(), ifacePair.second);
     }
 
-
     auto result = makeSkDestroyListener();
     if (!isOk(result)) {
         ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
@@ -223,15 +316,20 @@
     }
     // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
     const auto rxHandler = [this](const nlmsghdr&, const Slice msg) {
+        std::lock_guard guard(mMutex);
         inet_diag_msg diagmsg = {};
         if (extract(msg, diagmsg) < sizeof(inet_diag_msg)) {
-            ALOGE("unrecognized netlink message: %s", toString(msg).c_str());
+            ALOGE("Unrecognized netlink message: %s", toString(msg).c_str());
             return;
         }
         uint64_t sock_cookie = static_cast<uint64_t>(diagmsg.id.idiag_cookie[0]) |
                                (static_cast<uint64_t>(diagmsg.id.idiag_cookie[1]) << 32);
 
-        mCookieTagMap.deleteValue(sock_cookie);
+        Status s = mCookieTagMap.deleteValue(sock_cookie);
+        if (!isOk(s) && s.code() != ENOENT) {
+            ALOGE("Failed to delete cookie %" PRIx64 ": %s", sock_cookie, toString(s).c_str());
+            return;
+        }
     };
     expectOk(mSkDestroyListener->subscribe(kSockDiagMsgType, rxHandler));
 
@@ -244,45 +342,16 @@
     };
     expectOk(mSkDestroyListener->subscribe(kSockDiagDoneMsgType, rxDoneHandler));
 
-    int* status = nullptr;
-
-    std::vector<const char*> prog_args{
-        "/system/bin/bpfloader",
-    };
-    int ret = access(BPF_INGRESS_PROG_PATH, R_OK);
-    if (ret != 0 && errno == ENOENT) {
-        prog_args.push_back((char*)"-i");
-    }
-    ret = access(BPF_EGRESS_PROG_PATH, R_OK);
-    if (ret != 0 && errno == ENOENT) {
-        prog_args.push_back((char*)"-e");
-    }
-    ret = access(XT_BPF_INGRESS_PROG_PATH, R_OK);
-    if (ret != 0 && errno == ENOENT) {
-        prog_args.push_back((char*)"-p");
-    }
-    ret = access(XT_BPF_EGRESS_PROG_PATH, R_OK);
-    if (ret != 0 && errno == ENOENT) {
-        prog_args.push_back((char*)"-m");
-    }
-
-    if (prog_args.size() == 1) {
-        // all program are loaded already.
-        return netdutils::status::ok;
-    }
-
-    prog_args.push_back(nullptr);
-    ret = android_fork_execvp(prog_args.size(), (char**)prog_args.data(), status, false, true);
-    if (ret) {
-        ret = errno;
-        ALOGE("failed to execute %s: %s", prog_args[0], strerror(errno));
-        return statusFromErrno(ret, "run bpf loader failed");
-    }
     return netdutils::status::ok;
 }
 
-int TrafficController::tagSocket(int sockFd, uint32_t tag, uid_t uid) {
-    if (!ebpfSupported) {
+int TrafficController::tagSocket(int sockFd, uint32_t tag, uid_t uid, uid_t callingUid) {
+    std::lock_guard guard(mMutex);
+    if (uid != callingUid && !hasUpdateDeviceStatsPermission(callingUid)) {
+        return -EPERM;
+    }
+
+    if (mBpfLevel == BpfLevel::NONE) {
         if (legacy_tagSocket(sockFd, tag, uid)) return -errno;
         return 0;
     }
@@ -291,12 +360,54 @@
     if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
     UidTag newKey = {.uid = (uint32_t)uid, .tag = tag};
 
+    uint32_t totalEntryCount = 0;
+    uint32_t perUidEntryCount = 0;
+    // Now we go through the stats map and count how many entries are associated
+    // with target uid. If the uid entry hit the limit for each uid, we block
+    // the request to prevent the map from overflow. It is safe here to iterate
+    // over the map since when mMutex is hold, system server cannot toggle
+    // the live stats map and clean it. So nobody can delete entries from the map.
+    const auto countUidStatsEntries = [uid, &totalEntryCount, &perUidEntryCount](
+                                              const StatsKey& key, BpfMap<StatsKey, StatsValue>&) {
+        if (key.uid == uid) {
+            perUidEntryCount++;
+        }
+        totalEntryCount++;
+        return netdutils::status::ok;
+    };
+    auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+    if (!isOk(configuration.status())) {
+        ALOGE("Failed to get current configuration: %s, fd: %d",
+              strerror(configuration.status().code()), mConfigurationMap.getMap().get());
+        return -configuration.status().code();
+    }
+    if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+        ALOGE("unknown configuration value: %d", configuration.value());
+        return -EINVAL;
+    }
+
+    BpfMap<StatsKey, StatsValue>& currentMap =
+            (configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
+    Status res = currentMap.iterate(countUidStatsEntries);
+    if (!isOk(res)) {
+        ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
+              strerror(res.code()));
+        return -res.code();
+    }
+
+    if (totalEntryCount > mTotalUidStatsEntriesLimit ||
+        perUidEntryCount > mPerUidStatsEntriesLimit) {
+        ALOGE("Too many stats entries in the map, total count: %u, uid(%u) count: %u, blocking tag"
+              " request to prevent map overflow",
+              totalEntryCount, uid, perUidEntryCount);
+        return -EMFILE;
+    }
     // Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
     // flag so it will insert a new entry to the map if that value doesn't exist
     // yet. And update the tag if there is already a tag stored. Since the eBPF
     // program in kernel only read this map, and is protected by rcu read lock. It
     // should be fine to cocurrently update the map while eBPF program is running.
-    Status res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
+    res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
     if (!isOk(res)) {
         ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.code()),
               mCookieTagMap.getMap().get());
@@ -305,7 +416,8 @@
 }
 
 int TrafficController::untagSocket(int sockFd) {
-    if (!ebpfSupported) {
+    std::lock_guard guard(mMutex);
+    if (mBpfLevel == BpfLevel::NONE) {
         if (legacy_untagSocket(sockFd)) return -errno;
         return 0;
     }
@@ -319,10 +431,13 @@
     return -res.code();
 }
 
-int TrafficController::setCounterSet(int counterSetNum, uid_t uid) {
+int TrafficController::setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) {
     if (counterSetNum < 0 || counterSetNum >= OVERFLOW_COUNTERSET) return -EINVAL;
-    Status res;
-    if (!ebpfSupported) {
+
+    std::lock_guard guard(mMutex);
+    if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
+
+    if (mBpfLevel == BpfLevel::NONE) {
         if (legacy_setCounterSet(counterSetNum, uid)) return -errno;
         return 0;
     }
@@ -339,7 +454,7 @@
         }
     }
     uint8_t tmpCounterSetNum = (uint8_t)counterSetNum;
-    res = mUidCounterSetMap.writeValue(uid, tmpCounterSetNum, BPF_ANY);
+    Status res = mUidCounterSetMap.writeValue(uid, tmpCounterSetNum, BPF_ANY);
     if (!isOk(res)) {
         ALOGE("Failed to set the counterSet: %s, fd: %d", strerror(res.code()),
               mUidCounterSetMap.getMap().get());
@@ -348,9 +463,14 @@
     return 0;
 }
 
-int TrafficController::deleteTagData(uint32_t tag, uid_t uid) {
+// This method only get called by system_server when an app get uinstalled, it
+// is called inside removeUidsLocked() while holding mStatsLock. So it is safe
+// to iterate and modify the stats maps.
+int TrafficController::deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) {
+    std::lock_guard guard(mMutex);
+    if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
 
-    if (!ebpfSupported) {
+    if (mBpfLevel == BpfLevel::NONE) {
         if (legacy_deleteTagData(tag, uid)) return -errno;
         return 0;
     }
@@ -369,7 +489,7 @@
         // Move forward to next cookie in the map.
         return netdutils::status::ok;
     };
-    mCookieTagMap.iterateWithValue(deleteMatchedCookieEntries);
+    mCookieTagMap.iterateWithValue(deleteMatchedCookieEntries).ignoreError();
     // Now we go through the Tag stats map and delete the data entry with correct uid and tag
     // combination. Or all tag stats under that uid if the target tag is 0.
     const auto deleteMatchedUidTagEntries = [uid, tag](const StatsKey& key,
@@ -385,7 +505,8 @@
         }
         return netdutils::status::ok;
     };
-    mTagStatsMap.iterate(deleteMatchedUidTagEntries);
+    mStatsMapB.iterate(deleteMatchedUidTagEntries).ignoreError();
+    mStatsMapA.iterate(deleteMatchedUidTagEntries).ignoreError();
     // If the tag is not zero, we already deleted all the data entry required. If tag is 0, we also
     // need to delete the stats stored in uidStatsMap and counterSet map.
     if (tag != 0) return 0;
@@ -395,7 +516,6 @@
         ALOGE("Failed to delete counterSet data(uid=%u, tag=%u): %s\n", uid, tag,
               strerror(res.code()));
     }
-    mUidStatsMap.iterate(deleteMatchedUidTagEntries);
 
     auto deleteAppUidStatsEntry = [uid](const uint32_t& key, BpfMap<uint32_t, StatsValue>& map) {
         if (key == uid) {
@@ -407,12 +527,12 @@
         }
         return netdutils::status::ok;
     };
-    mAppUidStatsMap.iterate(deleteAppUidStatsEntry);
+    mAppUidStatsMap.iterate(deleteAppUidStatsEntry).ignoreError();
     return 0;
 }
 
 int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
-    if (!ebpfSupported) return 0;
+    if (mBpfLevel == BpfLevel::NONE) return 0;
 
     IfaceValue iface;
     if (ifaceIndex == 0) {
@@ -429,41 +549,113 @@
     return 0;
 }
 
-Status TrafficController::updateOwnerMapEntry(BpfMap<uint32_t, uint8_t>& map, uid_t uid,
-                                              FirewallRule rule, FirewallType type) {
-    if (uid == UID_MAP_ENABLED) {
-        return statusFromErrno(-EINVAL, "This uid is reserved for map state");
-    }
-
+Status TrafficController::updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
+                                              FirewallType type) {
+    std::lock_guard guard(mMutex);
     if ((rule == ALLOW && type == WHITELIST) || (rule == DENY && type == BLACKLIST)) {
-        uint8_t flag = (type == WHITELIST) ? BPF_PASS : BPF_DROP;
-        RETURN_IF_NOT_OK(map.writeValue(uid, flag, BPF_ANY));
+        RETURN_IF_NOT_OK(addRule(mUidOwnerMap, uid, match));
     } else if ((rule == ALLOW && type == BLACKLIST) || (rule == DENY && type == WHITELIST)) {
-        RETURN_IF_NOT_OK(map.deleteValue(uid));
+        RETURN_IF_NOT_OK(removeRule(mUidOwnerMap, uid, match));
     } else {
         //Cannot happen.
-        return statusFromErrno(-EINVAL, "");
+        return statusFromErrno(EINVAL, "");
+    }
+    return netdutils::status::ok;
+}
+
+UidOwnerMatchType TrafficController::jumpOpToMatch(BandwidthController::IptJumpOp jumpHandling) {
+    switch (jumpHandling) {
+        case BandwidthController::IptJumpReject:
+            return PENALTY_BOX_MATCH;
+        case BandwidthController::IptJumpReturn:
+            return HAPPY_BOX_MATCH;
+        case BandwidthController::IptJumpNoAdd:
+            return NO_MATCH;
+    }
+}
+
+Status TrafficController::removeRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                                     UidOwnerMatchType match) {
+    auto oldMatch = map.readValue(uid);
+    if (isOk(oldMatch)) {
+        UidOwnerValue newMatch = {.rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+                                  .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif};
+        if (newMatch.rule == 0) {
+            RETURN_IF_NOT_OK(map.deleteValue(uid));
+        } else {
+            RETURN_IF_NOT_OK(map.writeValue(uid, newMatch, BPF_ANY));
+        }
+    } else {
+        return statusFromErrno(ENOENT, StringPrintf("uid: %u does not exist in map", uid));
+    }
+    return netdutils::status::ok;
+}
+
+Status TrafficController::addRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                                  UidOwnerMatchType match, uint32_t iif) {
+    // iif should be non-zero if and only if match == MATCH_IIF
+    if (match == IIF_MATCH && iif == 0) {
+        return statusFromErrno(EINVAL, "Interface match must have nonzero interface index");
+    } else if (match != IIF_MATCH && iif != 0) {
+        return statusFromErrno(EINVAL, "Non-interface match must have zero interface index");
+    }
+    auto oldMatch = map.readValue(uid);
+    if (isOk(oldMatch)) {
+        UidOwnerValue newMatch = {.rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+                                  .iif = iif ? iif : oldMatch.value().iif};
+        RETURN_IF_NOT_OK(map.writeValue(uid, newMatch, BPF_ANY));
+    } else {
+        UidOwnerValue newMatch = {.rule = static_cast<uint8_t>(match), .iif = iif};
+        RETURN_IF_NOT_OK(map.writeValue(uid, newMatch, BPF_ANY));
+    }
+    return netdutils::status::ok;
+}
+
+Status TrafficController::updateUidOwnerMap(const std::vector<std::string>& appStrUids,
+                                            BandwidthController::IptJumpOp jumpHandling,
+                                            BandwidthController::IptOp op) {
+    std::lock_guard guard(mMutex);
+    UidOwnerMatchType match = jumpOpToMatch(jumpHandling);
+    if (match == NO_MATCH) {
+        return statusFromErrno(
+                EINVAL, StringPrintf("invalid IptJumpOp: %d, command: %d", jumpHandling, match));
+    }
+    for (const auto& appStrUid : appStrUids) {
+        char* endPtr;
+        long uid = strtol(appStrUid.c_str(), &endPtr, 10);
+        if ((errno == ERANGE && (uid == LONG_MAX || uid == LONG_MIN)) ||
+            (endPtr == appStrUid.c_str()) || (*endPtr != '\0')) {
+               return statusFromErrno(errno, "invalid uid string:" + appStrUid);
+        }
+
+        if (op == BandwidthController::IptOpDelete) {
+            RETURN_IF_NOT_OK(removeRule(mUidOwnerMap, uid, match));
+        } else if (op == BandwidthController::IptOpInsert) {
+            RETURN_IF_NOT_OK(addRule(mUidOwnerMap, uid, match));
+        } else {
+            // Cannot happen.
+            return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, match));
+        }
     }
     return netdutils::status::ok;
 }
 
 int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallRule rule,
                                           FirewallType type) {
-    std::lock_guard<std::mutex> guard(mOwnerMatchMutex);
-    if (!ebpfSupported) {
+    if (mBpfLevel == BpfLevel::NONE) {
         ALOGE("bpf is not set up, should use iptables rule");
         return -ENOSYS;
     }
     Status res;
     switch (chain) {
         case DOZABLE:
-            res = updateOwnerMapEntry(mDozableUidMap, uid, rule, type);
+            res = updateOwnerMapEntry(DOZABLE_MATCH, uid, rule, type);
             break;
         case STANDBY:
-            res = updateOwnerMapEntry(mStandbyUidMap, uid, rule, type);
+            res = updateOwnerMapEntry(STANDBY_MATCH, uid, rule, type);
             break;
         case POWERSAVE:
-            res = updateOwnerMapEntry(mPowerSaveUidMap, uid, rule, type);
+            res = updateOwnerMapEntry(POWERSAVE_MATCH, uid, rule, type);
             break;
         case NONE:
         default:
@@ -477,33 +669,68 @@
     return 0;
 }
 
-Status TrafficController::replaceUidsInMap(BpfMap<uint32_t, uint8_t>& map,
-                                           const std::vector<int32_t>& uids, FirewallRule rule,
-                                           FirewallType type) {
+Status TrafficController::replaceRulesInMap(const UidOwnerMatchType match,
+                                            const std::vector<int32_t>& uids) {
+    std::lock_guard guard(mMutex);
     std::set<int32_t> uidSet(uids.begin(), uids.end());
     std::vector<uint32_t> uidsToDelete;
     auto getUidsToDelete = [&uidsToDelete, &uidSet](const uint32_t& key,
-                                                    const BpfMap<uint32_t, uint8_t>&) {
-        if (key != UID_MAP_ENABLED && uidSet.find((int32_t)key) == uidSet.end()) {
+                                                    const BpfMap<uint32_t, UidOwnerValue>&) {
+        if (uidSet.find((int32_t) key) == uidSet.end()) {
             uidsToDelete.push_back(key);
         }
         return netdutils::status::ok;
     };
-    RETURN_IF_NOT_OK(map.iterate(getUidsToDelete));
+    RETURN_IF_NOT_OK(mUidOwnerMap.iterate(getUidsToDelete));
 
     for(auto uid : uidsToDelete) {
-        RETURN_IF_NOT_OK(map.deleteValue(uid));
+        RETURN_IF_NOT_OK(removeRule(mUidOwnerMap, uid, match));
     }
 
     for (auto uid : uids) {
-        RETURN_IF_NOT_OK(updateOwnerMapEntry(map, uid, rule, type));
+        RETURN_IF_NOT_OK(addRule(mUidOwnerMap, uid, match));
+    }
+    return netdutils::status::ok;
+}
+
+Status TrafficController::addUidInterfaceRules(const int iif,
+                                               const std::vector<int32_t>& uidsToAdd) {
+    if (mBpfLevel == BpfLevel::NONE) {
+        ALOGW("UID ingress interface filtering not possible without BPF owner match");
+        return statusFromErrno(EOPNOTSUPP, "eBPF not supported");
+    }
+    if (!iif) {
+        return statusFromErrno(EINVAL, "Interface rule must specify interface");
+    }
+    std::lock_guard guard(mMutex);
+
+    for (auto uid : uidsToAdd) {
+        netdutils::Status result = addRule(mUidOwnerMap, uid, IIF_MATCH, iif);
+        if (!isOk(result)) {
+            ALOGW("addRule failed(%d): uid=%d iif=%d", result.code(), uid, iif);
+        }
+    }
+    return netdutils::status::ok;
+}
+
+Status TrafficController::removeUidInterfaceRules(const std::vector<int32_t>& uidsToDelete) {
+    if (mBpfLevel == BpfLevel::NONE) {
+        ALOGW("UID ingress interface filtering not possible without BPF owner match");
+        return statusFromErrno(EOPNOTSUPP, "eBPF not supported");
+    }
+    std::lock_guard guard(mMutex);
+
+    for (auto uid : uidsToDelete) {
+        netdutils::Status result = removeRule(mUidOwnerMap, uid, IIF_MATCH);
+        if (!isOk(result)) {
+            ALOGW("removeRule failed(%d): uid=%d", result.code(), uid);
+        }
     }
     return netdutils::status::ok;
 }
 
 int TrafficController::replaceUidOwnerMap(const std::string& name, bool isWhitelist,
                                           const std::vector<int32_t>& uids) {
-    std::lock_guard<std::mutex> guard(mOwnerMatchMutex);
     FirewallRule rule;
     FirewallType type;
     if (isWhitelist) {
@@ -515,11 +742,11 @@
     }
     Status res;
     if (!name.compare(FirewallController::LOCAL_DOZABLE)) {
-        res = replaceUidsInMap(mDozableUidMap, uids, rule, type);
+        res = replaceRulesInMap(DOZABLE_MATCH, uids);
     } else if (!name.compare(FirewallController::LOCAL_STANDBY)) {
-        res = replaceUidsInMap(mStandbyUidMap, uids, rule, type);
+        res = replaceRulesInMap(STANDBY_MATCH, uids);
     } else if (!name.compare(FirewallController::LOCAL_POWERSAVE)) {
-        res = replaceUidsInMap(mPowerSaveUidMap, uids, rule, type);
+        res = replaceRulesInMap(POWERSAVE_MATCH, uids);
     } else {
         ALOGE("unknown chain name: %s", name.c_str());
         return -EINVAL;
@@ -532,31 +759,131 @@
 }
 
 int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
-    std::lock_guard<std::mutex> guard(mOwnerMatchMutex);
-    uint32_t keyUid = UID_MAP_ENABLED;
-    uint8_t mapState = enable ? 1 : 0;
+    std::lock_guard guard(mMutex);
+    uint32_t key = UID_RULES_CONFIGURATION_KEY;
+    auto oldConfiguration = mConfigurationMap.readValue(key);
+    if (!isOk(oldConfiguration)) {
+        ALOGE("Cannot read the old configuration from map: %s",
+              oldConfiguration.status().msg().c_str());
+        return -oldConfiguration.status().code();
+    }
     Status res;
+    BpfConfig newConfiguration;
+    uint8_t match;
     switch (chain) {
         case DOZABLE:
-            res = mDozableUidMap.writeValue(keyUid, mapState, BPF_EXIST);
+            match = DOZABLE_MATCH;
             break;
         case STANDBY:
-            res = mStandbyUidMap.writeValue(keyUid, mapState, BPF_EXIST);
+            match = STANDBY_MATCH;
             break;
         case POWERSAVE:
-            res = mPowerSaveUidMap.writeValue(keyUid, mapState, BPF_EXIST);
+            match = POWERSAVE_MATCH;
             break;
         default:
             return -EINVAL;
     }
+    newConfiguration =
+            enable ? (oldConfiguration.value() | match) : (oldConfiguration.value() & (~match));
+    res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
     if (!isOk(res)) {
         ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
     }
     return -res.code();
 }
 
-bool TrafficController::checkBpfStatsEnable() {
-    return ebpfSupported;
+BpfLevel TrafficController::getBpfLevel() {
+    return mBpfLevel;
+}
+
+Status TrafficController::swapActiveStatsMap() {
+    std::lock_guard guard(mMutex);
+
+    if (mBpfLevel == BpfLevel::NONE) {
+        return statusFromErrno(EOPNOTSUPP, "This device doesn't have eBPF support");
+    }
+
+    uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+    auto oldConfiguration = mConfigurationMap.readValue(key);
+    if (!isOk(oldConfiguration)) {
+        ALOGE("Cannot read the old configuration from map: %s",
+              oldConfiguration.status().msg().c_str());
+        return oldConfiguration.status();
+    }
+
+    // Write to the configuration map to inform the kernel eBPF program to switch
+    // from using one map to the other. Use flag BPF_EXIST here since the map should
+    // be already populated in initMaps.
+    uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
+    Status res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure,
+                                              BPF_EXIST);
+    if (!isOk(res)) {
+        ALOGE("Failed to toggle the stats map: %s", strerror(res.code()));
+        return res;
+    }
+    // After changing the config, we need to make sure all the current running
+    // eBPF programs are finished and all the CPUs are aware of this config change
+    // before we modify the old map. So we do a special hack here to wait for
+    // the kernel to do a synchronize_rcu(). Once the kernel called
+    // synchronize_rcu(), the config we just updated will be available to all cores
+    // and the next eBPF programs triggered inside the kernel will use the new
+    // map configuration. So once this function returns we can safely modify the
+    // old stats map without concerning about race between the kernel and
+    // userspace.
+    int ret = synchronizeKernelRCU();
+    if (ret) {
+        ALOGE("map swap synchronize_rcu() ended with failure: %s", strerror(-ret));
+        return statusFromErrno(-ret, "map swap synchronize_rcu() failed");
+    }
+    return netdutils::status::ok;
+}
+
+void TrafficController::setPermissionForUids(int permission, const std::vector<uid_t>& uids) {
+    std::lock_guard guard(mMutex);
+    if (permission == INetd::PERMISSION_UNINSTALLED) {
+        for (uid_t uid : uids) {
+            // Clean up all permission information for the related uid if all the
+            // packages related to it are uninstalled.
+            mPrivilegedUser.erase(uid);
+            if (mBpfLevel > BpfLevel::NONE) {
+                Status ret = mUidPermissionMap.deleteValue(uid);
+                if (!isOk(ret) && ret.code() != ENONET) {
+                    ALOGE("Failed to clean up the permission for %u: %s", uid,
+                          strerror(ret.code()));
+                }
+            }
+        }
+        return;
+    }
+
+    bool privileged = (permission & INetd::PERMISSION_UPDATE_DEVICE_STATS);
+
+    for (uid_t uid : uids) {
+        if (privileged) {
+            mPrivilegedUser.insert(uid);
+        } else {
+            mPrivilegedUser.erase(uid);
+        }
+
+        // Skip the bpf map operation if not supported.
+        if (mBpfLevel == BpfLevel::NONE) {
+            continue;
+        }
+        // The map stores all the permissions that the UID has, except if the only permission
+        // the UID has is the INTERNET permission, then the UID should not appear in the map.
+        if (permission != INetd::PERMISSION_INTERNET) {
+            Status ret = mUidPermissionMap.writeValue(uid, permission, BPF_ANY);
+            if (!isOk(ret)) {
+                ALOGE("Failed to set permission: %s of uid(%u) to permission map: %s",
+                      UidPermissionTypeToString(permission).c_str(), uid, strerror(ret.code()));
+            }
+        } else {
+            Status ret = mUidPermissionMap.deleteValue(uid);
+            if (!isOk(ret) && ret.code() != ENOENT) {
+                ALOGE("Failed to remove uid %u from permission map: %s", uid, strerror(ret.code()));
+            }
+        }
+    }
 }
 
 std::string getProgramStatus(const char *path) {
@@ -580,26 +907,28 @@
     return StringPrintf("OK");
 }
 
-void dumpBpfMap(std::string mapName, DumpWriter& dw, const std::string& header) {
+// NOLINTNEXTLINE(google-runtime-references): grandfathered pass by non-const reference
+void dumpBpfMap(const std::string& mapName, DumpWriter& dw, const std::string& header) {
     dw.blankline();
     dw.println("%s:", mapName.c_str());
-    if(!header.empty()) {
-        dw.println(header.c_str());
+    if (!header.empty()) {
+        dw.println(header);
     }
 }
 
 const String16 TrafficController::DUMP_KEYWORD = String16("trafficcontroller");
 
 void TrafficController::dump(DumpWriter& dw, bool verbose) {
-    std::lock_guard<std::mutex> ownerMapGuard(mOwnerMatchMutex);
-    dw.incIndent();
+    std::lock_guard guard(mMutex);
+    ScopedIndent indentTop(dw);
     dw.println("TrafficController");
 
-    dw.incIndent();
-    dw.println("BPF module status: %s", ebpfSupported? "ON" : "OFF");
+    ScopedIndent indentPreBpfModule(dw);
+    dw.println("BPF module status: %s", BpfLevelToString(mBpfLevel).c_str());
 
-    if (!ebpfSupported)
+    if (mBpfLevel == BpfLevel::NONE) {
         return;
+    }
 
     dw.blankline();
     dw.println("mCookieTagMap status: %s",
@@ -608,20 +937,18 @@
                getMapStatus(mUidCounterSetMap.getMap(), UID_COUNTERSET_MAP_PATH).c_str());
     dw.println("mAppUidStatsMap status: %s",
                getMapStatus(mAppUidStatsMap.getMap(), APP_UID_STATS_MAP_PATH).c_str());
-    dw.println("mUidStatsMap status: %s",
-               getMapStatus(mUidStatsMap.getMap(), UID_STATS_MAP_PATH).c_str());
-    dw.println("mTagStatsMap status: %s",
-               getMapStatus(mTagStatsMap.getMap(), TAG_STATS_MAP_PATH).c_str());
+    dw.println("mStatsMapA status: %s",
+               getMapStatus(mStatsMapA.getMap(), STATS_MAP_A_PATH).c_str());
+    dw.println("mStatsMapB status: %s",
+               getMapStatus(mStatsMapB.getMap(), STATS_MAP_B_PATH).c_str());
     dw.println("mIfaceIndexNameMap status: %s",
                getMapStatus(mIfaceIndexNameMap.getMap(), IFACE_INDEX_NAME_MAP_PATH).c_str());
     dw.println("mIfaceStatsMap status: %s",
                getMapStatus(mIfaceStatsMap.getMap(), IFACE_STATS_MAP_PATH).c_str());
-    dw.println("mDozableUidMap status: %s",
-               getMapStatus(mDozableUidMap.getMap(), DOZABLE_UID_MAP_PATH).c_str());
-    dw.println("mStandbyUidMap status: %s",
-               getMapStatus(mStandbyUidMap.getMap(), STANDBY_UID_MAP_PATH).c_str());
-    dw.println("mPowerSaveUidMap status: %s",
-               getMapStatus(mPowerSaveUidMap.getMap(), POWERSAVE_UID_MAP_PATH).c_str());
+    dw.println("mConfigurationMap status: %s",
+               getMapStatus(mConfigurationMap.getMap(), CONFIGURATION_MAP_PATH).c_str());
+    dw.println("mUidOwnerMap status: %s",
+               getMapStatus(mUidOwnerMap.getMap(), UID_OWNER_MAP_PATH).c_str());
 
     dw.blankline();
     dw.println("Cgroup ingress program status: %s",
@@ -631,13 +958,19 @@
                getProgramStatus(XT_BPF_INGRESS_PROG_PATH).c_str());
     dw.println("xt_bpf egress program status: %s",
                getProgramStatus(XT_BPF_EGRESS_PROG_PATH).c_str());
+    dw.println("xt_bpf bandwidth whitelist program status: %s",
+               getProgramStatus(XT_BPF_WHITELIST_PROG_PATH).c_str());
+    dw.println("xt_bpf bandwidth blacklist program status: %s",
+               getProgramStatus(XT_BPF_BLACKLIST_PROG_PATH).c_str());
 
-    if(!verbose) return;
+    if (!verbose) {
+        return;
+    }
 
     dw.blankline();
     dw.println("BPF map content:");
 
-    dw.incIndent();
+    ScopedIndent indentForMapContent(dw);
 
     // Print CookieTagMap content.
     dumpBpfMap("mCookieTagMap", dw, "");
@@ -680,7 +1013,7 @@
     // Print uidStatsMap content
     std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes"
                                            " rxPackets txBytes txPackets");
-    dumpBpfMap("mUidStatsMap", dw, statsHeader);
+    dumpBpfMap("mStatsMapA", dw, statsHeader);
     const auto printStatsInfo = [&dw, this](const StatsKey& key, const StatsValue& value,
                                             const BpfMap<StatsKey, StatsValue>&) {
         uint32_t ifIndex = key.ifaceIndex;
@@ -693,16 +1026,16 @@
                    value.rxPackets, value.txBytes, value.txPackets);
         return netdutils::status::ok;
     };
-    res = mUidStatsMap.iterateWithValue(printStatsInfo);
+    res = mStatsMapA.iterateWithValue(printStatsInfo);
     if (!isOk(res)) {
-        dw.println("mUidStatsMap print end with error: %s", res.msg().c_str());
+        dw.println("mStatsMapA print end with error: %s", res.msg().c_str());
     }
 
     // Print TagStatsMap content.
-    dumpBpfMap("mTagStatsMap", dw, statsHeader);
-    res = mTagStatsMap.iterateWithValue(printStatsInfo);
+    dumpBpfMap("mStatsMapB", dw, statsHeader);
+    res = mStatsMapB.iterateWithValue(printStatsInfo);
     if (!isOk(res)) {
-        dw.println("mTagStatsMap print end with error: %s", res.msg().c_str());
+        dw.println("mStatsMapB print end with error: %s", res.msg().c_str());
     }
 
     // Print ifaceIndexToNameMap content.
@@ -737,31 +1070,59 @@
         dw.println("mIfaceStatsMap print end with error: %s", res.msg().c_str());
     }
 
-    // Print owner match uid maps
-    dumpBpfMap("mDozableUidMap", dw, "");
-    res = mDozableUidMap.iterateWithValue(printUidInfo);
+    dw.blankline();
+
+    uint32_t key = UID_RULES_CONFIGURATION_KEY;
+    auto configuration = mConfigurationMap.readValue(key);
+    if (isOk(configuration)) {
+        dw.println("current ownerMatch configuration: %d", configuration.value());
+    } else {
+        dw.println("mConfigurationMap read ownerMatch configure failed with error: %s",
+                   configuration.status().msg().c_str());
+    }
+    key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+    configuration = mConfigurationMap.readValue(key);
+    if (isOk(configuration)) {
+        dw.println("current statsMap configuration: %d", configuration.value());
+    } else {
+        dw.println("mConfigurationMap read stats map configure failed with error: %s",
+                   configuration.status().msg().c_str());
+    }
+    dumpBpfMap("mUidOwnerMap", dw, "");
+    const auto printUidMatchInfo = [&dw, this](const uint32_t& key, const UidOwnerValue& value,
+                                               const BpfMap<uint32_t, UidOwnerValue>&) {
+        if (value.rule & IIF_MATCH) {
+            auto ifname = mIfaceIndexNameMap.readValue(value.iif);
+            if (isOk(ifname)) {
+                dw.println("%u %s %s", key, uidMatchTypeToString(value.rule).c_str(),
+                           ifname.value().name);
+            } else {
+                dw.println("%u %s %u", key, uidMatchTypeToString(value.rule).c_str(), value.iif);
+            }
+        } else {
+            dw.println("%u %s", key, uidMatchTypeToString(value.rule).c_str());
+        }
+        return netdutils::status::ok;
+    };
+    res = mUidOwnerMap.iterateWithValue(printUidMatchInfo);
     if (!isOk(res)) {
-        dw.println("mDozableUidMap print end with error: %s", res.msg().c_str());
+        dw.println("mUidOwnerMap print end with error: %s", res.msg().c_str());
+    }
+    dumpBpfMap("mUidPermissionMap", dw, "");
+    const auto printUidPermissionInfo = [&dw](const uint32_t& key, const uint8_t& value,
+                                              const BpfMap<uint32_t, uint8_t>&) {
+        dw.println("%u %s", key, UidPermissionTypeToString(value).c_str());
+        return netdutils::status::ok;
+    };
+    res = mUidPermissionMap.iterateWithValue(printUidPermissionInfo);
+    if (!isOk(res)) {
+        dw.println("mUidPermissionMap print end with error: %s", res.msg().c_str());
     }
 
-    dumpBpfMap("mStandbyUidMap", dw, "");
-    res = mStandbyUidMap.iterateWithValue(printUidInfo);
-    if (!isOk(res)) {
-        dw.println("mDozableUidMap print end with error: %s", res.msg().c_str());
+    dumpBpfMap("mPrivilegedUser", dw, "");
+    for (uid_t uid : mPrivilegedUser) {
+        dw.println("%u ALLOW_UPDATE_DEVICE_STATS", (uint32_t)uid);
     }
-
-    dumpBpfMap("mPowerSaveUidMap", dw, "");
-    res = mPowerSaveUidMap.iterateWithValue(printUidInfo);
-    if (!isOk(res)) {
-        dw.println("mDozableUidMap print end with error: %s", res.msg().c_str());
-    }
-
-    dw.decIndent();
-
-    dw.decIndent();
-
-    dw.decIndent();
-
 }
 
 }  // namespace net
diff --git a/server/TrafficController.h b/server/TrafficController.h
index 79f7d14..7ec0279 100644
--- a/server/TrafficController.h
+++ b/server/TrafficController.h
@@ -19,13 +19,17 @@
 
 #include <linux/bpf.h>
 
-#include <netdutils/StatusOr.h>
+#include "BandwidthController.h"
 #include "FirewallController.h"
 #include "NetlinkListener.h"
 #include "Network.h"
 #include "android-base/thread_annotations.h"
 #include "android-base/unique_fd.h"
 #include "bpf/BpfMap.h"
+#include "netdbpf/bpf_shared.h"
+#include "netdutils/DumpWriter.h"
+#include "netdutils/StatusOr.h"
+#include "utils/String16.h"
 
 using android::bpf::BpfMap;
 using android::bpf::IfaceValue;
@@ -36,8 +40,6 @@
 namespace android {
 namespace net {
 
-class DumpWriter;
-
 class TrafficController {
   public:
     TrafficController();
@@ -55,7 +57,7 @@
      * the spinlock initialized with the map. So the behavior of two modules
      * should be the same. No additional lock needed.
      */
-    int tagSocket(int sockFd, uint32_t tag, uid_t uid);
+    int tagSocket(int sockFd, uint32_t tag, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
 
     /*
      * The untag process is similiar to tag socket and both old qtaguid module and
@@ -67,7 +69,7 @@
     /*
      * Similiar as above, no external lock required.
      */
-    int setCounterSet(int counterSetNum, uid_t uid);
+    int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
 
     /*
      * When deleting a tag data, the qtaguid module will grab the spinlock of each
@@ -77,13 +79,18 @@
      * each map one by one. And deleting processes are also protected by the
      * spinlock of the map. So no additional lock is required.
      */
-    int deleteTagData(uint32_t tag, uid_t uid);
+    int deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
 
     /*
      * Check if the current device have the bpf traffic stats accounting service
      * running.
      */
-    bool checkBpfStatsEnable();
+    bpf::BpfLevel getBpfLevel();
+
+    /*
+     * Swap the stats map config from current active stats map to the idle one.
+     */
+    netdutils::Status swapActiveStatsMap() EXCLUDES(mMutex);
 
     /*
      * Add the interface name and index pair into the eBPF map.
@@ -97,18 +104,28 @@
     int replaceUidOwnerMap(const std::string& name, bool isWhitelist,
                            const std::vector<int32_t>& uids);
 
-    netdutils::Status updateOwnerMapEntry(BpfMap<uint32_t, uint8_t>& map, uid_t uid,
-                                          FirewallRule rule, FirewallType type);
+    netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
+                                          FirewallType type) EXCLUDES(mMutex);
 
-    void dump(DumpWriter& dw, bool verbose);
+    void dump(netdutils::DumpWriter& dw, bool verbose) EXCLUDES(mMutex);
 
-    netdutils::Status replaceUidsInMap(BpfMap<uint32_t, uint8_t>& map,
-                                       const std::vector<int32_t>& uids, FirewallRule rule,
-                                       FirewallType type);
+    netdutils::Status replaceRulesInMap(UidOwnerMatchType match, const std::vector<int32_t>& uids)
+            EXCLUDES(mMutex);
 
+    netdutils::Status addUidInterfaceRules(const int ifIndex, const std::vector<int32_t>& uids)
+            EXCLUDES(mMutex);
+    netdutils::Status removeUidInterfaceRules(const std::vector<int32_t>& uids) EXCLUDES(mMutex);
+
+    netdutils::Status updateUidOwnerMap(const std::vector<std::string>& appStrUids,
+                                        BandwidthController::IptJumpOp jumpHandling,
+                                        BandwidthController::IptOp op) EXCLUDES(mMutex);
     static const String16 DUMP_KEYWORD;
 
-    int toggleUidOwnerMap(ChildChain chain, bool enable);
+    int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
+
+    static netdutils::StatusOr<std::unique_ptr<NetlinkListenerInterface>> makeSkDestroyListener();
+
+    void setPermissionForUids(int permission, const std::vector<uid_t>& uids) EXCLUDES(mMutex);
 
   private:
     /*
@@ -121,7 +138,7 @@
      * Map Key: uint64_t socket cookie
      * Map Value: struct UidTag, contains a uint32 uid and a uint32 tag.
      */
-    BpfMap<uint64_t, UidTag> mCookieTagMap;
+    BpfMap<uint64_t, UidTag> mCookieTagMap GUARDED_BY(mMutex);
 
     /*
      * mUidCounterSetMap: Store the counterSet of a specific uid.
@@ -129,7 +146,7 @@
      * Map Value: uint32 counterSet specifies if the traffic is a background
      * or foreground traffic.
      */
-    BpfMap<uint32_t, uint8_t> mUidCounterSetMap;
+    BpfMap<uint32_t, uint8_t> mUidCounterSetMap GUARDED_BY(mMutex);
 
     /*
      * mAppUidStatsMap: Store the total traffic stats for a uid regardless of
@@ -139,27 +156,17 @@
     BpfMap<uint32_t, StatsValue> mAppUidStatsMap;
 
     /*
-     * mUidStatsMap: Store the traffic statistics for a specific combination of
-     * uid, iface and counterSet. We maintain this map in addition to
-     * mTagStatsMap because we want to be able to track per-UID data usage even
-     * if mTagStatsMap is full.
-     * Map Key: Struct StatsKey contains the uid, counterSet and ifaceIndex
-     * information. The Tag in the StatsKey should always be 0.
+     * mStatsMapA/mStatsMapB: Store the traffic statistics for a specific
+     * combination of uid, tag, iface and counterSet. These two maps contain
+     * both tagged and untagged traffic.
+     * Map Key: Struct StatsKey contains the uid, tag, counterSet and ifaceIndex
+     * information.
      * Map Value: struct Stats, contains packet count and byte count of each
      * transport protocol on egress and ingress direction.
      */
-    BpfMap<StatsKey, StatsValue> mUidStatsMap;
+    BpfMap<StatsKey, StatsValue> mStatsMapA GUARDED_BY(mMutex);
 
-    /*
-     * mTagStatsMap: Store the traffic statistics for a specific combination of
-     * uid, tag, iface and counterSet. Only tagged socket stats should be stored
-     * in this map.
-     * Map Key: Struct StatsKey contains the uid, counterSet and ifaceIndex
-     * information. The tag field should not be 0.
-     * Map Value: struct Stats, contains packet count and byte count of each
-     * transport protocol on egress and ingress direction.
-     */
-    BpfMap<StatsKey, StatsValue> mTagStatsMap;
+    BpfMap<StatsKey, StatsValue> mStatsMapB GUARDED_BY(mMutex);
 
     /*
      * mIfaceIndexNameMap: Store the index name pair of each interface show up
@@ -175,33 +182,81 @@
     BpfMap<uint32_t, StatsValue> mIfaceStatsMap;
 
     /*
-     * mDozableUidMap: Store uids that have related rules in dozable mode owner match
-     * chain.
+     * mConfigurationMap: Store the current network policy about uid filtering
+     * and the current stats map in use. There are two configuration entries in
+     * the map right now:
+     * - Entry with UID_RULES_CONFIGURATION_KEY:
+     *    Store the configuration for the current uid rules. It indicates the device
+     *    is in doze/powersave/standby mode.
+     * - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
+     *    Stores the current live stats map that kernel program is writing to.
+     *    Userspace can do scraping and cleaning job on the other one depending on the
+     *    current configs.
      */
-    BpfMap<uint32_t, uint8_t> mDozableUidMap GUARDED_BY(mOwnerMatchMutex);
+    BpfMap<uint32_t, uint8_t> mConfigurationMap GUARDED_BY(mMutex);
 
     /*
-     * mStandbyUidMap: Store uids that have related rules in standby mode owner match
-     * chain.
+     * mUidOwnerMap: Store uids that are used for bandwidth control uid match.
      */
-    BpfMap<uint32_t, uint8_t> mStandbyUidMap GUARDED_BY(mOwnerMatchMutex);
+    BpfMap<uint32_t, UidOwnerValue> mUidOwnerMap GUARDED_BY(mMutex);
 
     /*
-     * mPowerSaveUidMap: Store uids that have related rules in power save mode owner match
-     * chain.
+     * mUidOwnerMap: Store uids that are used for INTERNET permission check.
      */
-    BpfMap<uint32_t, uint8_t> mPowerSaveUidMap GUARDED_BY(mOwnerMatchMutex);
+    BpfMap<uint32_t, uint8_t> mUidPermissionMap GUARDED_BY(mMutex);
 
     std::unique_ptr<NetlinkListenerInterface> mSkDestroyListener;
 
-    bool ebpfSupported;
+    netdutils::Status removeRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                                 UidOwnerMatchType match) REQUIRES(mMutex);
 
-    std::mutex mOwnerMatchMutex;
+    netdutils::Status addRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                              UidOwnerMatchType match, uint32_t iif = 0) REQUIRES(mMutex);
+
+    bpf::BpfLevel mBpfLevel;
+
+    // mMutex guards all accesses to mConfigurationMap, mUidOwnerMap, mUidPermissionMap,
+    // mStatsMapA, mStatsMapB and mPrivilegedUser. It is designed to solve the following
+    // problems:
+    // 1. Prevent concurrent access and modification to mConfigurationMap, mUidOwnerMap,
+    //    mUidPermissionMap, and mPrivilegedUser. These data members are controlled by netd but can
+    //    be modified from different threads. TrafficController provides several APIs directly
+    //    called by the binder RPC, and different binder threads can concurrently access these data
+    //    members mentioned above. Some of the data members such as mUidPermissionMap and
+    //    mPrivilegedUsers are also accessed from a different thread when tagging sockets or
+    //    setting the counterSet through FwmarkServer
+    // 2. Coordinate the deletion of uid stats in mStatsMapA and mStatsMapB. The system server
+    //    always call into netd to ask for a live stats map change before it pull and clean up the
+    //    stats from the inactive map. The mMutex will block netd from accessing the stats map when
+    //    the mConfigurationMap is updating the current stats map so netd will not accidentally
+    //    read the map that system_server is cleaning up.
+    std::mutex mMutex;
+
+    // The limit on the number of stats entries a uid can have in the per uid stats map.
+    // TrafficController will block that specific uid from tagging new sockets after the limit is
+    // reached.
+    const uint32_t mPerUidStatsEntriesLimit;
+
+    // The limit on the total number of stats entries in the per uid stats map. TrafficController
+    // will block all tagging requests after the limit is reached.
+    const uint32_t mTotalUidStatsEntriesLimit;
 
     netdutils::Status loadAndAttachProgram(bpf_attach_type type, const char* path, const char* name,
                                            base::unique_fd& cg_fd);
 
-    netdutils::Status initMaps();
+    netdutils::Status initMaps() EXCLUDES(mMutex);
+
+    // Keep track of uids that have permission UPDATE_DEVICE_STATS so we don't
+    // need to call back to system server for permission check.
+    std::set<uid_t> mPrivilegedUser GUARDED_BY(mMutex);
+
+    UidOwnerMatchType jumpOpToMatch(BandwidthController::IptJumpOp jumpHandling);
+
+    bool hasUpdateDeviceStatsPermission(uid_t uid) REQUIRES(mMutex);
+
+    // For testing
+    TrafficController(uint32_t perUidLimit, uint32_t totalLimit);
+
     // For testing
     friend class TrafficControllerTest;
 };
diff --git a/server/TrafficControllerTest.cpp b/server/TrafficControllerTest.cpp
index a354f83..c97c104 100644
--- a/server/TrafficControllerTest.cpp
+++ b/server/TrafficControllerTest.cpp
@@ -33,106 +33,110 @@
 #include <android-base/strings.h>
 
 #include <netdutils/MockSyscalls.h>
-#include "netdutils/Status.h"
 #include "netdutils/StatusOr.h"
 
 #include "FirewallController.h"
 #include "TrafficController.h"
 #include "bpf/BpfUtils.h"
 
-using namespace android::bpf;
-
-using ::testing::_;
-using ::testing::ByMove;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::StrictMock;
-using ::testing::Test;
+using namespace android::bpf;  // NOLINT(google-build-using-namespace): grandfathered
 
 namespace android {
 namespace net {
 
-using base::unique_fd;
 using netdutils::isOk;
-using netdutils::Status;
-using netdutils::status::ok;
 using netdutils::StatusOr;
 
 constexpr int TEST_MAP_SIZE = 10;
+constexpr int TEST_COOKIE = 1;
 constexpr uid_t TEST_UID = 10086;
 constexpr uid_t TEST_UID2 = 54321;
 constexpr uid_t TEST_UID3 = 98765;
 constexpr uint32_t TEST_TAG = 42;
 constexpr uint32_t TEST_COUNTERSET = 1;
 constexpr uint32_t DEFAULT_COUNTERSET = 0;
+constexpr uint32_t TEST_PER_UID_STATS_ENTRIES_LIMIT = 3;
+constexpr uint32_t TEST_TOTAL_UID_STATS_ENTRIES_LIMIT = 7;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
 
 class TrafficControllerTest : public ::testing::Test {
   protected:
-    TrafficControllerTest() {}
+    TrafficControllerTest()
+        : mTc(TEST_PER_UID_STATS_ENTRIES_LIMIT, TEST_TOTAL_UID_STATS_ENTRIES_LIMIT) {}
     TrafficController mTc;
     BpfMap<uint64_t, UidTag> mFakeCookieTagMap;
     BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
     BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
-    BpfMap<StatsKey, StatsValue> mFakeUidStatsMap;
-    BpfMap<StatsKey, StatsValue> mFakeTagStatsMap;
-    BpfMap<uint32_t, uint8_t> mFakeDozableUidMap;
-    BpfMap<uint32_t, uint8_t> mFakeStandbyUidMap;
-    BpfMap<uint32_t, uint8_t> mFakePowerSaveUidMap;
+    BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+    BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+    BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
+    BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
 
     void SetUp() {
-        std::lock_guard<std::mutex> ownerGuard(mTc.mOwnerMatchMutex);
+        std::lock_guard guard(mTc.mMutex);
         SKIP_IF_BPF_NOT_SUPPORTED;
+        ASSERT_EQ(0, setrlimitForTest());
 
         mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t),
                                           sizeof(struct UidTag), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeCookieTagMap.getMap());
+        ASSERT_VALID(mFakeCookieTagMap);
 
         mFakeUidCounterSetMap.reset(
             createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeUidCounterSetMap.getMap());
+        ASSERT_VALID(mFakeUidCounterSetMap);
 
         mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t),
                                             sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
+        ASSERT_VALID(mFakeAppUidStatsMap);
 
-        mFakeUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(struct StatsKey),
-                                         sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeUidStatsMap.getMap());
+        mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(struct StatsKey),
+                                       sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeStatsMapA);
 
-        mFakeTagStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(struct StatsKey),
-                                         sizeof(struct StatsValue), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeTagStatsMap.getMap());
+        mFakeConfigurationMap.reset(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+        ASSERT_VALID(mFakeConfigurationMap);
 
-        mFakeDozableUidMap.reset(
-            createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeDozableUidMap.getMap());
+        mFakeUidOwnerMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(UidOwnerValue),
+                                         TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeUidOwnerMap);
+        mFakeUidPermissionMap.reset(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeUidPermissionMap);
 
-        mFakeStandbyUidMap.reset(
-            createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakeStandbyUidMap.getMap());
+        mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+        ASSERT_VALID(mTc.mCookieTagMap);
+        mTc.mUidCounterSetMap.reset(dupFd(mFakeUidCounterSetMap.getMap()));
+        ASSERT_VALID(mTc.mUidCounterSetMap);
+        mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
+        ASSERT_VALID(mTc.mAppUidStatsMap);
+        mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+        ASSERT_VALID(mTc.mStatsMapA);
+        mTc.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+        ASSERT_VALID(mTc.mConfigurationMap);
 
-        mFakePowerSaveUidMap.reset(
-            createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
-        ASSERT_LE(0, mFakePowerSaveUidMap.getMap());
-        // Make sure trafficController use the eBPF code path.
-        mTc.ebpfSupported = true;
-
-        mTc.mCookieTagMap.reset(mFakeCookieTagMap.getMap());
-        mTc.mUidCounterSetMap.reset(mFakeUidCounterSetMap.getMap());
-        mTc.mAppUidStatsMap.reset(mFakeAppUidStatsMap.getMap());
-        mTc.mUidStatsMap.reset(mFakeUidStatsMap.getMap());
-        mTc.mTagStatsMap.reset(mFakeTagStatsMap.getMap());
-        mTc.mDozableUidMap.reset(mFakeDozableUidMap.getMap());
-        mTc.mStandbyUidMap.reset(mFakeStandbyUidMap.getMap());
-        mTc.mPowerSaveUidMap.reset(mFakePowerSaveUidMap.getMap());
+        // Always write to stats map A by default.
+        ASSERT_TRUE(isOk(mTc.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+                                                          SELECT_MAP_A, BPF_ANY)));
+        mTc.mUidOwnerMap.reset(dupFd(mFakeUidOwnerMap.getMap()));
+        ASSERT_VALID(mTc.mUidOwnerMap);
+        mTc.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+        ASSERT_VALID(mTc.mUidPermissionMap);
+        mTc.mPrivilegedUser.clear();
     }
 
-    int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid) {
-        int sock = socket(protocol, SOCK_STREAM, 0);
+    int dupFd(const android::base::unique_fd& mapFd) {
+        return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+    }
+
+    int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
+                          uid_t callingUid) {
+        int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
         EXPECT_LE(0, sock);
         *cookie = getSocketCookie(sock);
         EXPECT_NE(NONEXISTENT_COOKIE, *cookie);
-        EXPECT_EQ(0, mTc.tagSocket(sock, tag, uid));
+        EXPECT_EQ(0, mTc.tagSocket(sock, tag, uid, callingUid));
         return sock;
     }
 
@@ -145,8 +149,6 @@
 
     void expectNoTag(uint64_t cookie) { EXPECT_FALSE(isOk(mFakeCookieTagMap.readValue(cookie))); }
 
-    void expectTagMapEmpty() { EXPECT_FALSE(isOk(mFakeCookieTagMap.getFirstKey())); }
-
     void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
         UidTag cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
         EXPECT_TRUE(isOk(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY)));
@@ -154,82 +156,162 @@
         StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
         uint8_t counterSet = TEST_COUNTERSET;
         EXPECT_TRUE(isOk(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY)));
-        EXPECT_TRUE(isOk(mFakeTagStatsMap.writeValue(*key, statsMapValue, BPF_ANY)));
+        EXPECT_TRUE(isOk(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY)));
         key->tag = 0;
-        EXPECT_TRUE(isOk(mFakeUidStatsMap.writeValue(*key, statsMapValue, BPF_ANY)));
+        EXPECT_TRUE(isOk(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY)));
         EXPECT_TRUE(isOk(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY)));
         // put tag information back to statsKey
         key->tag = tag;
     }
 
-    void checkUidOwnerRuleForChain(ChildChain chain, BpfMap<uint32_t, uint8_t>& targetMap) {
+    void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
         uint32_t uid = TEST_UID;
         EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, BLACKLIST));
-        StatusOr<uint8_t> value = targetMap.readValue(uid);
+        StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_TRUE(isOk(value));
-        EXPECT_EQ(BPF_DROP, value.value());
+        EXPECT_TRUE(value.value().rule & match);
 
         uid = TEST_UID2;
         EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, WHITELIST));
-        value = targetMap.readValue(uid);
+        value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_TRUE(isOk(value));
-        EXPECT_EQ(BPF_PASS, value.value());
+        EXPECT_TRUE(value.value().rule & match);
 
         EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, WHITELIST));
-        value = targetMap.readValue(uid);
+        value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_FALSE(isOk(value));
         EXPECT_EQ(ENOENT, value.status().code());
 
         uid = TEST_UID;
         EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, BLACKLIST));
-        value = targetMap.readValue(uid);
+        value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_FALSE(isOk(value));
         EXPECT_EQ(ENOENT, value.status().code());
 
         uid = TEST_UID3;
         EXPECT_EQ(-ENOENT, mTc.changeUidOwnerRule(chain, uid, ALLOW, BLACKLIST));
-        value = targetMap.readValue(uid);
+        value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_FALSE(isOk(value));
         EXPECT_EQ(ENOENT, value.status().code());
     }
 
-    void checkEachUidValue(const std::vector<int32_t>& uids, const uint8_t expectValue,
-                           BpfMap<uint32_t, uint8_t>& targetMap) {
+    void checkEachUidValue(const std::vector<int32_t>& uids, UidOwnerMatchType match) {
         for (uint32_t uid : uids) {
-            StatusOr<uint8_t> value = targetMap.readValue(uid);
+            StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
             EXPECT_TRUE(isOk(value));
-            EXPECT_EQ(expectValue, value.value());
+            EXPECT_TRUE(value.value().rule & match);
         }
         std::set<uint32_t> uidSet(uids.begin(), uids.end());
         const auto checkNoOtherUid = [&uidSet](const int32_t& key,
-                                               const BpfMap<uint32_t, uint8_t>&) {
+                                               const BpfMap<uint32_t, UidOwnerValue>&) {
             EXPECT_NE(uidSet.end(), uidSet.find(key));
             return netdutils::status::ok;
         };
-        EXPECT_TRUE(isOk(targetMap.iterate(checkNoOtherUid)));
+        EXPECT_TRUE(isOk(mFakeUidOwnerMap.iterate(checkNoOtherUid)));
     }
 
     void checkUidMapReplace(const std::string& name, const std::vector<int32_t>& uids,
-                            BpfMap<uint32_t, uint8_t>& targetMap) {
+                            UidOwnerMatchType match) {
         bool isWhitelist = true;
         EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isWhitelist, uids));
-        checkEachUidValue(uids, BPF_PASS, targetMap);
+        checkEachUidValue(uids, match);
 
         isWhitelist = false;
         EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isWhitelist, uids));
-        checkEachUidValue(uids, BPF_DROP, targetMap);
+        checkEachUidValue(uids, match);
+    }
+    void expectUidOwnerMapValues(const std::vector<std::string>& appStrUids, uint8_t expectedRule,
+                                 uint32_t expectedIif) {
+        for (const std::string& strUid : appStrUids) {
+            uint32_t uid = stoi(strUid);
+            StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
+            EXPECT_TRUE(isOk(value));
+            EXPECT_EQ(expectedRule, value.value().rule)
+                    << "Expected rule for UID " << uid << " to be " << expectedRule << ", but was "
+                    << value.value().rule;
+            EXPECT_EQ(expectedIif, value.value().iif)
+                    << "Expected iif for UID " << uid << " to be " << expectedIif << ", but was "
+                    << value.value().iif;
+        }
     }
 
-    void TearDown() {
-        std::lock_guard<std::mutex> ownerGuard(mTc.mOwnerMatchMutex);
-        mFakeCookieTagMap.reset();
-        mFakeUidCounterSetMap.reset();
-        mFakeAppUidStatsMap.reset();
-        mFakeUidStatsMap.reset();
-        mFakeTagStatsMap.reset();
-        mTc.mDozableUidMap.reset();
-        mTc.mStandbyUidMap.reset();
-        mTc.mPowerSaveUidMap.reset();
+    template <class Key, class Value>
+    void expectMapEmpty(BpfMap<Key, Value>& map) {
+        auto isEmpty = map.isEmpty();
+        EXPECT_TRUE(isOk(isEmpty));
+        EXPECT_TRUE(isEmpty.value());
+    }
+
+    void expectUidPermissionMapValues(const std::vector<uid_t>& appUids, uint8_t expectedValue) {
+        for (uid_t uid : appUids) {
+            StatusOr<uint8_t> value = mFakeUidPermissionMap.readValue(uid);
+            EXPECT_TRUE(isOk(value));
+            EXPECT_EQ(expectedValue, value.value())
+                    << "Expected value for UID " << uid << " to be " << expectedValue
+                    << ", but was " << value.value();
+        }
+    }
+
+    void expectPrivilegedUserSet(const std::vector<uid_t>& appUids) {
+        std::lock_guard guard(mTc.mMutex);
+        EXPECT_EQ(appUids.size(), mTc.mPrivilegedUser.size());
+        for (uid_t uid : appUids) {
+            EXPECT_NE(mTc.mPrivilegedUser.end(), mTc.mPrivilegedUser.find(uid));
+        }
+    }
+
+    void expectPrivilegedUserSetEmpty() {
+        std::lock_guard guard(mTc.mMutex);
+        EXPECT_TRUE(mTc.mPrivilegedUser.empty());
+    }
+
+    void addPrivilegedUid(uid_t uid) {
+        std::vector privilegedUid = {uid};
+        mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, privilegedUid);
+    }
+
+    void removePrivilegedUid(uid_t uid) {
+        std::vector privilegedUid = {uid};
+        mTc.setPermissionForUids(INetd::PERMISSION_NONE, privilegedUid);
+    }
+
+    void expectFakeStatsUnchanged(uint64_t cookie, uint32_t tag, uint32_t uid,
+                                  StatsKey tagStatsMapKey) {
+        StatusOr<UidTag> cookieMapResult = mFakeCookieTagMap.readValue(cookie);
+        EXPECT_TRUE(isOk(cookieMapResult));
+        EXPECT_EQ(uid, cookieMapResult.value().uid);
+        EXPECT_EQ(tag, cookieMapResult.value().tag);
+        StatusOr<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
+        EXPECT_TRUE(isOk(counterSetResult));
+        EXPECT_EQ(TEST_COUNTERSET, counterSetResult.value());
+        StatusOr<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
+        EXPECT_TRUE(isOk(statsMapResult));
+        EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+        EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+        tagStatsMapKey.tag = 0;
+        statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
+        EXPECT_TRUE(isOk(statsMapResult));
+        EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+        EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+        auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
+        EXPECT_TRUE(isOk(appStatsResult));
+        EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
+        EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+    }
+
+    void expectTagSocketReachLimit(uint32_t tag, uint32_t uid) {
+        int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        EXPECT_LE(0, sock);
+        if (sock < 0) return;
+        uint64_t sockCookie = getSocketCookie(sock);
+        EXPECT_NE(NONEXISTENT_COOKIE, sockCookie);
+        EXPECT_EQ(-EMFILE, mTc.tagSocket(sock, tag, uid, uid));
+        expectNoTag(sockCookie);
+
+        // Delete stats entries then tag socket success
+        EXPECT_EQ(0, mTc.deleteTagData(0, uid, 0));
+        EXPECT_EQ(0, mTc.tagSocket(sock, tag, uid, uid));
+        expectUidTag(sockCookie, uid, tag);
     }
 };
 
@@ -237,20 +319,20 @@
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     uint64_t sockCookie;
-    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID);
+    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie, TEST_UID, TEST_TAG);
     ASSERT_EQ(0, mTc.untagSocket(v4socket));
     expectNoTag(sockCookie);
-    expectTagMapEmpty();
+    expectMapEmpty(mFakeCookieTagMap);
 }
 
 TEST_F(TrafficControllerTest, TestReTagSocket) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     uint64_t sockCookie;
-    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID);
+    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie, TEST_UID, TEST_TAG);
-    ASSERT_EQ(0, mTc.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1));
+    ASSERT_EQ(0, mTc.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1, TEST_UID + 1));
     expectUidTag(sockCookie, TEST_UID + 1, TEST_TAG + 1);
 }
 
@@ -259,8 +341,8 @@
 
     uint64_t sockCookie1;
     uint64_t sockCookie2;
-    int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID);
-    setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID);
+    int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
+    setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie1, TEST_UID, TEST_TAG);
     expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
     ASSERT_EQ(0, mTc.untagSocket(v4socket1));
@@ -273,19 +355,48 @@
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     uint64_t sockCookie;
-    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID);
+    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie, TEST_UID, TEST_TAG);
     ASSERT_EQ(0, mTc.untagSocket(v6socket));
     expectNoTag(sockCookie);
-    expectTagMapEmpty();
+    expectMapEmpty(mFakeCookieTagMap);
 }
 
 TEST_F(TrafficControllerTest, TestTagInvalidSocket) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     int invalidSocket = -1;
-    ASSERT_GT(0, mTc.tagSocket(invalidSocket, TEST_TAG, TEST_UID));
-    expectTagMapEmpty();
+    ASSERT_GT(0, mTc.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(TrafficControllerTest, TestTagSocketWithoutPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_NE(-1, sock);
+    ASSERT_EQ(-EPERM, mTc.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(TrafficControllerTest, TestTagSocketWithPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    // Grant permission to calling uid.
+    std::vector<uid_t> callingUid = {TEST_UID2};
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, callingUid);
+
+    // Tag a socket to a different uid other then callingUid.
+    uint64_t sockCookie;
+    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID2);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    EXPECT_EQ(0, mTc.untagSocket(v6socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+
+    // Clean up the permission
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, callingUid);
+    expectPrivilegedUserSetEmpty();
 }
 
 TEST_F(TrafficControllerTest, TestUntagInvalidSocket) {
@@ -293,34 +404,73 @@
 
     int invalidSocket = -1;
     ASSERT_GT(0, mTc.untagSocket(invalidSocket));
-    int v4socket = socket(AF_INET, SOCK_STREAM, 0);
+    int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_GT(0, mTc.untagSocket(v4socket));
-    expectTagMapEmpty();
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(TrafficControllerTest, TestTagSocketReachLimitFail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    uid_t uid = TEST_UID;
+    StatsKey tagStatsMapKey[4];
+    for (int i = 0; i < 3; i++) {
+        uint64_t cookie = TEST_COOKIE + i;
+        uint32_t tag = TEST_TAG + i;
+        populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+    }
+    expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+TEST_F(TrafficControllerTest, TestTagSocketReachTotalLimitFail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    StatsKey tagStatsMapKey[4];
+    for (int i = 0; i < 4; i++) {
+        uint64_t cookie = TEST_COOKIE + i;
+        uint32_t tag = TEST_TAG + i;
+        uid_t uid = TEST_UID + i;
+        populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+    }
+    expectTagSocketReachLimit(TEST_TAG, TEST_UID);
 }
 
 TEST_F(TrafficControllerTest, TestSetCounterSet) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
-    ASSERT_EQ(0, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID));
+    uid_t callingUid = TEST_UID2;
+    addPrivilegedUid(callingUid);
+    ASSERT_EQ(0, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, callingUid));
     uid_t uid = TEST_UID;
     StatusOr<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
     ASSERT_TRUE(isOk(counterSetResult));
     ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_EQ(0, mTc.setCounterSet(DEFAULT_COUNTERSET, TEST_UID));
+    ASSERT_EQ(0, mTc.setCounterSet(DEFAULT_COUNTERSET, TEST_UID, callingUid));
     ASSERT_FALSE(isOk(mFakeUidCounterSetMap.readValue(uid)));
-    ASSERT_FALSE(isOk(mFakeUidCounterSetMap.getFirstKey()));
+    expectMapEmpty(mFakeUidCounterSetMap);
+}
+
+TEST_F(TrafficControllerTest, TestSetCounterSetWithoutPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    ASSERT_EQ(-EPERM, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, TEST_UID2));
+    uid_t uid = TEST_UID;
+    ASSERT_FALSE(isOk(mFakeUidCounterSetMap.readValue(uid)));
+    expectMapEmpty(mFakeUidCounterSetMap);
 }
 
 TEST_F(TrafficControllerTest, TestSetInvalidCounterSet) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
-    ASSERT_GT(0, mTc.setCounterSet(OVERFLOW_COUNTERSET, TEST_UID));
+    uid_t callingUid = TEST_UID2;
+    addPrivilegedUid(callingUid);
+    ASSERT_GT(0, mTc.setCounterSet(OVERFLOW_COUNTERSET, TEST_UID, callingUid));
     uid_t uid = TEST_UID;
     ASSERT_FALSE(isOk(mFakeUidCounterSetMap.readValue(uid)));
-    ASSERT_FALSE(isOk(mFakeUidCounterSetMap.getFirstKey()));
+    expectMapEmpty(mFakeUidCounterSetMap);
 }
 
-TEST_F(TrafficControllerTest, TestDeleteTagData) {
+TEST_F(TrafficControllerTest, TestDeleteTagDataWithoutPermission) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     uint64_t cookie = 1;
@@ -328,14 +478,29 @@
     uint32_t tag = TEST_TAG;
     StatsKey tagStatsMapKey;
     populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID));
+    ASSERT_EQ(-EPERM, mTc.deleteTagData(0, TEST_UID, TEST_UID2));
+
+    expectFakeStatsUnchanged(cookie, tag, uid, tagStatsMapKey);
+}
+
+TEST_F(TrafficControllerTest, TestDeleteTagData) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    uid_t callingUid = TEST_UID2;
+    addPrivilegedUid(callingUid);
+    uint64_t cookie = 1;
+    uid_t uid = TEST_UID;
+    uint32_t tag = TEST_TAG;
+    StatsKey tagStatsMapKey;
+    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
+    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
     ASSERT_FALSE(isOk(mFakeCookieTagMap.readValue(cookie)));
     StatusOr<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
     ASSERT_TRUE(isOk(counterSetResult));
     ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(isOk(mFakeTagStatsMap.readValue(tagStatsMapKey)));
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey)));
     tagStatsMapKey.tag = 0;
-    StatusOr<StatsValue> statsMapResult = mFakeUidStatsMap.readValue(tagStatsMapKey);
+    StatusOr<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
     ASSERT_TRUE(isOk(statsMapResult));
     ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
     ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
@@ -348,23 +513,27 @@
 TEST_F(TrafficControllerTest, TestDeleteAllUidData) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
+    uid_t callingUid = TEST_UID2;
+    addPrivilegedUid(callingUid);
     uint64_t cookie = 1;
     uid_t uid = TEST_UID;
     uint32_t tag = TEST_TAG;
     StatsKey tagStatsMapKey;
     populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(0, mTc.deleteTagData(0, TEST_UID));
+    ASSERT_EQ(0, mTc.deleteTagData(0, TEST_UID, callingUid));
     ASSERT_FALSE(isOk(mFakeCookieTagMap.readValue(cookie)));
     ASSERT_FALSE(isOk(mFakeUidCounterSetMap.readValue(uid)));
-    ASSERT_FALSE(isOk(mFakeTagStatsMap.readValue(tagStatsMapKey)));
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey)));
     tagStatsMapKey.tag = 0;
-    ASSERT_FALSE(isOk(mFakeUidStatsMap.readValue(tagStatsMapKey)));
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey)));
     ASSERT_FALSE(isOk(mFakeAppUidStatsMap.readValue(TEST_UID)));
 }
 
 TEST_F(TrafficControllerTest, TestDeleteDataWithTwoTags) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
+    uid_t callingUid = TEST_UID2;
+    addPrivilegedUid(callingUid);
     uint64_t cookie1 = 1;
     uint64_t cookie2 = 2;
     uid_t uid = TEST_UID;
@@ -374,7 +543,7 @@
     StatsKey tagStatsMapKey2;
     populateFakeStats(cookie1, uid, tag1, &tagStatsMapKey1);
     populateFakeStats(cookie2, uid, tag2, &tagStatsMapKey2);
-    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID));
+    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
     ASSERT_FALSE(isOk(mFakeCookieTagMap.readValue(cookie1)));
     StatusOr<UidTag> cookieMapResult = mFakeCookieTagMap.readValue(cookie2);
     ASSERT_TRUE(isOk(cookieMapResult));
@@ -383,8 +552,8 @@
     StatusOr<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
     ASSERT_TRUE(isOk(counterSetResult));
     ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(isOk(mFakeTagStatsMap.readValue(tagStatsMapKey1)));
-    StatusOr<StatsValue> statsMapResult = mFakeTagStatsMap.readValue(tagStatsMapKey2);
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey1)));
+    StatusOr<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey2);
     ASSERT_TRUE(isOk(statsMapResult));
     ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
     ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
@@ -393,6 +562,8 @@
 TEST_F(TrafficControllerTest, TestDeleteDataWithTwoUids) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
+    uid_t callingUid = TEST_UID2;
+    addPrivilegedUid(callingUid);
     uint64_t cookie1 = 1;
     uint64_t cookie2 = 2;
     uid_t uid1 = TEST_UID;
@@ -405,18 +576,18 @@
 
     // Delete the stats of one of the uid. Check if it is properly collected by
     // removedStats.
-    ASSERT_EQ(0, mTc.deleteTagData(0, uid2));
+    ASSERT_EQ(0, mTc.deleteTagData(0, uid2, callingUid));
     ASSERT_FALSE(isOk(mFakeCookieTagMap.readValue(cookie2)));
     StatusOr<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid1);
     ASSERT_TRUE(isOk(counterSetResult));
     ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
     ASSERT_FALSE(isOk(mFakeUidCounterSetMap.readValue(uid2)));
-    ASSERT_FALSE(isOk(mFakeTagStatsMap.readValue(tagStatsMapKey2)));
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey2)));
     tagStatsMapKey2.tag = 0;
-    ASSERT_FALSE(isOk(mFakeUidStatsMap.readValue(tagStatsMapKey2)));
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey2)));
     ASSERT_FALSE(isOk(mFakeAppUidStatsMap.readValue(uid2)));
     tagStatsMapKey1.tag = 0;
-    StatusOr<StatsValue> statsMapResult = mFakeUidStatsMap.readValue(tagStatsMapKey1);
+    StatusOr<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey1);
     ASSERT_TRUE(isOk(statsMapResult));
     ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
     ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
@@ -426,8 +597,8 @@
     ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
 
     // Delete the stats of the other uid.
-    ASSERT_EQ(0, mTc.deleteTagData(0, uid1));
-    ASSERT_FALSE(isOk(mFakeUidStatsMap.readValue(tagStatsMapKey1)));
+    ASSERT_EQ(0, mTc.deleteTagData(0, uid1, callingUid));
+    ASSERT_FALSE(isOk(mFakeStatsMapA.readValue(tagStatsMapKey1)));
     ASSERT_FALSE(isOk(mFakeAppUidStatsMap.readValue(uid1)));
 }
 
@@ -435,35 +606,35 @@
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     uint32_t uid = TEST_UID;
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(mFakeDozableUidMap, uid, DENY, BLACKLIST)));
-    StatusOr<uint8_t> value = mFakeDozableUidMap.readValue(uid);
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, BLACKLIST)));
+    StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
     ASSERT_TRUE(isOk(value));
-    ASSERT_EQ(BPF_DROP, value.value());
+    ASSERT_TRUE(value.value().rule & STANDBY_MATCH);
+
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, ALLOW, WHITELIST)));
+    value = mFakeUidOwnerMap.readValue(uid);
+    ASSERT_TRUE(isOk(value));
+    ASSERT_TRUE(value.value().rule & DOZABLE_MATCH);
+
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, DENY, WHITELIST)));
+    value = mFakeUidOwnerMap.readValue(uid);
+    ASSERT_TRUE(isOk(value));
+    ASSERT_FALSE(value.value().rule & DOZABLE_MATCH);
+
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, BLACKLIST)));
+    ASSERT_FALSE(isOk(mFakeUidOwnerMap.readValue(uid)));
 
     uid = TEST_UID2;
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(mFakeDozableUidMap, uid, ALLOW, WHITELIST)));
-    value = mFakeDozableUidMap.readValue(uid);
-    ASSERT_TRUE(isOk(value));
-    ASSERT_EQ(BPF_PASS, value.value());
-
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(mFakeDozableUidMap, uid, DENY, WHITELIST)));
-    ASSERT_FALSE(isOk(mFakeDozableUidMap.readValue(uid)));
-
-    uid = TEST_UID;
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(mFakeDozableUidMap, uid, ALLOW, BLACKLIST)));
-    ASSERT_FALSE(isOk(mFakeDozableUidMap.readValue(uid)));
-
-    uid = TEST_UID3;
-    ASSERT_FALSE(isOk(mTc.updateOwnerMapEntry(mFakeDozableUidMap, uid, ALLOW, BLACKLIST)));
-    ASSERT_FALSE(isOk(mFakeDozableUidMap.readValue(uid)));
+    ASSERT_FALSE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, BLACKLIST)));
+    ASSERT_FALSE(isOk(mFakeUidOwnerMap.readValue(uid)));
 }
 
 TEST_F(TrafficControllerTest, TestChangeUidOwnerRule) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
-    checkUidOwnerRuleForChain(DOZABLE, mFakeDozableUidMap);
-    checkUidOwnerRuleForChain(STANDBY, mFakeStandbyUidMap);
-    checkUidOwnerRuleForChain(POWERSAVE, mFakePowerSaveUidMap);
+    checkUidOwnerRuleForChain(DOZABLE, DOZABLE_MATCH);
+    checkUidOwnerRuleForChain(STANDBY, STANDBY_MATCH);
+    checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
     ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, WHITELIST));
     ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, WHITELIST));
 }
@@ -472,11 +643,312 @@
     SKIP_IF_BPF_NOT_SUPPORTED;
 
     std::vector<int32_t> uids = {TEST_UID, TEST_UID2, TEST_UID3};
-    checkUidMapReplace("fw_dozable", uids, mFakeDozableUidMap);
-    checkUidMapReplace("fw_standby", uids, mFakeStandbyUidMap);
-    checkUidMapReplace("fw_powersave", uids, mFakePowerSaveUidMap);
+    checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH);
+    checkUidMapReplace("fw_standby", uids, STANDBY_MATCH);
+    checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
     ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
 }
 
+TEST_F(TrafficControllerTest, TestReplaceSameChain) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<int32_t> uids = {TEST_UID, TEST_UID2, TEST_UID3};
+    checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH);
+    std::vector<int32_t> newUids = {TEST_UID2, TEST_UID3};
+    checkUidMapReplace("fw_dozable", newUids, DOZABLE_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestBlacklistUidMatch) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
+    expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestWhitelistUidMatch) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpDelete)));
+    expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestReplaceMatchUid) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    // Add appStrUids to the blacklist and expect that their values are all PENALTY_BOX_MATCH.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
+
+    // Add the same UIDs to the whitelist and expect that we get PENALTY_BOX_MATCH |
+    // HAPPY_BOX_MATCH.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0);
+
+    // Remove the same UIDs from the whitelist and check the PENALTY_BOX_MATCH is still there.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpDelete)));
+    expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
+
+    // Remove the same UIDs from the blacklist and check the map is empty.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
+    ASSERT_FALSE(isOk(mFakeUidOwnerMap.getFirstKey()));
+}
+
+TEST_F(TrafficControllerTest, TestDeleteWrongMatchSilentlyFails) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    // If the uid does not exist in the map, trying to delete a rule about it will fail.
+    ASSERT_FALSE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                            BandwidthController::IptOpDelete)));
+    expectMapEmpty(mFakeUidOwnerMap);
+
+    // Add blacklist rules for appStrUids.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
+
+    // Delete (non-existent) blacklist rules for appStrUids, and check that this silently does
+    // nothing if the uid is in the map but does not have blacklist match. This is required because
+    // NetworkManagementService will try to remove a uid from blacklist after adding it to the
+    // whitelist and if the remove fails it will not update the uid status.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
+}
+
+TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRules) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int iif0 = 15;
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
+    expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+
+    // Add some non-overlapping new uids. They should coexist with existing rules
+    int iif1 = 16;
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
+    expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2000", "2001"}, IIF_MATCH, iif1);
+
+    // Overwrite some existing uids
+    int iif2 = 17;
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif2, {1000, 2000})));
+    expectUidOwnerMapValues({"1001"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2001"}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000", "2000"}, IIF_MATCH, iif2);
+}
+
+TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRules) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int iif0 = 15;
+    int iif1 = 16;
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
+    expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2000", "2001"}, IIF_MATCH, iif1);
+
+    // Rmove some uids
+    ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001, 2001})));
+    expectUidOwnerMapValues({"1000"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2000"}, IIF_MATCH, iif1);
+    checkEachUidValue({1000, 2000}, IIF_MATCH);  // Make sure there are only two uids remaining
+
+    // Remove non-existent uids shouldn't fail
+    ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({2000, 3000})));
+    expectUidOwnerMapValues({"1000"}, IIF_MATCH, iif0);
+    checkEachUidValue({1000}, IIF_MATCH);  // Make sure there are only one uid remaining
+
+    // Remove everything
+    ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+    expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    // Set up existing PENALTY_BOX_MATCH rules
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({"1000", "1001", "10012"},
+                                           BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues({"1000", "1001", "10012"}, PENALTY_BOX_MATCH, 0);
+
+    // Add some partially-overlapping uid owner rules and check result
+    int iif1 = 32;
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10012, 10013, 10014})));
+    expectUidOwnerMapValues({"1000", "1001"}, PENALTY_BOX_MATCH, 0);
+    expectUidOwnerMapValues({"10012"}, PENALTY_BOX_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10013", "10014"}, IIF_MATCH, iif1);
+
+    // Removing some PENALTY_BOX_MATCH rules should not change uid interface rule
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({"1001", "10012"}, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
+    expectUidOwnerMapValues({"1000"}, PENALTY_BOX_MATCH, 0);
+    expectUidOwnerMapValues({"10012", "10013", "10014"}, IIF_MATCH, iif1);
+
+    // Remove all uid interface rules
+    ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({10012, 10013, 10014})));
+    expectUidOwnerMapValues({"1000"}, PENALTY_BOX_MATCH, 0);
+    // Make sure these are the only uids left
+    checkEachUidValue({1000}, PENALTY_BOX_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithNewMatches) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int iif1 = 56;
+    // Set up existing uid interface rules
+    ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10001, 10002})));
+    expectUidOwnerMapValues({"10001", "10002"}, IIF_MATCH, iif1);
+
+    // Add some partially-overlapping doze rules
+    EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {10002, 10003}));
+    expectUidOwnerMapValues({"10001"}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10002"}, DOZABLE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10003"}, DOZABLE_MATCH, 0);
+
+    // Introduce a third rule type (powersave) on various existing UIDs
+    EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {10000, 10001, 10002, 10003}));
+    expectUidOwnerMapValues({"10000"}, POWERSAVE_MATCH, 0);
+    expectUidOwnerMapValues({"10001"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10002"}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10003"}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
+
+    // Remove all doze rules
+    EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {}));
+    expectUidOwnerMapValues({"10000"}, POWERSAVE_MATCH, 0);
+    expectUidOwnerMapValues({"10001"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10002"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10003"}, POWERSAVE_MATCH, 0);
+
+    // Remove all powersave rules, expect ownerMap to only have uid interface rules left
+    EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {}));
+    expectUidOwnerMapValues({"10001", "10002"}, IIF_MATCH, iif1);
+    // Make sure these are the only uids left
+    checkEachUidValue({10001, 10002}, IIF_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestGrantInternetPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids);
+    expectMapEmpty(mFakeUidPermissionMap);
+    expectPrivilegedUserSetEmpty();
+}
+
+TEST_F(TrafficControllerTest, TestRevokeInternetPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+    expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+}
+
+TEST_F(TrafficControllerTest, TestPermissionUninstalled) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+    expectUidPermissionMapValues(appUids, INetd::PERMISSION_UPDATE_DEVICE_STATS);
+    expectPrivilegedUserSet(appUids);
+
+    std::vector<uid_t> uidToRemove = {TEST_UID};
+    mTc.setPermissionForUids(INetd::PERMISSION_UNINSTALLED, uidToRemove);
+
+    std::vector<uid_t> uidRemain = {TEST_UID3, TEST_UID2};
+    expectUidPermissionMapValues(uidRemain, INetd::PERMISSION_UPDATE_DEVICE_STATS);
+    expectPrivilegedUserSet(uidRemain);
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UNINSTALLED, uidRemain);
+    expectMapEmpty(mFakeUidPermissionMap);
+    expectPrivilegedUserSetEmpty();
+}
+
+TEST_F(TrafficControllerTest, TestGrantUpdateStatsPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+    expectUidPermissionMapValues(appUids, INetd::PERMISSION_UPDATE_DEVICE_STATS);
+    expectPrivilegedUserSet(appUids);
+
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+    expectPrivilegedUserSetEmpty();
+    expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+}
+
+TEST_F(TrafficControllerTest, TestRevokeUpdateStatsPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+    expectPrivilegedUserSet(appUids);
+
+    std::vector<uid_t> uidToRemove = {TEST_UID};
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, uidToRemove);
+
+    std::vector<uid_t> uidRemain = {TEST_UID3, TEST_UID2};
+    expectPrivilegedUserSet(uidRemain);
+
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, uidRemain);
+    expectPrivilegedUserSetEmpty();
+}
+
+TEST_F(TrafficControllerTest, TestGrantWrongPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+    expectPrivilegedUserSetEmpty();
+    expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+}
+
+TEST_F(TrafficControllerTest, TestGrantDuplicatePermissionSlientlyFail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+    mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids);
+    expectMapEmpty(mFakeUidPermissionMap);
+
+    std::vector<uid_t> uidToAdd = {TEST_UID};
+    mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, uidToAdd);
+
+    expectPrivilegedUserSetEmpty();
+
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+    expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+    expectPrivilegedUserSet(appUids);
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, uidToAdd);
+    expectPrivilegedUserSet(appUids);
+
+    mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+    expectPrivilegedUserSetEmpty();
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/UidRanges.cpp b/server/UidRanges.cpp
index cd1ae78..883a13f 100644
--- a/server/UidRanges.cpp
+++ b/server/UidRanges.cpp
@@ -30,6 +30,29 @@
 namespace android {
 namespace net {
 
+namespace {
+
+bool compUidRangeParcel(const UidRangeParcel& lhs, const UidRangeParcel& rhs) {
+    return lhs.start != rhs.start ? (lhs.start < rhs.start) : (lhs.stop < rhs.stop);
+};
+
+UidRangeParcel makeUidRangeParcel(int start, int stop) {
+    UidRangeParcel res;
+    res.start = start;
+    res.stop = stop;
+
+    return res;
+}
+
+uint32_t length(const UidRangeParcel& t) {
+    if (t.start == -1 || t.stop == -1) {
+        return 0;
+    }
+    return static_cast<uint32_t>(t.stop - t.start + 1);
+}
+
+}  // namespace
+
 bool UidRanges::hasUid(uid_t uid) const {
     if (uid > (unsigned) INT32_MAX) {
         ALOGW("UID larger than 32 bits: %" PRIu64, static_cast<uint64_t>(uid));
@@ -37,12 +60,13 @@
     }
     const int32_t intUid = static_cast<int32_t>(uid);
 
-    auto iter = std::lower_bound(mRanges.begin(), mRanges.end(), UidRange(intUid, intUid));
-    return (iter != mRanges.end() && iter->getStart() == intUid) ||
-           (iter != mRanges.begin() && (--iter)->getStop() >= intUid);
+    auto iter = std::lower_bound(mRanges.begin(), mRanges.end(), makeUidRangeParcel(intUid, intUid),
+                                 compUidRangeParcel);
+    return (iter != mRanges.end() && iter->start == intUid) ||
+           (iter != mRanges.begin() && (--iter)->stop >= intUid);
 }
 
-const std::vector<UidRange>& UidRanges::getRanges() const {
+const std::vector<UidRangeParcel>& UidRanges::getRanges() const {
     return mRanges;
 }
 
@@ -81,37 +105,37 @@
             // Invalid UIDs.
             return false;
         }
-        mRanges.push_back(UidRange(uidStart, uidEnd));
+        mRanges.push_back(makeUidRangeParcel(uidStart, uidEnd));
     }
-    std::sort(mRanges.begin(), mRanges.end());
+    std::sort(mRanges.begin(), mRanges.end(), compUidRangeParcel);
     return true;
 }
 
-UidRanges::UidRanges(const std::vector<UidRange>& ranges) {
+UidRanges::UidRanges(const std::vector<UidRangeParcel>& ranges) {
     mRanges = ranges;
-    std::sort(mRanges.begin(), mRanges.end());
+    std::sort(mRanges.begin(), mRanges.end(), compUidRangeParcel);
 }
 
 void UidRanges::add(const UidRanges& other) {
     auto middle = mRanges.insert(mRanges.end(), other.mRanges.begin(), other.mRanges.end());
-    std::inplace_merge(mRanges.begin(), middle, mRanges.end());
+    std::inplace_merge(mRanges.begin(), middle, mRanges.end(), compUidRangeParcel);
 }
 
 void UidRanges::remove(const UidRanges& other) {
     auto end = std::set_difference(mRanges.begin(), mRanges.end(), other.mRanges.begin(),
-                                   other.mRanges.end(), mRanges.begin());
+                                   other.mRanges.end(), mRanges.begin(), compUidRangeParcel);
     mRanges.erase(end, mRanges.end());
 }
 
 std::string UidRanges::toString() const {
     std::string s("UidRanges{ ");
     for (const auto &range : mRanges) {
-        if (range.length() == 0) {
-            StringAppendF(&s, "<BAD: %u-%u> ", range.getStart(), range.getStop());
-        } else if (range.length() == 1) {
-            StringAppendF(&s, "%u ", range.getStart());
+        if (length(range) == 0) {
+            StringAppendF(&s, "<BAD: %u-%u> ", range.start, range.stop);
+        } else if (length(range) == 1) {
+            StringAppendF(&s, "%u ", range.start);
         } else {
-            StringAppendF(&s, "%u-%u ", range.getStart(), range.getStop());
+            StringAppendF(&s, "%u-%u ", range.start, range.stop);
         }
     }
     StringAppendF(&s, "}");
diff --git a/server/UidRanges.h b/server/UidRanges.h
index 206998f..28d3c6b 100644
--- a/server/UidRanges.h
+++ b/server/UidRanges.h
@@ -17,7 +17,7 @@
 #ifndef NETD_SERVER_UID_RANGES_H
 #define NETD_SERVER_UID_RANGES_H
 
-#include "android/net/UidRange.h"
+#include "android/net/INetd.h"
 
 #include <sys/types.h>
 #include <utility>
@@ -29,10 +29,10 @@
 class UidRanges {
 public:
     UidRanges() {}
-    UidRanges(const std::vector<android::net::UidRange>& ranges);
+    UidRanges(const std::vector<android::net::UidRangeParcel>& ranges);
 
     bool hasUid(uid_t uid) const;
-    const std::vector<android::net::UidRange>& getRanges() const;
+    const std::vector<UidRangeParcel>& getRanges() const;
 
     bool parseFrom(int argc, char* argv[]);
     std::string toString() const;
@@ -40,8 +40,8 @@
     void add(const UidRanges& other);
     void remove(const UidRanges& other);
 
-private:
-    std::vector<android::net::UidRange> mRanges;
+  private:
+    std::vector<UidRangeParcel> mRanges;
 };
 
 }  // namespace net
diff --git a/server/VirtualNetwork.cpp b/server/VirtualNetwork.cpp
index 49418eb..33791c6 100644
--- a/server/VirtualNetwork.cpp
+++ b/server/VirtualNetwork.cpp
@@ -15,27 +15,22 @@
  */
 
 #include <set>
+
+#define LOG_TAG "Netd"
+
 #include "VirtualNetwork.h"
 
 #include "SockDiag.h"
 #include "RouteController.h"
 
-#define LOG_TAG "Netd"
 #include "log/log.h"
 
 namespace android {
 namespace net {
 
-VirtualNetwork::VirtualNetwork(unsigned netId, bool hasDns, bool secure) :
-        Network(netId), mHasDns(hasDns), mSecure(secure) {
-}
+VirtualNetwork::VirtualNetwork(unsigned netId, bool secure) : Network(netId), mSecure(secure) {}
 
-VirtualNetwork::~VirtualNetwork() {
-}
-
-bool VirtualNetwork::getHasDns() const {
-    return mHasDns;
-}
+VirtualNetwork::~VirtualNetwork() {}
 
 bool VirtualNetwork::isSecure() const {
     return mSecure;
diff --git a/server/VirtualNetwork.h b/server/VirtualNetwork.h
index e48060b..69b14d8 100644
--- a/server/VirtualNetwork.h
+++ b/server/VirtualNetwork.h
@@ -34,10 +34,9 @@
 // permitted to skip it and pick any other network for their connections.
 class VirtualNetwork : public Network {
 public:
-    VirtualNetwork(unsigned netId, bool hasDns, bool secure);
+    VirtualNetwork(unsigned netId, bool secure);
     virtual ~VirtualNetwork();
 
-    bool getHasDns() const;
     bool isSecure() const;
     bool appliesToUser(uid_t uid) const;
 
@@ -53,7 +52,6 @@
     int maybeCloseSockets(bool add, const UidRanges& uidRanges,
                           const std::set<uid_t>& protectableUsers);
 
-    const bool mHasDns;
     const bool mSecure;
     UidRanges mUidRanges;
 };
diff --git a/server/WakeupController.cpp b/server/WakeupController.cpp
index 397e1a6..eb36291 100644
--- a/server/WakeupController.cpp
+++ b/server/WakeupController.cpp
@@ -30,7 +30,7 @@
 
 #include <android-base/strings.h>
 #include <android-base/stringprintf.h>
-#include <cutils/log.h>
+#include <log/log.h>
 #include <netdutils/Netfilter.h>
 #include <netdutils/Netlink.h>
 
diff --git a/server/WakeupControllerTest.cpp b/server/WakeupControllerTest.cpp
index 500c5f9..ff99b98 100644
--- a/server/WakeupControllerTest.cpp
+++ b/server/WakeupControllerTest.cpp
@@ -44,10 +44,10 @@
 
 class MockNetdEventListener {
   public:
-    MOCK_METHOD10(onWakeupEvent, void(
-            const std::string& prefix, int uid, int ether, int ipNextHeader,
-            std::vector<uint8_t> dstHw, const std::string& srcIp, const std::string& dstIp,
-            int srcPort, int dstPort, uint64_t timestampNs));
+    MOCK_METHOD10(onWakeupEvent,
+                  void(const std::string& prefix, int uid, int ether, int ipNextHeader,
+                       const std::vector<uint8_t>& dstHw, const std::string& srcIp,
+                       const std::string& dstIp, int srcPort, int dstPort, uint64_t timestampNs));
 };
 
 class MockIptablesRestore : public IptablesRestoreInterface {
@@ -74,7 +74,7 @@
             .WillOnce(DoAll(SaveArg<2>(&mMessageHandler), Return(ok)));
         EXPECT_CALL(mListener,
             unsubscribe(NetlinkManager::NFLOG_WAKEUP_GROUP)).WillOnce(Return(ok));
-        mController.init(&mListener);
+        EXPECT_OK(mController.init(&mListener));
     }
 
     StrictMock<MockNetdEventListener> mEventListener;
@@ -291,7 +291,7 @@
         " -j NFLOG --nflog-prefix wlan8 --nflog-group 3 --nflog-threshold 8"
         " -m mark --mark 0x12345678/0x0f0f0f0f -m limit --limit 10/s\nCOMMIT\n";
     EXPECT_CALL(mIptables, execute(V4V6, kExpected, _)).WillOnce(Return(0));
-    mController.addInterface(kPrefix, kIfName, kMark, kMask);
+    EXPECT_OK(mController.addInterface(kPrefix, kIfName, kMark, kMask));
 }
 
 TEST_F(WakeupControllerTest, delInterface) {
@@ -304,7 +304,7 @@
         " -j NFLOG --nflog-prefix wlan8 --nflog-group 3 --nflog-threshold 8"
         " -m mark --mark 0x12345678/0xf0f0f0f0 -m limit --limit 10/s\nCOMMIT\n";
     EXPECT_CALL(mIptables, execute(V4V6, kExpected, _)).WillOnce(Return(0));
-    mController.delInterface(kPrefix, kIfName, kMark, kMask);
+    EXPECT_OK(mController.delInterface(kPrefix, kIfName, kMark, kMask));
 }
 
 }  // namespace net
diff --git a/server/XfrmController.cpp b/server/XfrmController.cpp
index b9a5a41..a57b492 100644
--- a/server/XfrmController.cpp
+++ b/server/XfrmController.cpp
@@ -31,6 +31,7 @@
 #include <inttypes.h>
 
 #include <arpa/inet.h>
+#include <net/if.h>
 #include <netinet/in.h>
 
 #include <sys/socket.h>
@@ -44,25 +45,33 @@
 #include <linux/xfrm.h>
 
 #define LOG_TAG "XfrmController"
-#include "InterfaceController.h"
-#include "NetdConstants.h"
-#include "NetlinkCommands.h"
-#include "ResponseCode.h"
-#include "XfrmController.h"
-#include "netdutils/Fd.h"
-#include "netdutils/Slice.h"
-#include "netdutils/Syscalls.h"
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/net/INetd.h>
-#include <cutils/log.h>
 #include <cutils/properties.h>
+#include <log/log.h>
+#include <log/log_properties.h>
 #include <logwrap/logwrap.h>
+#include "Fwmark.h"
+#include "InterfaceController.h"
+#include "NetdConstants.h"
+#include "NetlinkCommands.h"
+#include "Permission.h"
+#include "XfrmController.h"
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
+#include "netdutils/DumpWriter.h"
+#include "netdutils/Fd.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Syscalls.h"
 
 using android::net::INetd;
+using android::netdutils::DumpWriter;
 using android::netdutils::Fd;
+using android::netdutils::ScopedIndent;
 using android::netdutils::Slice;
 using android::netdutils::Status;
 using android::netdutils::StatusOr;
@@ -86,6 +95,13 @@
 constexpr uint32_t RAND_SPI_MAX = 0xFFFFFFFE;
 
 constexpr uint32_t INVALID_SPI = 0;
+constexpr const char* INFO_KIND_VTI = "vti";
+constexpr const char* INFO_KIND_VTI6 = "vti6";
+constexpr const char* INFO_KIND_XFRMI = "xfrm";
+constexpr int INFO_KIND_MAX_LEN = 8;
+constexpr int LOOPBACK_IFINDEX = 1;
+
+bool mIsXfrmIntfSupported = false;
 
 static inline bool isEngBuild() {
     static const std::string sBuildType = android::base::GetProperty("ro.build.type", "user");
@@ -163,7 +179,7 @@
 
 void logIov(const std::vector<iovec>& iov) {
     for (const iovec& row : iov) {
-        logHex(0, reinterpret_cast<char*>(row.iov_base), row.iov_len);
+        logHex(nullptr, reinterpret_cast<char*>(row.iov_base), row.iov_len);
     }
 }
 
@@ -181,13 +197,13 @@
     return fillNlAttr(nlaType, (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr), nlAttr);
 }
 
-size_t fillNlAttrU32(__u16 nlaType, int32_t value, nlattr* nlAttr, uint32_t* u32Value) {
-    *u32Value = htonl(value);
-    return fillNlAttr(nlaType, sizeof((*u32Value)), nlAttr);
+size_t fillNlAttrU32(__u16 nlaType, uint32_t value, XfrmController::nlattr_payload_u32* nlAttr) {
+    nlAttr->value = value;
+    return fillNlAttr(nlaType, sizeof(value), &nlAttr->hdr);
 }
 
 // returns the address family, placing the string in the provided buffer
-StatusOr<uint16_t> convertStringAddress(std::string addr, uint8_t* buffer) {
+StatusOr<uint16_t> convertStringAddress(const std::string& addr, uint8_t* buffer) {
     if (inet_pton(AF_INET, addr.c_str(), buffer) == 1) {
         return AF_INET;
     } else if (inet_pton(AF_INET6, addr.c_str(), buffer) == 1) {
@@ -379,8 +395,15 @@
 //
 XfrmController::XfrmController(void) {}
 
+// Test-only constructor allowing override of XFRM Interface support checks
+XfrmController::XfrmController(bool xfrmIntfSupport) {
+    mIsXfrmIntfSupported = xfrmIntfSupport;
+}
+
 netdutils::Status XfrmController::Init() {
     RETURN_IF_NOT_OK(flushInterfaces());
+    mIsXfrmIntfSupported = isXfrmIntfSupported();
+
     XfrmSocketImpl sock;
     RETURN_IF_NOT_OK(sock.open());
     RETURN_IF_NOT_OK(flushSaDb(sock));
@@ -393,12 +416,10 @@
     const String8 ifPrefix8 = String8(INetd::IPSEC_INTERFACE_PREFIX().string());
 
     for (const std::string& iface : ifaces.value()) {
-        int status = 0;
+        netdutils::Status status;
         // Look for the reserved interface prefix, which must be in the name at position 0
-        if (!iface.compare(0, ifPrefix8.length(), ifPrefix8.c_str()) &&
-            (status = removeVirtualTunnelInterface(iface)) < 0) {
-            ALOGE("Failed to delete ipsec tunnel %s.", iface.c_str());
-            return netdutils::statusFromErrno(status, "Failed to remove ipsec tunnel.");
+        if (android::base::StartsWith(iface.c_str(), ifPrefix8.c_str())) {
+            RETURN_IF_NOT_OK(ipSecRemoveTunnelInterface(iface));
         }
     }
     return netdutils::status::ok;
@@ -407,7 +428,7 @@
 netdutils::Status XfrmController::flushSaDb(const XfrmSocket& s) {
     struct xfrm_usersa_flush flushUserSa = {.proto = IPSEC_PROTO_ANY};
 
-    std::vector<iovec> iov = {{NULL, 0}, // reserved for the eventual addition of a NLMSG_HDR
+    std::vector<iovec> iov = {{nullptr, 0}, // reserved for the eventual addition of a NLMSG_HDR
                               {&flushUserSa, sizeof(flushUserSa)}, // xfrm_usersa_flush structure
                               {kPadBytes, NLMSG_ALIGN(sizeof(flushUserSa)) - sizeof(flushUserSa)}};
 
@@ -415,15 +436,27 @@
 }
 
 netdutils::Status XfrmController::flushPolicyDb(const XfrmSocket& s) {
-    std::vector<iovec> iov = {{NULL, 0}}; // reserved for the eventual addition of a NLMSG_HDR
+    std::vector<iovec> iov = {{nullptr, 0}}; // reserved for the eventual addition of a NLMSG_HDR
     return s.sendMessage(XFRM_MSG_FLUSHPOLICY, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
-netdutils::Status XfrmController::ipSecSetEncapSocketOwner(const android::base::unique_fd& socket,
-                                                           int newUid, uid_t callerUid) {
+bool XfrmController::isXfrmIntfSupported() {
+    const char* IPSEC_TEST_INTF_NAME = "ipsec_test";
+    const int32_t XFRM_TEST_IF_ID = 0xFFFF;
+
+    bool errored = false;
+    errored |=
+            ipSecAddXfrmInterface(IPSEC_TEST_INTF_NAME, XFRM_TEST_IF_ID, NETLINK_ROUTE_CREATE_FLAGS)
+                    .code();
+    errored |= ipSecRemoveTunnelInterface(IPSEC_TEST_INTF_NAME).code();
+    return !errored;
+}
+
+netdutils::Status XfrmController::ipSecSetEncapSocketOwner(int socketFd, int newUid,
+                                                           uid_t callerUid) {
     ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
 
-    const int fd = socket.get();
+    const int fd = socketFd;
     struct stat info;
     if (fstat(fd, &info)) {
         return netdutils::statusFromErrno(errno, "Failed to stat socket file descriptor");
@@ -436,9 +469,9 @@
     }
 
     int optval;
-    socklen_t optlen;
+    socklen_t optlen = sizeof(optval);
     netdutils::Status status =
-        getSyscallInstance().getsockopt(Fd(socket), IPPROTO_UDP, UDP_ENCAP, &optval, &optlen);
+            getSyscallInstance().getsockopt(Fd(fd), IPPROTO_UDP, UDP_ENCAP, &optval, &optlen);
     if (status != netdutils::status::ok) {
         return status;
     }
@@ -463,8 +496,8 @@
     ALOGD("inSpi=%0.8x", inSpi);
 
     XfrmSaInfo saInfo{};
-    netdutils::Status ret =
-        fillXfrmId(sourceAddress, destinationAddress, INVALID_SPI, 0, 0, transformId, &saInfo);
+    netdutils::Status ret = fillXfrmCommonInfo(sourceAddress, destinationAddress, INVALID_SPI, 0, 0,
+                                               transformId, 0, &saInfo);
     if (!isOk(ret)) {
         return ret;
     }
@@ -492,12 +525,13 @@
 }
 
 netdutils::Status XfrmController::ipSecAddSecurityAssociation(
-    int32_t transformId, int32_t mode, const std::string& sourceAddress,
-    const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi, int32_t markValue,
-    int32_t markMask, const std::string& authAlgo, const std::vector<uint8_t>& authKey,
-    int32_t authTruncBits, const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey,
-    int32_t cryptTruncBits, const std::string& aeadAlgo, const std::vector<uint8_t>& aeadKey,
-    int32_t aeadIcvBits, int32_t encapType, int32_t encapLocalPort, int32_t encapRemotePort) {
+        int32_t transformId, int32_t mode, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+        int32_t markValue, int32_t markMask, const std::string& authAlgo,
+        const std::vector<uint8_t>& authKey, int32_t authTruncBits, const std::string& cryptAlgo,
+        const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits, const std::string& aeadAlgo,
+        const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+        int32_t encapLocalPort, int32_t encapRemotePort, int32_t xfrmInterfaceId) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("transformId=%d", transformId);
     ALOGD("mode=%d", mode);
@@ -516,10 +550,11 @@
     ALOGD("encapType=%d", encapType);
     ALOGD("encapLocalPort=%d", encapLocalPort);
     ALOGD("encapRemotePort=%d", encapRemotePort);
+    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
 
     XfrmSaInfo saInfo{};
-    netdutils::Status ret = fillXfrmId(sourceAddress, destinationAddress, spi, markValue, markMask,
-                                       transformId, &saInfo);
+    netdutils::Status ret = fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, markValue,
+                                               markMask, transformId, xfrmInterfaceId, &saInfo);
     if (!isOk(ret)) {
         return ret;
     }
@@ -559,7 +594,7 @@
             // direction is ultimately input.
             saInfo.encap.srcPort = encapLocalPort;
             saInfo.encap.dstPort = encapRemotePort;
-        // fall through
+            [[fallthrough]];
         case XfrmEncapType::NONE:
             saInfo.encap.type = static_cast<XfrmEncapType>(encapType);
             break;
@@ -578,8 +613,9 @@
 }
 
 netdutils::Status XfrmController::ipSecDeleteSecurityAssociation(
-    int32_t transformId, const std::string& sourceAddress, const std::string& destinationAddress,
-    int32_t spi, int32_t markValue, int32_t markMask) {
+        int32_t transformId, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t spi, int32_t markValue, int32_t markMask,
+        int32_t xfrmInterfaceId) {
     ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("transformId=%d", transformId);
     ALOGD("sourceAddress=%s", sourceAddress.c_str());
@@ -587,10 +623,11 @@
     ALOGD("spi=%0.8x", spi);
     ALOGD("markValue=%x", markValue);
     ALOGD("markMask=%x", markMask);
+    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
 
-    XfrmId saId{};
-    netdutils::Status ret =
-        fillXfrmId(sourceAddress, destinationAddress, spi, markValue, markMask, transformId, &saId);
+    XfrmSaInfo saInfo{};
+    netdutils::Status ret = fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, markValue,
+                                               markMask, transformId, xfrmInterfaceId, &saInfo);
     if (!isOk(ret)) {
         return ret;
     }
@@ -602,7 +639,7 @@
         return socketStatus;
     }
 
-    ret = deleteSecurityAssociation(saId, sock);
+    ret = deleteSecurityAssociation(saInfo, sock);
     if (!isOk(ret)) {
         ALOGD("Failed to delete Security Association, line=%d", __LINE__);
     }
@@ -610,16 +647,12 @@
     return ret;
 }
 
-netdutils::Status XfrmController::fillXfrmId(const std::string& sourceAddress,
-                                             const std::string& destinationAddress, int32_t spi,
-                                             int32_t markValue, int32_t markMask,
-                                             int32_t transformId, XfrmId* xfrmId) {
-    // Fill the straightforward fields first
-    xfrmId->transformId = transformId;
-    xfrmId->spi = htonl(spi);
-    xfrmId->mark.v = markValue;
-    xfrmId->mark.m = markMask;
-
+netdutils::Status XfrmController::fillXfrmCommonInfo(const std::string& sourceAddress,
+                                                     const std::string& destinationAddress,
+                                                     int32_t spi, int32_t markValue,
+                                                     int32_t markMask, int32_t transformId,
+                                                     int32_t xfrmInterfaceId,
+                                                     XfrmCommonInfo* info) {
     // Use the addresses to determine the address family and do validation
     xfrm_address_t sourceXfrmAddr{}, destXfrmAddr{};
     StatusOr<int> sourceFamily, destFamily;
@@ -637,16 +670,34 @@
         return netdutils::statusFromErrno(EINVAL, "Invalid or mismatched address families");
     }
 
-    xfrmId->addrFamily = destFamily.value();
+    info->addrFamily = destFamily.value();
 
-    xfrmId->dstAddr = destXfrmAddr;
-    xfrmId->srcAddr = sourceXfrmAddr;
+    info->dstAddr = destXfrmAddr;
+    info->srcAddr = sourceXfrmAddr;
+
+    return fillXfrmCommonInfo(spi, markValue, markMask, transformId, xfrmInterfaceId, info);
+}
+
+netdutils::Status XfrmController::fillXfrmCommonInfo(int32_t spi, int32_t markValue,
+                                                     int32_t markMask, int32_t transformId,
+                                                     int32_t xfrmInterfaceId,
+                                                     XfrmCommonInfo* info) {
+    info->transformId = transformId;
+    info->spi = htonl(spi);
+
+    if (mIsXfrmIntfSupported) {
+        info->xfrm_if_id = xfrmInterfaceId;
+    } else {
+        info->mark.v = markValue;
+        info->mark.m = markMask;
+    }
+
     return netdutils::status::ok;
 }
 
 netdutils::Status XfrmController::ipSecApplyTransportModeTransform(
-    const android::base::unique_fd& socket, int32_t transformId, int32_t direction,
-    const std::string& sourceAddress, const std::string& destinationAddress, int32_t spi) {
+        int socketFd, int32_t transformId, int32_t direction, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t spi) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("transformId=%d", transformId);
     ALOGD("direction=%d", direction);
@@ -654,25 +705,31 @@
     ALOGD("destinationAddress=%s", destinationAddress.c_str());
     ALOGD("spi=%0.8x", spi);
 
-    StatusOr<sockaddr_storage> ret = getSyscallInstance().getsockname<sockaddr_storage>(Fd(socket));
+    StatusOr<sockaddr_storage> ret =
+            getSyscallInstance().getsockname<sockaddr_storage>(Fd(socketFd));
     if (!isOk(ret)) {
         ALOGE("Failed to get socket info in %s", __FUNCTION__);
         return ret;
     }
     struct sockaddr_storage saddr = ret.value();
 
-    XfrmSaInfo saInfo{};
-    netdutils::Status status =
-        fillXfrmId(sourceAddress, destinationAddress, spi, 0, 0, transformId, &saInfo);
+    XfrmSpInfo spInfo{};
+    netdutils::Status status = fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, 0, 0,
+                                                  transformId, 0, &spInfo);
     if (!isOk(status)) {
         ALOGE("Couldn't build SA ID %s", __FUNCTION__);
         return status;
     }
 
-    if (saddr.ss_family == AF_INET && saInfo.addrFamily != AF_INET) {
+    spInfo.selAddrFamily = spInfo.addrFamily;
+
+    // Allow dual stack sockets. Dual stack sockets are guaranteed to never have an AF_INET source
+    // address; the source address would instead be an IPv4-mapped address. Thus, disallow AF_INET
+    // sockets with mismatched address families (All other cases are acceptable).
+    if (saddr.ss_family == AF_INET && spInfo.addrFamily != AF_INET) {
         ALOGE("IPV4 socket address family(%d) should match IPV4 Transform "
               "address family(%d)!",
-              saddr.ss_family, saInfo.addrFamily);
+              saddr.ss_family, spInfo.addrFamily);
         return netdutils::statusFromErrno(EINVAL, "Mismatched address family");
     }
 
@@ -681,8 +738,8 @@
         xfrm_user_tmpl tmpl;
     } policy{};
 
-    fillTransportModeUserSpInfo(saInfo, static_cast<XfrmDirection>(direction), &policy.info);
-    fillUserTemplate(saInfo, &policy.tmpl);
+    fillUserSpInfo(spInfo, static_cast<XfrmDirection>(direction), &policy.info);
+    fillUserTemplate(spInfo, &policy.tmpl);
 
     LOG_HEX("XfrmUserPolicy", reinterpret_cast<char*>(&policy), sizeof(policy));
 
@@ -700,7 +757,7 @@
             return netdutils::statusFromErrno(EAFNOSUPPORT, "Invalid address family");
     }
 
-    status = getSyscallInstance().setsockopt(Fd(socket), sockLayer, sockOpt, policy);
+    status = getSyscallInstance().setsockopt(Fd(socketFd), sockLayer, sockOpt, policy);
     if (!isOk(status)) {
         ALOGE("Error setting socket option for XFRM! (%s)", toString(status).c_str());
     }
@@ -708,11 +765,11 @@
     return status;
 }
 
-netdutils::Status
-XfrmController::ipSecRemoveTransportModeTransform(const android::base::unique_fd& socket) {
+netdutils::Status XfrmController::ipSecRemoveTransportModeTransform(int socketFd) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
 
-    StatusOr<sockaddr_storage> ret = getSyscallInstance().getsockname<sockaddr_storage>(Fd(socket));
+    StatusOr<sockaddr_storage> ret =
+            getSyscallInstance().getsockname<sockaddr_storage>(Fd(socketFd));
     if (!isOk(ret)) {
         ALOGE("Failed to get socket info in %s! (%s)", __FUNCTION__, toString(ret).c_str());
         return ret;
@@ -735,7 +792,7 @@
     // Kernel will delete the security policy on this socket for both direction
     // if optval is set to NULL and optlen is set to 0.
     netdutils::Status status =
-        getSyscallInstance().setsockopt(Fd(socket), sockLayer, sockOpt, NULL, 0);
+            getSyscallInstance().setsockopt(Fd(socketFd), sockLayer, sockOpt, nullptr, 0);
     if (!isOk(status)) {
         ALOGE("Error removing socket option for XFRM! (%s)", toString(status).c_str());
     }
@@ -743,66 +800,77 @@
     return status;
 }
 
-netdutils::Status XfrmController::ipSecAddSecurityPolicy(int32_t transformId, int32_t direction,
-                                                         const std::string& localAddress,
-                                                         const std::string& remoteAddress,
-                                                         int32_t spi, int32_t markValue,
-                                                         int32_t markMask) {
-    return processSecurityPolicy(transformId, direction, localAddress, remoteAddress, spi,
-                                 markValue, markMask, XFRM_MSG_NEWPOLICY);
+netdutils::Status XfrmController::ipSecAddSecurityPolicy(
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t xfrmInterfaceId) {
+    return processSecurityPolicy(transformId, selAddrFamily, direction, tmplSrcAddress,
+                                 tmplDstAddress, spi, markValue, markMask, xfrmInterfaceId,
+                                 XFRM_MSG_NEWPOLICY);
 }
 
-netdutils::Status XfrmController::ipSecUpdateSecurityPolicy(int32_t transformId, int32_t direction,
-                                                            const std::string& localAddress,
-                                                            const std::string& remoteAddress,
-                                                            int32_t spi, int32_t markValue,
-                                                            int32_t markMask) {
-    return processSecurityPolicy(transformId, direction, localAddress, remoteAddress, spi,
-                                 markValue, markMask, XFRM_MSG_UPDPOLICY);
+netdutils::Status XfrmController::ipSecUpdateSecurityPolicy(
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t xfrmInterfaceId) {
+    return processSecurityPolicy(transformId, selAddrFamily, direction, tmplSrcAddress,
+                                 tmplDstAddress, spi, markValue, markMask, xfrmInterfaceId,
+                                 XFRM_MSG_UPDPOLICY);
 }
 
-netdutils::Status XfrmController::ipSecDeleteSecurityPolicy(int32_t transformId, int32_t direction,
-                                                            const std::string& localAddress,
-                                                            const std::string& remoteAddress,
-                                                            int32_t markValue, int32_t markMask) {
-    return processSecurityPolicy(transformId, direction, localAddress, remoteAddress, 0, markValue,
-                                 markMask, XFRM_MSG_DELPOLICY);
+netdutils::Status XfrmController::ipSecDeleteSecurityPolicy(int32_t transformId,
+                                                            int32_t selAddrFamily,
+                                                            int32_t direction, int32_t markValue,
+                                                            int32_t markMask,
+                                                            int32_t xfrmInterfaceId) {
+    return processSecurityPolicy(transformId, selAddrFamily, direction, "", "", 0, markValue,
+                                 markMask, xfrmInterfaceId, XFRM_MSG_DELPOLICY);
 }
 
-netdutils::Status XfrmController::processSecurityPolicy(int32_t transformId, int32_t direction,
-                                                        const std::string& localAddress,
-                                                        const std::string& remoteAddress,
-                                                        int32_t spi, int32_t markValue,
-                                                        int32_t markMask, int32_t msgType) {
+netdutils::Status XfrmController::processSecurityPolicy(
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t xfrmInterfaceId, int32_t msgType) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
+    ALOGD("selAddrFamily=%s", selAddrFamily == AF_INET6 ? "AF_INET6" : "AF_INET");
     ALOGD("transformId=%d", transformId);
     ALOGD("direction=%d", direction);
-    ALOGD("localAddress=%s", localAddress.c_str());
-    ALOGD("remoteAddress=%s", remoteAddress.c_str());
+    ALOGD("tmplSrcAddress=%s", tmplSrcAddress.c_str());
+    ALOGD("tmplDstAddress=%s", tmplDstAddress.c_str());
     ALOGD("spi=%0.8x", spi);
     ALOGD("markValue=%d", markValue);
     ALOGD("markMask=%d", markMask);
     ALOGD("msgType=%d", msgType);
+    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
 
-    XfrmSaInfo saInfo{};
-    saInfo.mode = XfrmMode::TUNNEL;
+    XfrmSpInfo spInfo{};
+    spInfo.mode = XfrmMode::TUNNEL;
 
     XfrmSocketImpl sock;
     RETURN_IF_NOT_OK(sock.open());
 
-    RETURN_IF_NOT_OK(
-        fillXfrmId(localAddress, remoteAddress, spi, markValue, markMask, transformId, &saInfo));
+    // Set the correct address families. Tunnel mode policies use wildcard selectors, while
+    // templates have addresses set. These may be different address families. This method is called
+    // separately for IPv4 and IPv6 policies, and thus only need to map a single inner address
+    // family to the outer address families.
+    spInfo.selAddrFamily = selAddrFamily;
 
     if (msgType == XFRM_MSG_DELPOLICY) {
-        return deleteTunnelModeSecurityPolicy(saInfo, sock, static_cast<XfrmDirection>(direction));
+        RETURN_IF_NOT_OK(fillXfrmCommonInfo(spi, markValue, markMask, transformId, xfrmInterfaceId,
+                                            &spInfo));
+
+        return deleteTunnelModeSecurityPolicy(spInfo, sock, static_cast<XfrmDirection>(direction));
     } else {
-        return updateTunnelModeSecurityPolicy(saInfo, sock, static_cast<XfrmDirection>(direction),
+        RETURN_IF_NOT_OK(fillXfrmCommonInfo(tmplSrcAddress, tmplDstAddress, spi, markValue,
+                                            markMask, transformId, xfrmInterfaceId, &spInfo));
+
+        return updateTunnelModeSecurityPolicy(spInfo, sock, static_cast<XfrmDirection>(direction),
                                               msgType);
     }
 }
 
-void XfrmController::fillXfrmSelector(const XfrmSaInfo& record, xfrm_selector* selector) {
-    selector->family = record.addrFamily;
+void XfrmController::fillXfrmSelector(const int selAddrFamily, xfrm_selector* selector) {
+    selector->family = selAddrFamily;
     selector->proto = AF_UNSPEC; // TODO: do we need to match the protocol? it's
                                  // possible via the socket
 }
@@ -816,6 +884,7 @@
     nlattr_xfrm_mark xfrmmark{};
     nlattr_xfrm_output_mark xfrmoutputmark{};
     nlattr_encap_tmpl encap{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
     enum {
         NLMSG_HDR,
@@ -833,24 +902,28 @@
         OUTPUT_MARK_PAD,
         ENCAP,
         ENCAP_PAD,
+        INTF_ID,
+        INTF_ID_PAD,
     };
 
     std::vector<iovec> iov = {
-        {NULL, 0},            // reserved for the eventual addition of a NLMSG_HDR
-        {&usersa, 0},         // main usersa_info struct
-        {kPadBytes, 0},       // up to NLMSG_ALIGNTO pad bytes of padding
-        {&crypt, 0},          // adjust size if crypt algo is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&auth, 0},           // adjust size if auth algo is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&aead, 0},           // adjust size if aead algo is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&xfrmmark, 0},       // adjust size if xfrm mark is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&xfrmoutputmark, 0}, // adjust size if xfrm output mark is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&encap, 0},          // adjust size if encapsulating
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},          // reserved for the eventual addition of a NLMSG_HDR
+            {&usersa, 0},          // main usersa_info struct
+            {kPadBytes, 0},        // up to NLMSG_ALIGNTO pad bytes of padding
+            {&crypt, 0},           // adjust size if crypt algo is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&auth, 0},            // adjust size if auth algo is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&aead, 0},            // adjust size if aead algo is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&xfrmmark, 0},        // adjust size if xfrm mark is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&xfrmoutputmark, 0},  // adjust size if xfrm output mark is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&encap, 0},           // adjust size if encapsulating
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},      // adjust size if interface ID is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
     };
 
     if (!record.aead.name.empty() && (!record.auth.name.empty() || !record.crypt.name.empty())) {
@@ -864,6 +937,15 @@
         return netdutils::statusFromErrno(EINVAL, "Key length invalid; exceeds MAX_KEY_LENGTH");
     }
 
+    if (record.mode != XfrmMode::TUNNEL &&
+        (record.xfrm_if_id != 0 || record.netId != 0 || record.mark.v != 0 || record.mark.m != 0)) {
+        return netdutils::statusFromErrno(EINVAL,
+                                          "xfrm_if_id, mark and netid parameters invalid "
+                                          "for non tunnel-mode transform");
+    } else if (record.mode == XfrmMode::TUNNEL && !mIsXfrmIntfSupported && record.xfrm_if_id != 0) {
+        return netdutils::statusFromErrno(EINVAL, "xfrm_if_id set for VTI Security Association");
+    }
+
     int len;
     len = iov[USERSA].iov_len = fillUserSaInfo(record, &usersa);
     iov[USERSA_PAD].iov_len = NLMSG_ALIGN(len) - len;
@@ -886,6 +968,9 @@
     len = iov[ENCAP].iov_len = fillNlAttrXfrmEncapTmpl(record, &encap);
     iov[ENCAP_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(XFRM_MSG_UPDSA, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
@@ -959,7 +1044,9 @@
 }
 
 int XfrmController::fillUserSaInfo(const XfrmSaInfo& record, xfrm_usersa_info* usersa) {
-    fillXfrmSelector(record, &usersa->sel);
+    // Use AF_UNSPEC for all SAs. In transport mode, kernel picks selector family based on
+    // usersa->family, while in tunnel mode, the XFRM_STATE_AF_UNSPEC flag allows dual-stack SAs.
+    fillXfrmSelector(AF_UNSPEC, &usersa->sel);
 
     usersa->id.proto = IPPROTO_ESP;
     usersa->id.spi = record.spi;
@@ -984,7 +1071,7 @@
     return sizeof(*usersa);
 }
 
-int XfrmController::fillUserSaId(const XfrmId& record, xfrm_usersa_id* said) {
+int XfrmController::fillUserSaId(const XfrmCommonInfo& record, xfrm_usersa_id* said) {
     said->daddr = record.dstAddr;
     said->spi = record.spi;
     said->family = record.addrFamily;
@@ -993,19 +1080,22 @@
     return sizeof(*said);
 }
 
-netdutils::Status XfrmController::deleteSecurityAssociation(const XfrmId& record,
+netdutils::Status XfrmController::deleteSecurityAssociation(const XfrmCommonInfo& record,
                                                             const XfrmSocket& sock) {
     xfrm_usersa_id said{};
     nlattr_xfrm_mark xfrmmark{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
-    enum { NLMSG_HDR, USERSAID, USERSAID_PAD, MARK, MARK_PAD };
+    enum { NLMSG_HDR, USERSAID, USERSAID_PAD, MARK, MARK_PAD, INTF_ID, INTF_ID_PAD };
 
     std::vector<iovec> iov = {
-        {NULL, 0},      // reserved for the eventual addition of a NLMSG_HDR
-        {&said, 0},     // main usersa_info struct
-        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
-        {&xfrmmark, 0}, // adjust size if xfrm mark is present
-        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
+            {&said, 0},        // main usersa_info struct
+            {kPadBytes, 0},    // up to NLMSG_ALIGNTO pad bytes of padding
+            {&xfrmmark, 0},    // adjust size if xfrm mark is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},  // adjust size if interface ID is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
     int len;
@@ -1015,6 +1105,9 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(XFRM_MSG_DELSA, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
@@ -1026,7 +1119,7 @@
     enum { NLMSG_HDR, USERSAID, USERSAID_PAD };
 
     std::vector<iovec> iov = {
-        {NULL, 0},      // reserved for the eventual addition of a NLMSG_HDR
+        {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
         {&spiInfo, 0},  // main userspi_info struct
         {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
     };
@@ -1066,13 +1159,14 @@
     return ret;
 }
 
-netdutils::Status XfrmController::updateTunnelModeSecurityPolicy(const XfrmSaInfo& record,
+netdutils::Status XfrmController::updateTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                                  const XfrmSocket& sock,
                                                                  XfrmDirection direction,
                                                                  uint16_t msgType) {
     xfrm_userpolicy_info userpolicy{};
     nlattr_user_tmpl usertmpl{};
     nlattr_xfrm_mark xfrmmark{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
     enum {
         NLMSG_HDR,
@@ -1082,20 +1176,24 @@
         USERTMPL_PAD,
         MARK,
         MARK_PAD,
+        INTF_ID,
+        INTF_ID_PAD,
     };
 
     std::vector<iovec> iov = {
-        {NULL, 0},        // reserved for the eventual addition of a NLMSG_HDR
-        {&userpolicy, 0}, // main xfrm_userpolicy_info struct
-        {kPadBytes, 0},   // up to NLMSG_ALIGNTO pad bytes of padding
-        {&usertmpl, 0},   // adjust size if xfrm_user_tmpl struct is present
-        {kPadBytes, 0},   // up to NLATTR_ALIGNTO pad bytes
-        {&xfrmmark, 0},   // adjust size if xfrm mark is present
-        {kPadBytes, 0},   // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
+            {&userpolicy, 0},  // main xfrm_userpolicy_info struct
+            {kPadBytes, 0},    // up to NLMSG_ALIGNTO pad bytes of padding
+            {&usertmpl, 0},    // adjust size if xfrm_user_tmpl struct is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrmmark, 0},    // adjust size if xfrm mark is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},  // adjust size if interface ID is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
     int len;
-    len = iov[USERPOLICY].iov_len = fillTransportModeUserSpInfo(record, direction, &userpolicy);
+    len = iov[USERPOLICY].iov_len = fillUserSpInfo(record, direction, &userpolicy);
     iov[USERPOLICY_PAD].iov_len = NLMSG_ALIGN(len) - len;
 
     len = iov[USERTMPL].iov_len = fillNlAttrUserTemplate(record, &usertmpl);
@@ -1104,14 +1202,18 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(msgType, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
-netdutils::Status XfrmController::deleteTunnelModeSecurityPolicy(const XfrmSaInfo& record,
+netdutils::Status XfrmController::deleteTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                                  const XfrmSocket& sock,
                                                                  XfrmDirection direction) {
     xfrm_userpolicy_id policyid{};
     nlattr_xfrm_mark xfrmmark{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
     enum {
         NLMSG_HDR,
@@ -1119,14 +1221,18 @@
         USERPOLICYID_PAD,
         MARK,
         MARK_PAD,
+        INTF_ID,
+        INTF_ID_PAD,
     };
 
     std::vector<iovec> iov = {
-        {NULL, 0},      // reserved for the eventual addition of a NLMSG_HDR
-        {&policyid, 0}, // main xfrm_userpolicy_id struct
-        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
-        {&xfrmmark, 0}, // adjust size if xfrm mark is present
-        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
+            {&policyid, 0},    // main xfrm_userpolicy_id struct
+            {kPadBytes, 0},    // up to NLMSG_ALIGNTO pad bytes of padding
+            {&xfrmmark, 0},    // adjust size if xfrm mark is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},  // adjust size if interface ID is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
     int len = iov[USERPOLICYID].iov_len = fillUserPolicyId(record, direction, &policyid);
@@ -1135,12 +1241,15 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(XFRM_MSG_DELPOLICY, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
-int XfrmController::fillTransportModeUserSpInfo(const XfrmSaInfo& record, XfrmDirection direction,
-                                                xfrm_userpolicy_info* usersp) {
-    fillXfrmSelector(record, &usersp->sel);
+int XfrmController::fillUserSpInfo(const XfrmSpInfo& record, XfrmDirection direction,
+                                   xfrm_userpolicy_info* usersp) {
+    fillXfrmSelector(record.selAddrFamily, &usersp->sel);
     fillXfrmLifetimeDefaults(&usersp->lft);
     fillXfrmCurLifetimeDefaults(&usersp->curlft);
     /* if (index) index & 0x3 == dir -- must be true
@@ -1153,7 +1262,7 @@
     return sizeof(*usersp);
 }
 
-int XfrmController::fillUserTemplate(const XfrmSaInfo& record, xfrm_user_tmpl* tmpl) {
+int XfrmController::fillUserTemplate(const XfrmSpInfo& record, xfrm_user_tmpl* tmpl) {
     tmpl->id.daddr = record.dstAddr;
     tmpl->id.spi = record.spi;
     tmpl->id.proto = IPPROTO_ESP;
@@ -1172,7 +1281,7 @@
     return sizeof(xfrm_user_tmpl*);
 }
 
-int XfrmController::fillNlAttrUserTemplate(const XfrmSaInfo& record, nlattr_user_tmpl* tmpl) {
+int XfrmController::fillNlAttrUserTemplate(const XfrmSpInfo& record, nlattr_user_tmpl* tmpl) {
     fillUserTemplate(record, &tmpl->tmpl);
 
     int len = NLA_HDRLEN + sizeof(xfrm_user_tmpl);
@@ -1180,7 +1289,12 @@
     return len;
 }
 
-int XfrmController::fillNlAttrXfrmMark(const XfrmId& record, nlattr_xfrm_mark* mark) {
+int XfrmController::fillNlAttrXfrmMark(const XfrmCommonInfo& record, nlattr_xfrm_mark* mark) {
+    // Do not set if we were not given a mark
+    if (record.mark.v == 0 && record.mark.m == 0) {
+        return 0;
+    }
+
     mark->mark.v = record.mark.v; // set to 0 if it's not used
     mark->mark.m = record.mark.m; // set to 0 if it's not used
     int len = NLA_HDRLEN + sizeof(xfrm_mark);
@@ -1188,63 +1302,192 @@
     return len;
 }
 
-int XfrmController::fillNlAttrXfrmOutputMark(const __u32 output_mark_value,
+// This function sets the output mark (or set-mark in newer kernels) to that of the underlying
+// Network's netid. This allows outbound IPsec Tunnel mode packets to be correctly directed to a
+// preselected underlying Network. Packet as marked as protected from VPNs and have a network
+// explicitly selected to prevent interference or routing loops. Also set permission flag to
+// PERMISSION_SYSTEM to ensure we can use background/restricted networks. Permission to use
+// restricted networks is checked in IpSecService.
+int XfrmController::fillNlAttrXfrmOutputMark(const __u32 underlyingNetId,
                                              nlattr_xfrm_output_mark* output_mark) {
     // Do not set if we were not given an output mark
-    if (output_mark_value == 0) {
+    if (underlyingNetId == 0) {
         return 0;
     }
 
-    output_mark->outputMark = output_mark_value;
+    Fwmark fwmark;
+    fwmark.netId = underlyingNetId;
+
+    // TODO: Rework this to more accurately follow the underlying network
+    fwmark.permission = PERMISSION_SYSTEM;
+    fwmark.explicitlySelected = true;
+    fwmark.protectedFromVpn = true;
+    output_mark->outputMark = fwmark.intValue;
+
     int len = NLA_HDRLEN + sizeof(__u32);
     fillXfrmNlaHdr(&output_mark->hdr, XFRMA_OUTPUT_MARK, len);
     return len;
 }
 
-int XfrmController::fillUserPolicyId(const XfrmSaInfo& record, XfrmDirection direction,
+int XfrmController::fillNlAttrXfrmIntfId(const uint32_t intfIdValue,
+                                         nlattr_xfrm_interface_id* intf_id) {
+    // Do not set if we were not given an interface id
+    if (intfIdValue == 0) {
+        return 0;
+    }
+
+    intf_id->if_id = intfIdValue;
+    int len = NLA_HDRLEN + sizeof(__u32);
+    fillXfrmNlaHdr(&intf_id->hdr, XFRMA_IF_ID, len);
+    return len;
+}
+
+int XfrmController::fillUserPolicyId(const XfrmSpInfo& record, XfrmDirection direction,
                                      xfrm_userpolicy_id* usersp) {
     // For DELPOLICY, when index is absent, selector is needed to match the policy
-    fillXfrmSelector(record, &usersp->sel);
+    fillXfrmSelector(record.selAddrFamily, &usersp->sel);
     usersp->dir = static_cast<uint8_t>(direction);
     return sizeof(*usersp);
 }
 
-int XfrmController::addVirtualTunnelInterface(const std::string& deviceName,
-                                              const std::string& localAddress,
-                                              const std::string& remoteAddress, int32_t ikey,
-                                              int32_t okey, bool isUpdate) {
+netdutils::Status XfrmController::ipSecAddTunnelInterface(const std::string& deviceName,
+                                                          const std::string& localAddress,
+                                                          const std::string& remoteAddress,
+                                                          int32_t ikey, int32_t okey,
+                                                          int32_t interfaceId, bool isUpdate) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("deviceName=%s", deviceName.c_str());
     ALOGD("localAddress=%s", localAddress.c_str());
     ALOGD("remoteAddress=%s", remoteAddress.c_str());
     ALOGD("ikey=%0.8x", ikey);
     ALOGD("okey=%0.8x", okey);
+    ALOGD("interfaceId=%0.8x", interfaceId);
     ALOGD("isUpdate=%d", isUpdate);
 
-    if (deviceName.empty() || localAddress.empty() || remoteAddress.empty()) {
-        return EINVAL;
+    uint16_t flags = isUpdate ? NETLINK_REQUEST_FLAGS : NETLINK_ROUTE_CREATE_FLAGS;
+
+    if (mIsXfrmIntfSupported) {
+        return ipSecAddXfrmInterface(deviceName, interfaceId, flags);
+    } else {
+        return ipSecAddVirtualTunnelInterface(deviceName, localAddress, remoteAddress, ikey, okey,
+                                              flags);
+    }
+}
+
+netdutils::Status XfrmController::ipSecAddXfrmInterface(const std::string& deviceName,
+                                                        int32_t interfaceId, uint16_t flags) {
+    ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
+
+    if (deviceName.empty()) {
+        return netdutils::statusFromErrno(EINVAL, "XFRM Interface deviceName empty");
     }
 
-    const char* INFO_KIND_VTI6 = "vti6";
-    const char* INFO_KIND_VTI = "vti";
+    ifinfomsg ifInfoMsg{};
+
+    struct XfrmIntfCreateReq {
+        nlattr ifNameNla;
+        char ifName[IFNAMSIZ];  // Already aligned
+
+        nlattr linkInfoNla;
+        struct LinkInfo {
+            nlattr infoKindNla;
+            char infoKind[INFO_KIND_MAX_LEN];  // Already aligned
+
+            nlattr infoDataNla;
+            struct InfoData {
+                nlattr xfrmLinkNla;
+                uint32_t xfrmLink;
+
+                nlattr xfrmIfIdNla;
+                uint32_t xfrmIfId;
+            } infoData;  // Already aligned
+
+        } linkInfo;  // Already aligned
+    } xfrmIntfCreateReq{
+            .ifNameNla =
+                    {
+                            .nla_len = RTA_LENGTH(IFNAMSIZ),
+                            .nla_type = IFLA_IFNAME,
+                    },
+            // Update .ifName via strlcpy
+
+            .linkInfoNla =
+                    {
+                            .nla_len = RTA_LENGTH(sizeof(XfrmIntfCreateReq::LinkInfo)),
+                            .nla_type = IFLA_LINKINFO,
+                    },
+            .linkInfo = {.infoKindNla =
+                                 {
+                                         .nla_len = RTA_LENGTH(INFO_KIND_MAX_LEN),
+                                         .nla_type = IFLA_INFO_KIND,
+                                 },
+                         // Update .infoKind via strlcpy
+
+                         .infoDataNla =
+                                 {
+                                         .nla_len = RTA_LENGTH(
+                                                 sizeof(XfrmIntfCreateReq::LinkInfo::InfoData)),
+                                         .nla_type = IFLA_INFO_DATA,
+                                 },
+                         .infoData = {
+                                 .xfrmLinkNla =
+                                         {
+                                                 .nla_len = RTA_LENGTH(sizeof(uint32_t)),
+                                                 .nla_type = IFLA_XFRM_LINK,
+                                         },
+                                 //   Always use LOOPBACK_IFINDEX, since we use output marks for
+                                 //   route lookup instead. The use case of having a Network with
+                                 //   loopback in it is unsupported in tunnel mode.
+                                 .xfrmLink = static_cast<uint32_t>(LOOPBACK_IFINDEX),
+
+                                 .xfrmIfIdNla =
+                                         {
+                                                 .nla_len = RTA_LENGTH(sizeof(uint32_t)),
+                                                 .nla_type = IFLA_XFRM_IF_ID,
+                                         },
+                                 .xfrmIfId = static_cast<uint32_t>(interfaceId),
+                         }}};
+
+    strlcpy(xfrmIntfCreateReq.ifName, deviceName.c_str(), IFNAMSIZ);
+    strlcpy(xfrmIntfCreateReq.linkInfo.infoKind, INFO_KIND_XFRMI, INFO_KIND_MAX_LEN);
+
+    iovec iov[] = {
+            {NULL, 0},  // reserved for the eventual addition of a NLMSG_HDR
+            {&ifInfoMsg, sizeof(ifInfoMsg)},
+
+            {&xfrmIntfCreateReq, sizeof(xfrmIntfCreateReq)},
+    };
+
+    // sendNetlinkRequest returns -errno
+    int ret = -sendNetlinkRequest(RTM_NEWLINK, flags, iov, ARRAY_SIZE(iov), nullptr);
+    return netdutils::statusFromErrno(ret, "Add/update xfrm interface");
+}
+
+netdutils::Status XfrmController::ipSecAddVirtualTunnelInterface(const std::string& deviceName,
+                                                                 const std::string& localAddress,
+                                                                 const std::string& remoteAddress,
+                                                                 int32_t ikey, int32_t okey,
+                                                                 uint16_t flags) {
+    ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
+
+    if (deviceName.empty() || localAddress.empty() || remoteAddress.empty()) {
+        return netdutils::statusFromErrno(EINVAL, "Required VTI creation parameter not provided");
+    }
+
     uint8_t PADDING_BUFFER[] = {0, 0, 0, 0};
 
     // Find address family.
     uint8_t remAddr[sizeof(in6_addr)];
 
     StatusOr<uint16_t> statusOrRemoteFam = convertStringAddress(remoteAddress, remAddr);
-    if (!isOk(statusOrRemoteFam)) {
-        return statusOrRemoteFam.status().code();
-    }
+    RETURN_IF_NOT_OK(statusOrRemoteFam);
 
     uint8_t locAddr[sizeof(in6_addr)];
     StatusOr<uint16_t> statusOrLocalFam = convertStringAddress(localAddress, locAddr);
-    if (!isOk(statusOrLocalFam)) {
-        return statusOrLocalFam.status().code();
-    }
+    RETURN_IF_NOT_OK(statusOrLocalFam);
 
     if (statusOrLocalFam.value() != statusOrRemoteFam.value()) {
-        return EINVAL;
+        return netdutils::statusFromErrno(EINVAL, "Local and remote address families do not match");
     }
 
     uint16_t family = statusOrLocalFam.value();
@@ -1283,18 +1526,16 @@
                             netdutils::makeSlice(binaryRemoteAddress));
 
     // Construct IFLA_VTI_OKEY
-    nlattr iflaVtiIKey;
-    uint32_t iKeyValue;
-    size_t iflaVtiIKeyPad = fillNlAttrU32(IFLA_VTI_IKEY, ikey, &iflaVtiIKey, &iKeyValue);
+    nlattr_payload_u32 iflaVtiIKey;
+    size_t iflaVtiIKeyPad = fillNlAttrU32(IFLA_VTI_IKEY, htonl(ikey), &iflaVtiIKey);
 
     // Construct IFLA_VTI_IKEY
-    nlattr iflaVtiOKey;
-    uint32_t oKeyValue;
-    size_t iflaVtiOKeyPad = fillNlAttrU32(IFLA_VTI_OKEY, okey, &iflaVtiOKey, &oKeyValue);
+    nlattr_payload_u32 iflaVtiOKey;
+    size_t iflaVtiOKeyPad = fillNlAttrU32(IFLA_VTI_OKEY, htonl(okey), &iflaVtiOKey);
 
     int iflaInfoDataPayloadLength = iflaVtiLocal.nla_len + iflaVtiLocalPad + iflaVtiRemote.nla_len +
-                                    iflaVtiRemotePad + iflaVtiIKey.nla_len + iflaVtiIKeyPad +
-                                    iflaVtiOKey.nla_len + iflaVtiOKeyPad;
+                                    iflaVtiRemotePad + iflaVtiIKey.hdr.nla_len + iflaVtiIKeyPad +
+                                    iflaVtiOKey.hdr.nla_len + iflaVtiOKeyPad;
 
     // Construct IFLA_INFO_DATA
     nlattr iflaInfoData;
@@ -1308,64 +1549,51 @@
                                         &iflaLinkInfo);
 
     iovec iov[] = {
-        {NULL, 0},
-        {&ifInfoMsg, sizeof(ifInfoMsg)},
+            {nullptr, 0},
+            {&ifInfoMsg, sizeof(ifInfoMsg)},
 
-        {&iflaIfName, sizeof(iflaIfName)},
-        {iflaIfNameStrValue, iflaIfNameLength},
-        {&PADDING_BUFFER, iflaIfNamePad},
+            {&iflaIfName, sizeof(iflaIfName)},
+            {iflaIfNameStrValue, iflaIfNameLength},
+            {&PADDING_BUFFER, iflaIfNamePad},
 
-        {&iflaLinkInfo, sizeof(iflaLinkInfo)},
+            {&iflaLinkInfo, sizeof(iflaLinkInfo)},
 
-        {&iflaIfInfoKind, sizeof(iflaIfInfoKind)},
-        {infoKindValueStrValue, iflaIfInfoKindLength},
-        {&PADDING_BUFFER, iflaIfInfoKindPad},
+            {&iflaIfInfoKind, sizeof(iflaIfInfoKind)},
+            {infoKindValueStrValue, iflaIfInfoKindLength},
+            {&PADDING_BUFFER, iflaIfInfoKindPad},
 
-        {&iflaInfoData, sizeof(iflaInfoData)},
+            {&iflaInfoData, sizeof(iflaInfoData)},
 
-        {&iflaVtiLocal, sizeof(iflaVtiLocal)},
-        {&binaryLocalAddress, (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr)},
-        {&PADDING_BUFFER, iflaVtiLocalPad},
+            {&iflaVtiLocal, sizeof(iflaVtiLocal)},
+            {&binaryLocalAddress, (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr)},
+            {&PADDING_BUFFER, iflaVtiLocalPad},
 
-        {&iflaVtiRemote, sizeof(iflaVtiRemote)},
-        {&binaryRemoteAddress, (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr)},
-        {&PADDING_BUFFER, iflaVtiRemotePad},
+            {&iflaVtiRemote, sizeof(iflaVtiRemote)},
+            {&binaryRemoteAddress, (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr)},
+            {&PADDING_BUFFER, iflaVtiRemotePad},
 
-        {&iflaVtiIKey, sizeof(iflaVtiIKey)},
-        {&iKeyValue, sizeof(iKeyValue)},
-        {&PADDING_BUFFER, iflaVtiIKeyPad},
+            {&iflaVtiIKey, iflaVtiIKey.hdr.nla_len},
+            {&PADDING_BUFFER, iflaVtiIKeyPad},
 
-        {&iflaVtiOKey, sizeof(iflaVtiOKey)},
-        {&oKeyValue, sizeof(oKeyValue)},
-        {&PADDING_BUFFER, iflaVtiOKeyPad},
+            {&iflaVtiOKey, iflaVtiOKey.hdr.nla_len},
+            {&PADDING_BUFFER, iflaVtiOKeyPad},
 
-        {&PADDING_BUFFER, iflaInfoDataPad},
+            {&PADDING_BUFFER, iflaInfoDataPad},
 
-        {&PADDING_BUFFER, iflaLinkInfoPad},
+            {&PADDING_BUFFER, iflaLinkInfoPad},
     };
 
-    uint16_t action = RTM_NEWLINK;
-    uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
-
-    if (!isUpdate) {
-        flags |= NLM_F_EXCL | NLM_F_CREATE;
-    }
-
     // sendNetlinkRequest returns -errno
-    int ret = -1 * sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr);
-    if (ret) {
-        ALOGE("Error in %s virtual tunnel interface. Error Code: %d",
-              isUpdate ? "updating" : "adding", ret);
-    }
-    return ret;
+    int ret = -1 * sendNetlinkRequest(RTM_NEWLINK, flags, iov, ARRAY_SIZE(iov), nullptr);
+    return netdutils::statusFromErrno(ret, "Failed to add/update virtual tunnel interface");
 }
 
-int XfrmController::removeVirtualTunnelInterface(const std::string& deviceName) {
+netdutils::Status XfrmController::ipSecRemoveTunnelInterface(const std::string& deviceName) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("deviceName=%s", deviceName.c_str());
 
     if (deviceName.empty()) {
-        return EINVAL;
+        return netdutils::statusFromErrno(EINVAL, "Required parameter not provided");
     }
 
     uint8_t PADDING_BUFFER[] = {0, 0, 0, 0};
@@ -1378,7 +1606,7 @@
     size_t iflaIfNamePad = fillNlAttr(IFLA_IFNAME, iflaIfNameLength, &iflaIfName);
 
     iovec iov[] = {
-        {NULL, 0},
+        {nullptr, 0},
         {&ifInfoMsg, sizeof(ifInfoMsg)},
 
         {&iflaIfName, sizeof(iflaIfName)},
@@ -1391,11 +1619,15 @@
 
     // sendNetlinkRequest returns -errno
     int ret = -1 * sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr);
-    if (ret) {
-        ALOGE("Error in removing virtual tunnel interface %s. Error Code: %d", iflaIfNameStrValue,
-              ret);
-    }
-    return ret;
+    return netdutils::statusFromErrno(ret, "Error in deleting IpSec interface " + deviceName);
+}
+
+void XfrmController::dump(DumpWriter& dw) {
+    ScopedIndent indentForXfrmController(dw);
+    dw.println("XfrmController");
+
+    ScopedIndent indentForXfrmISupport(dw);
+    dw.println("XFRM-I support: %d", mIsXfrmIntfSupported);
 }
 
 } // namespace net
diff --git a/server/XfrmController.h b/server/XfrmController.h
index f21dfa1..a38bbeb 100644
--- a/server/XfrmController.h
+++ b/server/XfrmController.h
@@ -22,16 +22,20 @@
 #include <string>
 #include <utility> // for pair
 
+#include <linux/if.h>
 #include <linux/if_link.h>
 #include <linux/if_tunnel.h>
 #include <linux/netlink.h>
 #include <linux/udp.h>
 #include <linux/xfrm.h>
-#include <sysutils/SocketClient.h>
+#include <unistd.h>
 
 #include "NetdConstants.h"
+#include "android-base/unique_fd.h"
+#include "netdutils/DumpWriter.h"
 #include "netdutils/Slice.h"
 #include "netdutils/Status.h"
+#include "sysutils/SocketClient.h"
 
 namespace android {
 namespace net {
@@ -103,16 +107,17 @@
 };
 
 // minimally sufficient structure to match either an SA or a Policy
-struct XfrmId {
+struct XfrmCommonInfo {
     xfrm_address_t dstAddr; // network order
     xfrm_address_t srcAddr;
     int addrFamily;  // AF_INET or AF_INET6
     int transformId; // requestId
     int spi;
     xfrm_mark mark;
+    int xfrm_if_id;
 };
 
-struct XfrmSaInfo : XfrmId {
+struct XfrmSaInfo : XfrmCommonInfo {
     XfrmAlgo auth;
     XfrmAlgo crypt;
     XfrmAlgo aead;
@@ -121,65 +126,140 @@
     XfrmEncap encap;
 };
 
+struct XfrmSpInfo : XfrmSaInfo {
+    // Address family in XfrmCommonInfo used for template/SA matching, need separate addrFamily
+    // for selectors
+    int selAddrFamily;  // AF_INET or AF_INET6
+};
+
+/*
+ * This is a workaround for a kernel bug in the 32bit netlink compat layer
+ * that has been present on x86_64 kernels since 2010 with no fix on the
+ * horizon.
+ *
+ * Below is a redefinition of the xfrm_usersa_info struct that is part
+ * of the Linux uapi <linux/xfrm.h> to align the structures to a 64-bit
+ * boundary.
+ *
+ * Note that we turn this on for all x86 32bit targets, under the assumption
+ * that nowadays all x86 targets are running 64bit kernels.
+ */
+#if defined(__i386__)
+// Shadow the kernel definition of xfrm_usersa_info with a 64-bit aligned version
+struct xfrm_usersa_info : ::xfrm_usersa_info {
+} __attribute__((aligned(8)));
+// Shadow the kernel's version, using the aligned version of xfrm_usersa_info
+struct xfrm_userspi_info {
+    struct xfrm_usersa_info info;
+    __u32 min;
+    __u32 max;
+};
+struct xfrm_userpolicy_info : ::xfrm_userpolicy_info {
+} __attribute__((aligned(8)));
+
+/*
+ * Anyone who encounters a failure when sending netlink messages should look here
+ * first. Hitting the static_assert() below should be a strong hint that Android
+ * IPsec will probably not work with your current settings.
+ *
+ * Again, experimentally determined, the "flags" field should be the first byte in
+ * the final word of the xfrm_usersa_info struct. The check validates the size of
+ * the padding to be 7.
+ *
+ * This padding is verified to be correct on gcc/x86_64 kernel, and clang/x86 userspace.
+ */
+static_assert(sizeof(::xfrm_usersa_info) % 8 != 0,
+              "struct xfrm_usersa_info has changed "
+              "alignment. Please consider whether this "
+              "patch is needed.");
+static_assert(sizeof(xfrm_usersa_info) - offsetof(xfrm_usersa_info, flags) == 8,
+              "struct xfrm_usersa_info probably misaligned with kernel struct.");
+static_assert(sizeof(xfrm_usersa_info) % 8 == 0,
+              "struct xfrm_usersa_info_t is not 64-bit  "
+              "aligned. Please consider whether this patch "
+              "is needed.");
+static_assert(sizeof(::xfrm_userspi_info) - sizeof(::xfrm_usersa_info) ==
+                      sizeof(xfrm_userspi_info) - sizeof(xfrm_usersa_info),
+              "struct xfrm_userspi_info has changed and does not match the kernel struct.");
+static_assert(sizeof(::xfrm_userpolicy_info) % 8 != 0,
+              "struct xfrm_userpolicy_info has changed "
+              "alignment. Please consider whether this "
+              "patch is needed.");
+static_assert(sizeof(xfrm_userpolicy_info) - offsetof(xfrm_userpolicy_info, share) == 5,
+              "struct xfrm_userpolicy_info probably misaligned with kernel struct.");
+static_assert(sizeof(xfrm_userpolicy_info) % 8 == 0,
+              "struct xfrm_userpolicy_info is not 64-bit "
+              "aligned. Please consider whether this patch "
+              "is needed.");
+#endif
+
 class XfrmController {
 public:
     XfrmController();
 
+    // Initializer to override XFRM-I support for unit-testing purposes
+    explicit XfrmController(bool xfrmIntfSupport);
+
     static netdutils::Status Init();
 
-    static netdutils::Status ipSecSetEncapSocketOwner(const android::base::unique_fd& socket,
-                                                      int newUid, uid_t callerUid);
+    static netdutils::Status ipSecSetEncapSocketOwner(int socketFd, int newUid, uid_t callerUid);
 
     static netdutils::Status ipSecAllocateSpi(int32_t transformId, const std::string& localAddress,
                                               const std::string& remoteAddress, int32_t inSpi,
                                               int32_t* outSpi);
 
     static netdutils::Status ipSecAddSecurityAssociation(
-        int32_t transformId, int32_t mode, const std::string& sourceAddress,
-        const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
-        int32_t markValue, int32_t markMask, const std::string& authAlgo,
-        const std::vector<uint8_t>& authKey, int32_t authTruncBits, const std::string& cryptAlgo,
-        const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits, const std::string& aeadAlgo,
-        const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
-        int32_t encapLocalPort, int32_t encapRemotePort);
+            int32_t transformId, int32_t mode, const std::string& sourceAddress,
+            const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+            int32_t markValue, int32_t markMask, const std::string& authAlgo,
+            const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+            const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey,
+            int32_t cryptTruncBits, const std::string& aeadAlgo,
+            const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+            int32_t encapLocalPort, int32_t encapRemotePort, int32_t xfrmInterfaceId);
 
     static netdutils::Status ipSecDeleteSecurityAssociation(int32_t transformId,
                                                             const std::string& sourceAddress,
                                                             const std::string& destinationAddress,
                                                             int32_t spi, int32_t markValue,
-                                                            int32_t markMask);
+                                                            int32_t markMask,
+                                                            int32_t xfrmInterfaceId);
 
-    static netdutils::Status
-    ipSecApplyTransportModeTransform(const android::base::unique_fd& socket, int32_t transformId,
-                                     int32_t direction, const std::string& localAddress,
-                                     const std::string& remoteAddress, int32_t spi);
+    static netdutils::Status ipSecApplyTransportModeTransform(int socketFd, int32_t transformId,
+                                                              int32_t direction,
+                                                              const std::string& localAddress,
+                                                              const std::string& remoteAddress,
+                                                              int32_t spi);
 
-    static netdutils::Status
-    ipSecRemoveTransportModeTransform(const android::base::unique_fd& socket);
+    static netdutils::Status ipSecRemoveTransportModeTransform(int socketFd);
 
-    static netdutils::Status ipSecAddSecurityPolicy(int32_t transformId, int32_t direction,
-                                                    const std::string& sourceAddress,
-                                                    const std::string& destinationAddress,
-                                                    int32_t spi, int32_t markValue,
-                                                    int32_t markMask);
+    static netdutils::Status ipSecAddSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                                    int32_t direction,
+                                                    const std::string& tmplSrcAddress,
+                                                    const std::string& tmplDstAddress, int32_t spi,
+                                                    int32_t markValue, int32_t markMask,
+                                                    int32_t xfrmInterfaceId);
 
-    static netdutils::Status ipSecUpdateSecurityPolicy(int32_t transformId, int32_t direction,
-                                                       const std::string& sourceAddress,
-                                                       const std::string& destinationAddress,
+    static netdutils::Status ipSecUpdateSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                                       int32_t direction,
+                                                       const std::string& tmplSrcAddress,
+                                                       const std::string& tmplDstAddress,
                                                        int32_t spi, int32_t markValue,
-                                                       int32_t markMask);
+                                                       int32_t markMask, int32_t xfrmInterfaceId);
 
-    static netdutils::Status ipSecDeleteSecurityPolicy(int32_t transformId, int32_t direction,
-                                                       const std::string& sourceAddress,
-                                                       const std::string& destinationAddress,
-                                                       int32_t markValue, int32_t markMask);
+    static netdutils::Status ipSecDeleteSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                                       int32_t direction, int32_t markValue,
+                                                       int32_t markMask, int32_t xfrmInterfaceId);
 
-    static int addVirtualTunnelInterface(const std::string& deviceName,
-                                         const std::string& localAddress,
-                                         const std::string& remoteAddress, int32_t ikey,
-                                         int32_t okey, bool isUpdate);
+    static netdutils::Status ipSecAddTunnelInterface(const std::string& deviceName,
+                                                     const std::string& localAddress,
+                                                     const std::string& remoteAddress, int32_t ikey,
+                                                     int32_t okey, int32_t interfaceId,
+                                                     bool isUpdate);
 
-    static int removeVirtualTunnelInterface(const std::string& deviceName);
+    static netdutils::Status ipSecRemoveTunnelInterface(const std::string& deviceName);
+
+    void dump(netdutils::DumpWriter& dw);
 
     // Some XFRM netlink attributes comprise a header, a struct, and some data
     // after the struct. We wrap all of those in one struct for easier
@@ -241,52 +321,31 @@
         __u32 outputMark;
     };
 
-private:
-/*
- * Below is a redefinition of the xfrm_usersa_info struct that is part
- * of the Linux uapi <linux/xfrm.h> to align the structures to a 64-bit
- * boundary.
- */
-#ifdef NETLINK_COMPAT32
-    // Shadow the kernel definition of xfrm_usersa_info with a 64-bit aligned version
-    struct xfrm_usersa_info : ::xfrm_usersa_info {
-    } __attribute__((aligned(8)));
-    // Shadow the kernel's version, using the aligned version of xfrm_usersa_info
-    struct xfrm_userspi_info {
-        struct xfrm_usersa_info info;
-        __u32 min;
-        __u32 max;
+    // Container for the content of an XFRMA_IF_ID netlink attribute.
+    // Exposed for testing
+    struct nlattr_xfrm_interface_id {
+        nlattr hdr;
+        __u32 if_id;
     };
 
-    /*
-     * Anyone who encounters a failure when sending netlink messages should look here
-     * first. Hitting the static_assert() below should be a strong hint that Android
-     * IPsec will probably not work with your current settings.
-     *
-     * Again, experimentally determined, the "flags" field should be the first byte in
-     * the final word of the xfrm_usersa_info struct. The check validates the size of
-     * the padding to be 7.
-     *
-     * This padding is verified to be correct on gcc/x86_64 kernel, and clang/x86 userspace.
-     */
-    static_assert(sizeof(::xfrm_usersa_info) % 8 != 0, "struct xfrm_usersa_info has changed "
-                                                       "alignment. Please consider whether this "
-                                                       "patch is needed.");
-    static_assert(sizeof(xfrm_usersa_info) - offsetof(xfrm_usersa_info, flags) == 8,
-                  "struct xfrm_usersa_info probably misaligned with kernel struct.");
-    static_assert(sizeof(xfrm_usersa_info) % 8 == 0, "struct xfrm_usersa_info_t is not 64-bit  "
-                                                     "aligned. Please consider whether this patch "
-                                                     "is needed.");
-    static_assert(sizeof(::xfrm_userspi_info) - sizeof(::xfrm_usersa_info) ==
-                      sizeof(xfrm_userspi_info) - sizeof(xfrm_usersa_info),
-                  "struct xfrm_userspi_info has changed and does not match the kernel struct.");
-#endif
+    // Exposed for testing
+    struct nlattr_payload_u32 {
+        nlattr hdr;
+        uint32_t value;
+    };
 
-    // helper function for filling in the XfrmId (and XfrmSaInfo) structure
-    static netdutils::Status fillXfrmId(const std::string& sourceAddress,
-                                        const std::string& destinationAddress, int32_t spi,
-                                        int32_t markValue, int32_t markMask, int32_t transformId,
-                                        XfrmId* xfrmId);
+  private:
+    static bool isXfrmIntfSupported();
+
+    // helper functions for filling in the XfrmCommonInfo (and XfrmSaInfo) structure
+    static netdutils::Status fillXfrmCommonInfo(const std::string& sourceAddress,
+                                                const std::string& destinationAddress, int32_t spi,
+                                                int32_t markValue, int32_t markMask,
+                                                int32_t transformId, int32_t xfrmInterfaceId,
+                                                XfrmCommonInfo* info);
+    static netdutils::Status fillXfrmCommonInfo(int32_t spi, int32_t markValue, int32_t markMask,
+                                                int32_t transformId, int32_t xfrmInterfaceId,
+                                                XfrmCommonInfo* info);
 
     // Top level functions for managing a Transport Mode Transform
     static netdutils::Status addTransportModeTransform(const XfrmSaInfo& record);
@@ -294,7 +353,7 @@
 
     // TODO(messagerefactor): FACTOR OUT ALL MESSAGE BUILDING CODE BELOW HERE
     // Shared between SA and SP
-    static void fillXfrmSelector(const XfrmSaInfo& record, xfrm_selector* selector);
+    static void fillXfrmSelector(const int record, xfrm_selector* selector);
 
     // Shared between Transport and Tunnel Mode
     static int fillNlAttrXfrmAlgoEnc(const XfrmAlgo& in_algo, nlattr_algo_crypt* algo);
@@ -308,39 +367,48 @@
     static int fillUserSaInfo(const XfrmSaInfo& record, xfrm_usersa_info* usersa);
 
     // Functions for deleting a Transport Mode SA
-    static netdutils::Status deleteSecurityAssociation(const XfrmId& record,
+    static netdutils::Status deleteSecurityAssociation(const XfrmCommonInfo& record,
                                                        const XfrmSocket& sock);
-    static int fillUserSaId(const XfrmId& record, xfrm_usersa_id* said);
-    static int fillUserTemplate(const XfrmSaInfo& record, xfrm_user_tmpl* tmpl);
+    static int fillUserSaId(const XfrmCommonInfo& record, xfrm_usersa_id* said);
+    static int fillUserTemplate(const XfrmSpInfo& record, xfrm_user_tmpl* tmpl);
 
-    static int fillTransportModeUserSpInfo(const XfrmSaInfo& record, XfrmDirection direction,
-                                           xfrm_userpolicy_info* usersp);
-    static int fillNlAttrUserTemplate(const XfrmSaInfo& record, nlattr_user_tmpl* tmpl);
-    static int fillUserPolicyId(const XfrmSaInfo& record, XfrmDirection direction,
+    static int fillUserSpInfo(const XfrmSpInfo& record, XfrmDirection direction,
+                              xfrm_userpolicy_info* usersp);
+    static int fillNlAttrUserTemplate(const XfrmSpInfo& record, nlattr_user_tmpl* tmpl);
+    static int fillUserPolicyId(const XfrmSpInfo& record, XfrmDirection direction,
                                 xfrm_userpolicy_id* policy_id);
-    static int fillNlAttrXfrmMark(const XfrmId& record, nlattr_xfrm_mark* mark);
-    static int fillNlAttrXfrmOutputMark(const __u32 output_mark_value,
+    static int fillNlAttrXfrmMark(const XfrmCommonInfo& record, nlattr_xfrm_mark* mark);
+    static int fillNlAttrXfrmOutputMark(const __u32 underlyingNetId,
                                         nlattr_xfrm_output_mark* output_mark);
+    static int fillNlAttrXfrmIntfId(const __u32 intf_id_value, nlattr_xfrm_interface_id* intf_id);
 
     static netdutils::Status allocateSpi(const XfrmSaInfo& record, uint32_t minSpi, uint32_t maxSpi,
                                          uint32_t* outSpi, const XfrmSocket& sock);
 
-    static netdutils::Status processSecurityPolicy(int32_t transformId, int32_t direction,
-                                                   const std::string& localAddress,
-                                                   const std::string& remoteAddress, int32_t spi,
+    static netdutils::Status processSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
+                                                   int32_t direction,
+                                                   const std::string& tmplSrcAddress,
+                                                   const std::string& tmplDstAddress, int32_t spi,
                                                    int32_t markValue, int32_t markMask,
-                                                   int32_t msgType);
-    static netdutils::Status updateTunnelModeSecurityPolicy(const XfrmSaInfo& record,
+                                                   int32_t xfrmInterfaceId, int32_t msgType);
+    static netdutils::Status updateTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                             const XfrmSocket& sock,
                                                             XfrmDirection direction,
                                                             uint16_t msgType);
-    static netdutils::Status deleteTunnelModeSecurityPolicy(const XfrmSaInfo& record,
+    static netdutils::Status deleteTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                             const XfrmSocket& sock,
                                                             XfrmDirection direction);
     static netdutils::Status flushInterfaces();
     static netdutils::Status flushSaDb(const XfrmSocket& s);
     static netdutils::Status flushPolicyDb(const XfrmSocket& s);
 
+    static netdutils::Status ipSecAddXfrmInterface(const std::string& deviceName,
+                                                   int32_t interfaceId, uint16_t flags);
+    static netdutils::Status ipSecAddVirtualTunnelInterface(const std::string& deviceName,
+                                                            const std::string& localAddress,
+                                                            const std::string& remoteAddress,
+                                                            int32_t ikey, int32_t okey,
+                                                            uint16_t flags);
     // END TODO(messagerefactor)
 };
 
diff --git a/server/XfrmControllerTest.cpp b/server/XfrmControllerTest.cpp
index 5715a6b..36af67a 100644
--- a/server/XfrmControllerTest.cpp
+++ b/server/XfrmControllerTest.cpp
@@ -43,12 +43,12 @@
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 
+#include "Fwmark.h"
 #include "NetdConstants.h"
 #include "NetlinkCommands.h"
-#include "Stopwatch.h"
+#include "Permission.h"
 #include "XfrmController.h"
 #include "android/net/INetd.h"
-#include "android/net/UidRange.h"
 #include "binder/IServiceManager.h"
 #include "netdutils/MockSyscalls.h"
 #include "netdutils/Netlink.h"
@@ -59,8 +59,6 @@
 using android::netdutils::MockSyscalls;
 using android::netdutils::Slice;
 using android::netdutils::Status;
-using android::netdutils::StatusOr;
-using android::netdutils::Syscalls;
 
 using ::testing::_;
 using ::testing::DoAll;
@@ -102,7 +100,8 @@
 static constexpr int DROID_SPI = 0xD1201D;
 static constexpr size_t KEY_LENGTH = 32;
 static constexpr int NLMSG_DEFAULTSIZE = 8192;
-static constexpr uint32_t TEST_XFRM_OUTPUT_MARK = 0x512;
+static constexpr uint32_t TEST_XFRM_UNDERLYING_NET = 0x512;
+static constexpr uint32_t TEST_XFRM_IF_ID = 0x1234;
 static constexpr uint32_t TEST_XFRM_MARK = 0x123;
 static constexpr uint32_t TEST_XFRM_MASK = 0xFFFFFFFF;
 
@@ -130,26 +129,10 @@
 }
 
 class XfrmControllerTest : public ::testing::Test {
-public:
-    MockSyscalls mockSyscalls;
-
-    void SetUp() override { netdutils::sSyscalls.swap(mockSyscalls); }
+  public:
+    testing::StrictMock<netdutils::ScopedMockSyscalls> mockSyscalls;
 };
 
-// Test class allowing IPv4/IPv6 parameterized tests.
-class XfrmControllerParameterizedTest : public XfrmControllerTest,
-                                        public ::testing::WithParamInterface<int> {};
-
-// Helper to make generated test names readable.
-std::string FamilyName(::testing::TestParamInfo<int> info) {
-    switch(info.param) {
-        case 4: return "IPv4";
-        case 5: return "IPv64DualStack";
-        case 6: return "IPv6";
-    }
-    return android::base::StringPrintf("UNKNOWN family type: %d", info.param);
-}
-
 /* Generate function to set value refered to by 3rd argument.
  *
  * This allows us to mock functions that pass in a pointer, expecting the result to be put into
@@ -159,7 +142,7 @@
 
 TEST_F(XfrmControllerTest, TestFchown) {
     XfrmController ctrl;
-    unique_fd sockFd(socket(AF_INET, SOCK_DGRAM, 0));
+    unique_fd sockFd(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
 
     EXPECT_CALL(mockSyscalls, getsockopt(Fd(sockFd), IPPROTO_UDP, UDP_ENCAP, _, _))
         .WillOnce(DoAll(SetArg3IntValue(UDP_ENCAP_ESPINUDP), Return(netdutils::status::ok)));
@@ -181,7 +164,7 @@
 
 TEST_F(XfrmControllerTest, TestFchownIncorrectCallerUid) {
     XfrmController ctrl;
-    unique_fd sockFd(socket(AF_INET, SOCK_DGRAM, 0));
+    unique_fd sockFd(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
 
     netdutils::Status res = ctrl.ipSecSetEncapSocketOwner(sockFd, 1001, 1001);
     EXPECT_EQ(netdutils::statusFromErrno(EPERM, "fchown disabled for non-owner calls"), res);
@@ -189,7 +172,7 @@
 
 TEST_F(XfrmControllerTest, TestFchownNonSocketFd) {
     XfrmController ctrl;
-    unique_fd fd(open("/dev/null", 0));
+    unique_fd fd(open("/dev/null", O_CLOEXEC));
 
     netdutils::Status res = ctrl.ipSecSetEncapSocketOwner(fd, 1001, getuid());
     EXPECT_EQ(netdutils::statusFromErrno(EINVAL, "File descriptor was not a socket"), res);
@@ -197,7 +180,7 @@
 
 TEST_F(XfrmControllerTest, TestFchownNonUdp) {
     XfrmController ctrl;
-    unique_fd sockFd(socket(AF_INET, SOCK_STREAM, 0));
+    unique_fd sockFd(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0));
 
     EXPECT_CALL(mockSyscalls, getsockopt(Fd(sockFd), IPPROTO_UDP, UDP_ENCAP, _, _))
         .WillOnce(DoAll(SetArg3IntValue(0), Return(netdutils::status::ok)));
@@ -208,7 +191,7 @@
 
 TEST_F(XfrmControllerTest, TestFchownNonUdpEncap) {
     XfrmController ctrl;
-    unique_fd sockFd(socket(AF_INET, SOCK_DGRAM, 0));
+    unique_fd sockFd(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
 
     EXPECT_CALL(mockSyscalls, getsockopt(Fd(sockFd), IPPROTO_UDP, UDP_ENCAP, _, _))
         .WillOnce(DoAll(SetArg3IntValue(0), Return(netdutils::status::ok)));
@@ -217,12 +200,62 @@
     EXPECT_EQ(netdutils::statusFromErrno(EINVAL, "Socket did not have UDP-encap sockopt set"), res);
 }
 
+struct testCaseParams {
+    const int version;
+    const bool xfrmInterfacesEnabled;
+
+    int getTunnelInterfaceNlAttrsLen() {
+        if (xfrmInterfacesEnabled) {
+            return NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_interface_id));
+        } else {
+            return NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+        }
+    }
+};
+
+// Test class allowing IPv4/IPv6 parameterized tests.
+class XfrmControllerParameterizedTest : public XfrmControllerTest,
+                                        public ::testing::WithParamInterface<testCaseParams> {};
+
+// Helper to make generated test names readable.
+std::string TestNameGenerator(::testing::TestParamInfo<testCaseParams> info) {
+    std::string name = "";
+    switch (info.param.version) {
+        case 4:
+            name += "IPv4";
+            break;
+        case 5:
+            name += "IPv64DualStack";
+            break;
+        case 6:
+            name += "IPv6";
+            break;
+        default:
+            name += android::base::StringPrintf("UNKNOWN family type: %d", info.param.version);
+            break;
+    }
+
+    name += "_";
+
+    if (info.param.xfrmInterfacesEnabled) {
+        name += "XFRMI";
+    } else {
+        name += "VTI";
+    }
+
+    return name;
+}
+
 // The TEST_P cases below will run with each of the following value parameters.
-INSTANTIATE_TEST_CASE_P(ByFamily, XfrmControllerParameterizedTest, Values(4, 5, 6),
-                        FamilyName);
+INSTANTIATE_TEST_CASE_P(ByFamily, XfrmControllerParameterizedTest,
+                        Values(testCaseParams{4, false}, testCaseParams{4, true},
+                               testCaseParams{5, false}, testCaseParams{5, true},
+                               testCaseParams{6, false}, testCaseParams{6, true}),
+                        TestNameGenerator);
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecAllocateSpi) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
     const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
@@ -231,6 +264,7 @@
     response.hdr.nlmsg_type = XFRM_MSG_ALLOCSPI;
     Slice responseSlice = netdutils::makeSlice(response);
 
+    // No IF_ID expected for allocSPI.
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userspi_info));
 
     // A vector to hold the flattened netlink message for nlMsgSlice
@@ -240,7 +274,7 @@
     EXPECT_CALL(mockSyscalls, read(_, _))
         .WillOnce(DoAll(SetArgSlice<1>(responseSlice), Return(responseSlice)));
 
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     int outSpi = 0;
     Status res = ctrl.ipSecAllocateSpi(1 /* resourceId */, localAddr,
                                        remoteAddr, DROID_SPI, &outSpi);
@@ -255,7 +289,7 @@
     xfrm_userspi_info userspi{};
     netdutils::extract(nlMsgSlice, userspi);
 
-    EXPECT_EQ(family, userspi.info.sel.family);
+    EXPECT_EQ(AF_UNSPEC, userspi.info.sel.family);
     expectAddressEquals(family, localAddr, userspi.info.saddr);
     expectAddressEquals(family, remoteAddr, userspi.info.id.daddr);
 
@@ -263,8 +297,23 @@
     EXPECT_EQ(DROID_SPI, static_cast<int>(userspi.max));
 }
 
-void testIpSecAddSecurityAssociation(int version, const MockSyscalls& mockSyscalls,
-                                     const XfrmMode& mode, __u32 underlying_netid) {
+void verifyXfrmiArguments(uint32_t mark, uint32_t mask, uint32_t ifId) {
+    // Check that correct arguments (and only those) are non-zero, and correct.
+    EXPECT_EQ(0U, mark);
+    EXPECT_EQ(0U, mask);
+    EXPECT_EQ(TEST_XFRM_IF_ID, ifId);
+}
+
+void verifyVtiArguments(uint32_t mark, uint32_t mask, uint32_t ifId) {
+    // Check that correct arguments (and only those) are non-zero, and correct.
+    EXPECT_EQ(TEST_XFRM_MARK, mark);
+    EXPECT_EQ(TEST_XFRM_MASK, mask);
+    EXPECT_EQ(0U, ifId);
+}
+
+void testIpSecAddSecurityAssociation(testCaseParams params, const MockSyscalls& mockSyscalls,
+                                     const XfrmMode& mode) {
+    const int version = params.version;
     const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
@@ -278,13 +327,22 @@
 
     // Calculate the length of the expected netlink message.
     size_t expectedMsgLength =
-        NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_usersa_info)) +
-        NLA_ALIGN(offsetof(XfrmController::nlattr_algo_crypt, key) + KEY_LENGTH) +
-        NLA_ALIGN(offsetof(XfrmController::nlattr_algo_auth, key) + KEY_LENGTH) +
-        NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+            NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_usersa_info)) +
+            NLA_ALIGN(offsetof(XfrmController::nlattr_algo_crypt, key) + KEY_LENGTH) +
+            NLA_ALIGN(offsetof(XfrmController::nlattr_algo_auth, key) + KEY_LENGTH);
 
-    if (underlying_netid) {
+    uint32_t testIfId = 0;
+    uint32_t testMark = 0;
+    uint32_t testMarkMask = 0;
+    uint32_t testOutputNetid = 0;
+    if (mode == XfrmMode::TUNNEL) {
+        expectedMsgLength += params.getTunnelInterfaceNlAttrsLen();
         expectedMsgLength += NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_output_mark));
+
+        testIfId = TEST_XFRM_IF_ID;
+        testMark = TEST_XFRM_MARK;
+        testMarkMask = TEST_XFRM_MASK;
+        testOutputNetid = TEST_XFRM_UNDERLYING_NET;
     }
 
     std::vector<uint8_t> nlMsgBuf;
@@ -293,14 +351,15 @@
     EXPECT_CALL(mockSyscalls, read(_, _))
         .WillOnce(DoAll(SetArgSlice<1>(responseSlice), Return(responseSlice)));
 
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     Status res = ctrl.ipSecAddSecurityAssociation(
-        1 /* resourceId */, static_cast<int>(mode), localAddr, remoteAddr,
-        underlying_netid /* underlying netid */, DROID_SPI, TEST_XFRM_MARK /* mark */,
-        TEST_XFRM_MASK /* mask */, "hmac(sha256)" /* auth algo */,
-        authKey, 128 /* auth trunc length */, "cbc(aes)" /* encryption algo */,
-        cryptKey, 0 /* crypt trunc length? */, "" /* AEAD algo */, {}, 0,
-        static_cast<int>(XfrmEncapType::NONE), 0 /* local port */, 0 /* remote port */);
+            1 /* resourceId */, static_cast<int>(mode), localAddr, remoteAddr,
+            testOutputNetid /* underlying netid */, DROID_SPI, testMark /* mark */,
+            testMarkMask /* mask */, "hmac(sha256)" /* auth algo */, authKey,
+            128 /* auth trunc length */, "cbc(aes)" /* encryption algo */, cryptKey,
+            0 /* crypt trunc length? */, "" /* AEAD algo */, {}, 0,
+            static_cast<int>(XfrmEncapType::NONE), 0 /* local port */, 0 /* remote port */,
+            testIfId /* xfrm_if_id */);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -335,8 +394,9 @@
     XfrmController::nlattr_algo_auth authAlgo{};
     XfrmController::nlattr_xfrm_mark mark{};
     XfrmController::nlattr_xfrm_output_mark outputmark{};
-    auto attrHandler = [&encryptAlgo, &authAlgo, &mark, &outputmark](const nlattr& attr,
-                                                                     const Slice& attr_payload) {
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    auto attrHandler = [&encryptAlgo, &authAlgo, &mark, &outputmark, &xfrm_if_id](
+                               const nlattr& attr, const Slice& attr_payload) {
         Slice buf = attr_payload;
         if (attr.nla_type == XFRMA_ALG_CRYPT) {
             encryptAlgo.hdr = attr;
@@ -354,6 +414,9 @@
         } else if (attr.nla_type == XFRMA_OUTPUT_MARK) {
             mark.hdr = attr;
             netdutils::extract(buf, outputmark.outputMark);
+        } else if (attr.nla_type == XFRMA_IF_ID) {
+            xfrm_if_id.hdr = attr;
+            netdutils::extract(buf, xfrm_if_id.if_id);
         } else {
             FAIL() << "Unexpected nlattr type: " << attr.nla_type;
         }
@@ -365,27 +428,36 @@
                         reinterpret_cast<void*>(&encryptAlgo.key), KEY_LENGTH));
     EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(authKey.data()),
                         reinterpret_cast<void*>(&authAlgo.key), KEY_LENGTH));
-    EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
-    EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
-    if (underlying_netid) {
-        EXPECT_EQ(TEST_XFRM_OUTPUT_MARK, outputmark.outputMark);
+
+    if (mode == XfrmMode::TUNNEL) {
+        if (params.xfrmInterfacesEnabled) {
+            verifyXfrmiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+        } else {
+            verifyVtiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+        }
+
+        Fwmark fwmark;
+        fwmark.intValue = outputmark.outputMark;
+        EXPECT_EQ(testOutputNetid, fwmark.netId);
+        EXPECT_EQ(PERMISSION_SYSTEM, fwmark.permission);
+        EXPECT_TRUE(fwmark.explicitlySelected);
+        EXPECT_TRUE(fwmark.protectedFromVpn);
+    } else {
+        EXPECT_EQ(0U, outputmark.outputMark);
+        EXPECT_EQ(0U, mark.mark.v);
+        EXPECT_EQ(0U, mark.mark.m);
+        EXPECT_EQ(0U, xfrm_if_id.if_id);
     }
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestTransportModeIpSecAddSecurityAssociation) {
-    const int version = GetParam();
-    testIpSecAddSecurityAssociation(version, mockSyscalls, XfrmMode::TRANSPORT, 0);
+    testCaseParams params = GetParam();
+    testIpSecAddSecurityAssociation(params, mockSyscalls, XfrmMode::TRANSPORT);
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestTunnelModeIpSecAddSecurityAssociation) {
-    const int version = GetParam();
-    testIpSecAddSecurityAssociation(version, mockSyscalls, XfrmMode::TUNNEL, 0);
-}
-
-TEST_P(XfrmControllerParameterizedTest, TestTunnelModeIpSecAddSecurityAssociationWithOutputMark) {
-    const int version = GetParam();
-    testIpSecAddSecurityAssociation(version, mockSyscalls, XfrmMode::TUNNEL,
-                                    TEST_XFRM_OUTPUT_MARK);
+    testCaseParams params = GetParam();
+    testIpSecAddSecurityAssociation(params, mockSyscalls, XfrmMode::TUNNEL);
 }
 
 TEST_F(XfrmControllerTest, TestIpSecAddSecurityAssociationIPv4Encap) {
@@ -399,9 +471,9 @@
 
     XfrmController ctrl;
     Status res = ctrl.ipSecAddSecurityAssociation(
-        1, static_cast<int>(XfrmMode::TRANSPORT),
-        LOCALHOST_V6, TEST_ADDR_V6, 0, DROID_SPI, 0, 0, "hmac(sha256)", {}, 128, "cbc(aes)",
-        {}, 0, "", {}, 0, static_cast<int>(XfrmEncapType::ESPINUDP_NON_IKE), 0, 0);
+            1, static_cast<int>(XfrmMode::TRANSPORT), LOCALHOST_V6, TEST_ADDR_V6, 0, DROID_SPI, 0,
+            0, "hmac(sha256)", {}, 128, "cbc(aes)", {}, 0, "", {}, 0,
+            static_cast<int>(XfrmEncapType::ESPINUDP_NON_IKE), 0, 0, 0);
 
     EXPECT_FALSE(isOk(res)) << "IPv6 UDP encap not rejected";
 }
@@ -410,7 +482,7 @@
     struct sockaddr socketaddr;
     socketaddr.sa_family = AF_INET;
 
-    unique_fd sock(socket(AF_INET, SOCK_STREAM, 0));
+    unique_fd sock(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0));
 
     EXPECT_CALL(mockSyscalls, getsockname(Fd(sock), _, _))
         .WillOnce(DoAll(SetArgPointee<1>(socketaddr), Return(netdutils::status::ok)));
@@ -424,7 +496,8 @@
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecApplyTransportModeTransform) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
     const int sockFamily = (version == 4) ? AF_INET : AF_INET6;
     const int xfrmFamily = (version == 6) ? AF_INET6: AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
@@ -441,7 +514,7 @@
     struct sockaddr socketaddr;
     socketaddr.sa_family = sockFamily;
 
-    unique_fd sock(socket(sockFamily, SOCK_STREAM, 0));
+    unique_fd sock(socket(sockFamily, SOCK_STREAM | SOCK_CLOEXEC, 0));
 
     EXPECT_CALL(mockSyscalls, getsockname(_, _, _))
         .WillOnce(DoAll(SetArgPointee<1>(socketaddr), Return(netdutils::status::ok)));
@@ -450,7 +523,7 @@
         .WillOnce(DoAll(WithArg<3>(Invoke(SavePolicy)), SaveArg<4>(&optlen),
                         Return(netdutils::status::ok)));
 
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     Status res = ctrl.ipSecApplyTransportModeTransform(sock, 1 /* resourceId */,
                                                        static_cast<int>(XfrmDirection::OUT),
                                                        localAddr, remoteAddr, DROID_SPI);
@@ -466,7 +539,8 @@
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecRemoveTransportModeTransform) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
     const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
@@ -477,7 +551,7 @@
     struct sockaddr socketaddr;
     socketaddr.sa_family = family;
 
-    unique_fd sock(socket(family, SOCK_STREAM, 0));
+    unique_fd sock(socket(family, SOCK_STREAM | SOCK_CLOEXEC, 0));
 
     EXPECT_CALL(mockSyscalls, getsockname(_, _, _))
         .WillOnce(DoAll(SetArgPointee<1>(socketaddr), Return(netdutils::status::ok)));
@@ -485,7 +559,7 @@
     EXPECT_CALL(mockSyscalls, setsockopt(_, _, _, _, _))
         .WillOnce(DoAll(SaveArg<3>(&optval), SaveArg<4>(&optlen),
                         Return(netdutils::status::ok)));
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     Status res = ctrl.ipSecRemoveTransportModeTransform(sock);
 
     EXPECT_TRUE(isOk(res)) << res;
@@ -493,8 +567,26 @@
     EXPECT_EQ(static_cast<socklen_t>(0), optlen);
 }
 
+void parseTunnelNetlinkAttrs(XfrmController::nlattr_xfrm_mark* mark,
+                             XfrmController::nlattr_xfrm_interface_id* xfrm_if_id, Slice attr_buf) {
+    auto attrHandler = [mark, xfrm_if_id](const nlattr& attr, const Slice& attr_payload) {
+        Slice buf = attr_payload;
+        if (attr.nla_type == XFRMA_MARK) {
+            mark->hdr = attr;
+            netdutils::extract(buf, mark->mark);
+        } else if (attr.nla_type == XFRMA_IF_ID) {
+            xfrm_if_id->hdr = attr;
+            netdutils::extract(buf, xfrm_if_id->if_id);
+        } else {
+            FAIL() << "Unexpected nlattr type: " << attr.nla_type;
+        }
+    };
+    forEachNetlinkAttribute(attr_buf, attrHandler);
+}
+
 TEST_P(XfrmControllerParameterizedTest, TestIpSecDeleteSecurityAssociation) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
     const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
@@ -504,7 +596,7 @@
     Slice responseSlice = netdutils::makeSlice(response);
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_usersa_id)) +
-                               NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               params.getTunnelInterfaceNlAttrsLen();
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -512,9 +604,10 @@
     EXPECT_CALL(mockSyscalls, read(_, _))
         .WillOnce(DoAll(SetArgSlice<1>(responseSlice), Return(responseSlice)));
 
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     Status res = ctrl.ipSecDeleteSecurityAssociation(1 /* resourceId */, localAddr, remoteAddr,
-                                                     DROID_SPI, TEST_XFRM_MARK, TEST_XFRM_MASK);
+                                                     DROID_SPI, TEST_XFRM_MARK, TEST_XFRM_MASK,
+                                                     TEST_XFRM_IF_ID);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -522,15 +615,28 @@
     Slice nlMsgSlice = netdutils::makeSlice(nlMsgBuf);
     nlMsgSlice = netdutils::drop(nlMsgSlice, NLMSG_HDRLEN);
 
+    // Extract and check the usersa_id
     xfrm_usersa_id said{};
     netdutils::extract(nlMsgSlice, said);
-
+    nlMsgSlice = drop(nlMsgSlice, sizeof(xfrm_usersa_id));
     EXPECT_EQ(htonl(DROID_SPI), said.spi);
     expectAddressEquals(family, remoteAddr, said.daddr);
+
+    // Extract and check the marks and xfrm_if_id
+    XfrmController::nlattr_xfrm_mark mark{};
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    parseTunnelNetlinkAttrs(&mark, &xfrm_if_id, nlMsgSlice);
+
+    if (params.xfrmInterfacesEnabled) {
+        verifyXfrmiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+    } else {
+        verifyVtiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+    }
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecAddSecurityPolicy) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
     const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
@@ -541,7 +647,7 @@
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userpolicy_info)) +
                                NLMSG_ALIGN(sizeof(XfrmController::nlattr_user_tmpl)) +
-                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               params.getTunnelInterfaceNlAttrsLen();
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -549,10 +655,10 @@
     EXPECT_CALL(mockSyscalls, read(_, _))
         .WillOnce(DoAll(SetArgSlice<1>(responseSlice), Return(responseSlice)));
 
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     Status res = ctrl.ipSecAddSecurityPolicy(
-        1 /* resourceId */, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
-        0 /* SPI */, TEST_XFRM_MARK, TEST_XFRM_MASK);
+            1 /* resourceId */, family, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
+            0 /* SPI */, TEST_XFRM_MARK, TEST_XFRM_MASK, TEST_XFRM_IF_ID);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -574,7 +680,9 @@
     // Extract and check the user tmpl and mark.
     XfrmController::nlattr_user_tmpl usertmpl{};
     XfrmController::nlattr_xfrm_mark mark{};
-    auto attrHandler = [&usertmpl, &mark](const nlattr& attr, const Slice& attr_payload) {
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    auto attrHandler = [&usertmpl, &mark, &xfrm_if_id](const nlattr& attr,
+                                                       const Slice& attr_payload) {
         Slice buf = attr_payload;
         if (attr.nla_type == XFRMA_TMPL) {
             usertmpl.hdr = attr;
@@ -582,6 +690,9 @@
         } else if (attr.nla_type == XFRMA_MARK) {
             mark.hdr = attr;
             netdutils::extract(buf, mark.mark);
+        } else if (attr.nla_type == XFRMA_IF_ID) {
+            mark.hdr = attr;
+            netdutils::extract(buf, xfrm_if_id.if_id);
         } else {
             FAIL() << "Unexpected nlattr type: " << attr.nla_type;
         }
@@ -589,13 +700,18 @@
     forEachNetlinkAttribute(attr_buf, attrHandler);
 
     expectAddressEquals(family, remoteAddr, usertmpl.tmpl.id.daddr);
-    EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
-    EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
 
+    if (params.xfrmInterfacesEnabled) {
+        verifyXfrmiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+    } else {
+        verifyVtiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+    }
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecUpdateSecurityPolicy) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
+    const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
 
@@ -605,7 +721,7 @@
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userpolicy_info)) +
                                NLMSG_ALIGN(sizeof(XfrmController::nlattr_user_tmpl)) +
-                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               params.getTunnelInterfaceNlAttrsLen();
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -613,10 +729,11 @@
     EXPECT_CALL(mockSyscalls, read(_, _))
         .WillOnce(DoAll(SetArgSlice<1>(responseSlice), Return(responseSlice)));
 
-    XfrmController ctrl;
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
     Status res = ctrl.ipSecUpdateSecurityPolicy(
-        1 /* resourceId */, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
-        0 /* SPI */, 0 /* Mark */, 0 /* Mask */);
+            1 /* resourceId */, family, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
+            0 /* SPI */, TEST_XFRM_MARK /* Mark */, TEST_XFRM_MARK /* Mask */,
+            TEST_XFRM_IF_ID /* xfrm_if_id */);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -628,7 +745,9 @@
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecDeleteSecurityPolicy) {
-    const int version = GetParam();
+    testCaseParams params = GetParam();
+    const int version = params.version;
+    const int family = (version == 6) ? AF_INET6 : AF_INET;
     const std::string localAddr = (version == 6) ? LOCALHOST_V6 : LOCALHOST_V4;
     const std::string remoteAddr = (version == 6) ? TEST_ADDR_V6 : TEST_ADDR_V4;
 
@@ -637,7 +756,7 @@
     Slice responseSlice = netdutils::makeSlice(response);
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userpolicy_id)) +
-                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               params.getTunnelInterfaceNlAttrsLen();
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -645,10 +764,10 @@
     EXPECT_CALL(mockSyscalls, read(_, _))
         .WillOnce(DoAll(SetArgSlice<1>(responseSlice), Return(responseSlice)));
 
-    XfrmController ctrl;
-    Status res = ctrl.ipSecDeleteSecurityPolicy(
-        1 /* resourceId */, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
-        TEST_XFRM_MARK, TEST_XFRM_MASK);
+    XfrmController ctrl(params.xfrmInterfacesEnabled);
+    Status res = ctrl.ipSecDeleteSecurityPolicy(1 /* resourceId */, family,
+                                                static_cast<int>(XfrmDirection::OUT),
+                                                TEST_XFRM_MARK, TEST_XFRM_MASK, TEST_XFRM_IF_ID);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -663,12 +782,17 @@
 
     // Drop the user policy id.
     nlMsgSlice = drop(nlMsgSlice, NLA_ALIGN(sizeof(xfrm_userpolicy_id)));
-    // Extract and check the mark.
-    XfrmController::nlattr_xfrm_mark mark{};
-    netdutils::extract(nlMsgSlice, mark);
-    EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
-    EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
 
+    // Extract and check the marks and xfrm_if_id
+    XfrmController::nlattr_xfrm_mark mark{};
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    parseTunnelNetlinkAttrs(&mark, &xfrm_if_id, nlMsgSlice);
+
+    if (params.xfrmInterfacesEnabled) {
+        verifyXfrmiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+    } else {
+        verifyVtiArguments(mark.mark.v, mark.mark.m, xfrm_if_id.if_id);
+    }
 }
 
 // TODO: Add tests for VTIs, ensuring that we are sending the correct data over netlink.
diff --git a/server/aidl/netd/1/android/net/INetd.aidl b/server/aidl/netd/1/android/net/INetd.aidl
new file mode 100644
index 0000000..664c643
--- /dev/null
+++ b/server/aidl/netd/1/android/net/INetd.aidl
@@ -0,0 +1,132 @@
+package android.net;
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  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);
+  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 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;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 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 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/server/aidl/netd/1/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl/netd/1/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..18631ff
--- /dev/null
+++ b/server/aidl/netd/1/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,14 @@
+package android.net;
+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/server/aidl/netd/1/android/net/InterfaceConfigurationParcel.aidl b/server/aidl/netd/1/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..93407dc
--- /dev/null
+++ b/server/aidl/netd/1/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl/netd/1/android/net/TetherStatsParcel.aidl b/server/aidl/netd/1/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..d1782bb
--- /dev/null
+++ b/server/aidl/netd/1/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/server/aidl/netd/1/android/net/UidRangeParcel.aidl b/server/aidl/netd/1/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..d3bc7ed
--- /dev/null
+++ b/server/aidl/netd/1/android/net/UidRangeParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl/netd/2/android/net/INetd.aidl b/server/aidl/netd/2/android/net/INetd.aidl
new file mode 100644
index 0000000..0e2d5f4
--- /dev/null
+++ b/server/aidl/netd/2/android/net/INetd.aidl
@@ -0,0 +1,153 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  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();
+  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 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;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 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 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/server/aidl/netd/2/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl/netd/2/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..621f1cf
--- /dev/null
+++ b/server/aidl/netd/2/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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/server/aidl/netd/2/android/net/InterfaceConfigurationParcel.aidl b/server/aidl/netd/2/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..18de61f
--- /dev/null
+++ b/server/aidl/netd/2/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl/netd/2/android/net/TetherStatsParcel.aidl b/server/aidl/netd/2/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..c0ba676
--- /dev/null
+++ b/server/aidl/netd/2/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/server/aidl/netd/2/android/net/UidRangeParcel.aidl b/server/aidl/netd/2/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..c2c35db
--- /dev/null
+++ b/server/aidl/netd/2/android/net/UidRangeParcel.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl/netdeventlistener/1/android/net/metrics/INetdEventListener.aidl b/server/aidl/netdeventlistener/1/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..9898a67
--- /dev/null
+++ b/server/aidl/netdeventlistener/1/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.metrics;
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index a9e10a1..ec9bbc3 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -16,8 +16,10 @@
 
 package android.net;
 
-import android.net.UidRange;
-import android.os.PersistableBundle;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.InterfaceConfigurationParcel;
+import android.net.TetherStatsParcel;
+import android.net.UidRangeParcel;
 
 /** {@hide} */
 interface INetd {
@@ -39,7 +41,9 @@
      * @param uids The list of UIDs to allow/deny.
      * @return true if the chain was successfully replaced, false otherwise.
      */
-    boolean firewallReplaceUidChain(String chainName, boolean isWhitelist, in int[] uids);
+    boolean firewallReplaceUidChain(in @utf8InCpp String chainName,
+                                    boolean isWhitelist,
+                                    in int[] uids);
 
     /**
      * Enables or disables data saver mode on costly network interfaces.
@@ -59,33 +63,28 @@
      */
     boolean bandwidthEnableDataSaver(boolean enable);
 
-    // Network permission values.
-    const String PERMISSION_NETWORK = "NETWORK";
-    const String PERMISSION_SYSTEM = "SYSTEM";
-
     /**
      * Creates a physical network (i.e., one containing physical interfaces.
      *
      * @param netId the networkId to create.
-     * @param permission the permission necessary to use the network. Must be one of the
-     *         PERMISSION_xxx values above.
+     * @param permission the permission necessary to use the network. Must be one of
+     *         PERMISSION_NONE/PERMISSION_NETWORK/PERMISSION_SYSTEM.
      *
      * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
      *         unix errno.
      */
-    void networkCreatePhysical(int netId, @utf8InCpp String permission);
+    void networkCreatePhysical(int netId, int permission);
 
     /**
      * Creates a VPN network.
      *
      * @param netId the network to create.
-     * @param hasDns whether the VPN has DNS servers.
      * @param secure whether unprivileged apps are allowed to bypass the VPN.
      *
      * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
      *         unix errno.
      */
-    void networkCreateVpn(int netId, boolean hasDns, boolean secure);
+    void networkCreateVpn(int netId, boolean secure);
 
     /**
      * Destroys a network. Any interfaces added to the network are removed, and the network ceases
@@ -132,7 +131,7 @@
      * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
      *         unix errno.
      */
-    void networkAddUidRanges(int netId, in UidRange[] uidRanges);
+    void networkAddUidRanges(int netId, in UidRangeParcel[] uidRanges);
 
     /**
      * Adds the specified UID ranges to the specified network. The network must be a VPN. Traffic
@@ -145,7 +144,7 @@
      * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
      *         unix errno.
      */
-    void networkRemoveUidRanges(int netId, in UidRange[] uidRanges);
+    void networkRemoveUidRanges(int netId, in UidRangeParcel[] uidRanges);
 
     /**
      * Adds or removes one rule for each supplied UID range to prohibit all network activity outside
@@ -168,78 +167,12 @@
      * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
      *         unix errno.
      */
-    void networkRejectNonSecureVpn(boolean add, in UidRange[] uidRanges);
+    void networkRejectNonSecureVpn(boolean add, in UidRangeParcel[] uidRanges);
 
     /**
      * Administratively closes sockets belonging to the specified UIDs.
      */
-    void socketDestroy(in UidRange[] uidRanges, in int[] exemptUids);
-
-    // Array indices for resolver parameters.
-    const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
-    const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
-    const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
-    const int RESOLVER_PARAMS_MAX_SAMPLES = 3;
-    const int RESOLVER_PARAMS_COUNT = 4;
-
-    /**
-     * Sets the name servers, search domains and resolver params for the given network. Flushes the
-     * cache as needed (i.e. when the servers or the number of samples to store changes).
-     *
-     * @param netId the network ID of the network for which information should be configured.
-     * @param servers the DNS servers to configure for the network.
-     * @param domains the search domains to configure.
-     * @param params the params to set. This array contains RESOLVER_PARAMS_COUNT integers that
-     *   encode the contents of Bionic's __res_params struct, i.e. sample_validity is stored at
-     *   position RESOLVER_PARAMS_SAMPLE_VALIDITY, etc.
-     * @param tlsName The TLS subject name to require for all servers, or empty if there is none.
-     * @param tlsServers the DNS servers to configure for strict mode Private DNS.
-     * @param tlsFingerprints An array containing TLS public key fingerprints (pins) of which each
-     *   server must match at least one, or empty if there are no pinned keys.
-     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
-     *         unix errno.
-     */
-    void setResolverConfiguration(int netId, in @utf8InCpp String[] servers,
-            in @utf8InCpp String[] domains, in int[] params,
-            in @utf8InCpp String tlsName, in @utf8InCpp String[] tlsServers,
-            in @utf8InCpp String[] tlsFingerprints);
-
-    // Array indices for resolver stats.
-    const int RESOLVER_STATS_SUCCESSES = 0;
-    const int RESOLVER_STATS_ERRORS = 1;
-    const int RESOLVER_STATS_TIMEOUTS = 2;
-    const int RESOLVER_STATS_INTERNAL_ERRORS = 3;
-    const int RESOLVER_STATS_RTT_AVG = 4;
-    const int RESOLVER_STATS_LAST_SAMPLE_TIME = 5;
-    const int RESOLVER_STATS_USABLE = 6;
-    const int RESOLVER_STATS_COUNT = 7;
-
-    /**
-     * Retrieves the name servers, search domains and resolver stats associated with the given
-     * network ID.
-     *
-     * @param netId the network ID of the network for which information should be retrieved.
-     * @param servers the DNS servers that are currently configured for the network.
-     * @param domains the search domains currently configured.
-     * @param params the resolver parameters configured, i.e. the contents of __res_params in order.
-     * @param stats the stats for each server in the order specified by RESOLVER_STATS_XXX
-     *         constants, serialized as an int array. The contents of this array are the number of
-     *         <ul>
-     *           <li> successes,
-     *           <li> errors,
-     *           <li> timeouts,
-     *           <li> internal errors,
-     *           <li> the RTT average,
-     *           <li> the time of the last recorded sample,
-     *           <li> and an integer indicating whether the server is usable (1) or broken (0).
-     *         </ul>
-     *         in this order. For example, the timeout counter for server N is stored at position
-     *         RESOLVER_STATS_COUNT*N + RESOLVER_STATS_TIMEOUTS
-     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
-     *         unix errno.
-     */
-    void getResolverInfo(int netId, out @utf8InCpp String[] servers,
-            out @utf8InCpp String[] domains, out int[] params, out int[] stats);
+    void socketDestroy(in UidRangeParcel[] uidRanges, in int[] exemptUids);
 
     /**
      * Instruct the tethering DNS server to reevaluated serving interfaces.
@@ -251,24 +184,16 @@
      */
     boolean tetherApplyDnsInterfaces();
 
-    // Ordering of the elements in the arrays returned by tetherGetStats.
-    const int TETHER_STATS_RX_BYTES   = 0;
-    const int TETHER_STATS_RX_PACKETS = 1;
-    const int TETHER_STATS_TX_BYTES   = 2;
-    const int TETHER_STATS_TX_PACKETS = 3;
-    const int TETHER_STATS_ARRAY_SIZE = 4;
-
     /**
      * Return tethering statistics.
      *
-     * @return a PersistableBundle, where each entry maps the upstream interface name to an array
-     *         of longs representing stats. The array is TETHER_STATS_ARRAY_SIZE elements long and
-     *         the order of the elements is specified by the TETHER_STATS_{RX,TX}_{PACKETS,BYTES}
-     *         constants.
+     * @return an array of TetherStatsParcel, where each entry contains the upstream interface
+     *         name and its tethering statistics.
+     *         There will only ever be one entry for a given interface.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *         cause of the the failure.
+     *         cause of the failure.
      */
-    PersistableBundle tetherGetStats();
+    TetherStatsParcel[] tetherGetStats();
 
     /**
      * Add/Remove and IP address from an interface.
@@ -288,40 +213,33 @@
     /**
      * Set and get /proc/sys/net interface configuration parameters.
      *
-     * @param family One of IPV4/IPV6 integers, indicating the desired address family directory.
+     * @param ipversion One of IPV4/IPV6 integers, indicating the desired IP version directory.
      * @param which One of CONF/NEIGH integers, indicating the desired parameter category directory.
      * @param ifname The interface name portion of the path; may also be "all" or "default".
      * @param parameter The parameter name portion of the path.
      * @param value The value string to be written into the assembled path.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
      */
 
     const int IPV4  = 4;
     const int IPV6  = 6;
     const int CONF  = 1;
     const int NEIGH = 2;
-    void setProcSysNet(int family, int which, in @utf8InCpp String ifname,
+    @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);
-    // TODO: add corresponding getProcSysNet().
-
-    /**
-     * Get/Set metrics reporting level.
-     *
-     * Reporting level is one of:
-     *     0 (NONE)
-     *     1 (METRICS)
-     *     2 (FULL)
-     */
-    int getMetricsReportingLevel();
-    void setMetricsReportingLevel(int level);
 
    /**
-    * Sets owner of socket FileDescriptor to the new UID, checking to ensure that the caller's
+    * Sets owner of socket ParcelFileDescriptor to the new UID, checking to ensure that the caller's
     * uid is that of the old owner's, and that this is a UDP-encap socket
     *
-    * @param FileDescriptor socket Socket file descriptor
+    * @param ParcelFileDescriptor socket Socket file descriptor
     * @param int newUid UID of the new socket fd owner
     */
-    void ipSecSetEncapSocketOwner(in FileDescriptor socket, int newUid);
+    void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
 
    /**
     * Reserve an SPI from the kernel
@@ -346,7 +264,8 @@
     * @param mode either Transport or Tunnel mode
     * @param sourceAddress InetAddress as string for the sending endpoint
     * @param destinationAddress InetAddress as string for the receiving endpoint
-    * @param underlyingNetId the netId of the network to which the SA is applied
+    * @param underlyingNetId the netId of the network to which the SA is applied. Only accepted for
+    *        tunnel mode SAs.
     * @param spi a 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
@@ -363,6 +282,8 @@
     * @param encapType encapsulation type used (if any) for the udp encap socket
     * @param encapLocalPort the port number on the host to be used in encap packets
     * @param encapRemotePort the port number of the remote to be used for encap packets
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    *        Only accepted for tunnel mode SAs.
     */
     void ipSecAddSecurityAssociation(
             int transformId,
@@ -378,7 +299,8 @@
             in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits,
             int encapType,
             int encapLocalPort,
-            int encapRemotePort);
+            int encapRemotePort,
+            int interfaceId);
 
    /**
     * Delete a previously created security association identified by the provided parameters
@@ -389,6 +311,7 @@
     * @param spi a requested 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecDeleteSecurityAssociation(
             int transformId,
@@ -396,7 +319,8 @@
             in @utf8InCpp String destinationAddress,
             int spi,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
    /**
     * Apply a previously created SA to a specified socket, starting IPsec on that socket
@@ -409,7 +333,7 @@
     * @param spi a 32-bit unique ID allocated to the user (socket owner)
     */
     void ipSecApplyTransportModeTransform(
-            in FileDescriptor socket,
+            in ParcelFileDescriptor socket,
             int transformId,
             int direction,
             in @utf8InCpp String sourceAddress,
@@ -423,108 +347,123 @@
     * @param socket a user-provided socket from which to remove any IPsec configuration
     */
     void ipSecRemoveTransportModeTransform(
-            in FileDescriptor socket);
+            in ParcelFileDescriptor socket);
 
    /**
     * Adds an IPsec global policy.
     *
     * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
     * @param direction DIRECTION_IN or DIRECTION_OUT
-    * @param sourceAddress InetAddress as string for the sending endpoint
-    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param tmplSrcAddress InetAddress as string for the sending endpoint
+    * @param tmplDstAddress InetAddress as string for the receiving endpoint
     * @param spi a 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecAddSecurityPolicy(
             int transformId,
+            int selAddrFamily,
             int direction,
-            in @utf8InCpp String sourceAddress,
-            in @utf8InCpp String destinationAddress,
+            in @utf8InCpp String tmplSrcAddress,
+            in @utf8InCpp String tmplDstAddress,
             int spi,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
    /**
     * Updates an IPsec global policy.
     *
     * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
     * @param direction DIRECTION_IN or DIRECTION_OUT
-    * @param sourceAddress InetAddress as string for the sending endpoint
-    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param tmplSrcAddress InetAddress as string for the sending endpoint
+    * @param tmplDstAddress InetAddress as string for the receiving endpoint
     * @param spi a 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecUpdateSecurityPolicy(
             int transformId,
+            int selAddrFamily,
             int direction,
-            in @utf8InCpp String sourceAddress,
-            in @utf8InCpp String destinationAddress,
+            in @utf8InCpp String tmplSrcAddress,
+            in @utf8InCpp String tmplDstAddress,
             int spi,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
    /**
     * Deletes an IPsec global policy.
     *
+    * Deletion of global policies does not do any matching based on the templates, thus
+    * template source/destination addresses are not needed (as opposed to add/update).
+    *
     * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
     * @param direction DIRECTION_IN or DIRECTION_OUT
-    * @param sourceAddress InetAddress as string for the sending endpoint
-    * @param destinationAddress InetAddress as string for the receiving endpoint
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecDeleteSecurityPolicy(
             int transformId,
+            int selAddrFamily,
             int direction,
-            in @utf8InCpp String sourceAddress,
-            in @utf8InCpp String destinationAddress,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
     // This could not be declared as @uft8InCpp; thus, when used in native code it must be
     // converted from a UTF-16 string to an ASCII string.
     const String IPSEC_INTERFACE_PREFIX = "ipsec";
 
    /**
-    * Add a Virtual Tunnel Interface.
+    * Add a IPsec Tunnel Interface.
     *
     * @param devName a unique identifier that represents the name of the device
     * @param localAddress InetAddress as string for the local endpoint
     * @param remoteAddress InetAddress as string for the remote endpoint
     * @param iKey, to match Policies and SAs for input packets.
     * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
-    void addVirtualTunnelInterface(
+    void ipSecAddTunnelInterface(
             in @utf8InCpp String deviceName,
             in @utf8InCpp String localAddress,
             in @utf8InCpp String remoteAddress,
             int iKey,
-            int oKey);
+            int oKey,
+            int interfaceId);
 
    /**
-    * Update a Virtual Tunnel Interface.
+    * Update a IPsec Tunnel Interface.
     *
     * @param devName a unique identifier that represents the name of the device
     * @param localAddress InetAddress as string for the local endpoint
     * @param remoteAddress InetAddress as string for the remote endpoint
     * @param iKey, to match Policies and SAs for input packets.
     * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
-    void updateVirtualTunnelInterface(
+    void ipSecUpdateTunnelInterface(
             in @utf8InCpp String deviceName,
             in @utf8InCpp String localAddress,
             in @utf8InCpp String remoteAddress,
             int iKey,
-            int oKey);
+            int oKey,
+            int interfaceId);
 
    /**
-    * Removes a Virtual Tunnel Interface.
+    * Removes a IPsec Tunnel Interface.
     *
     * @param devName a unique identifier that represents the name of the device
     */
-    void removeVirtualTunnelInterface(in @utf8InCpp String deviceName);
+    void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
 
    /**
     * Request notification of wakeup packets arriving on an interface. Notifications will be
@@ -557,8 +496,702 @@
     void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
 
    /**
-    * Query the netd service to know if the eBPF traffic stats accounting service is currently
-    * running on the device.
+    * Add idletimer for specific interface
+    *
+    * @param ifName Name of target interface
+    * @param timeout The time in seconds that will trigger idletimer
+    * @param classLabel The unique identifier for this idletimer
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
     */
-    boolean trafficCheckBpfStatsEnable();
+    void idletimerAddInterface(
+            in @utf8InCpp String ifName,
+            int timeout,
+            in @utf8InCpp String classLabel);
+
+   /**
+    * Remove idletimer for specific interface
+    *
+    * @param ifName Name of target interface
+    * @param timeout The time in seconds that will trigger idletimer
+    * @param classLabel The unique identifier for this idletimer
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void idletimerRemoveInterface(
+            in @utf8InCpp String ifName,
+            int timeout,
+            in @utf8InCpp String classLabel);
+
+    const int PENALTY_POLICY_ACCEPT = 1;
+    const int PENALTY_POLICY_LOG = 2;
+    const int PENALTY_POLICY_REJECT = 3;
+
+   /**
+    * Offers to detect sockets sending data not wrapped inside a layer of SSL/TLS encryption.
+    *
+    * @param uid Uid of the app
+    * @param policyPenalty The penalty policy of the app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void strictUidCleartextPenalty(int uid, int policyPenalty);
+
+   /**
+    * Start clatd
+    *
+    * @param ifName interface name to start clatd
+    * @param nat64Prefix the NAT64 prefix, e.g., "2001:db8:64::/96".
+    * @return a string, the IPv6 address that will be used for 464xlat.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+
+   /**
+    * Stop clatd
+    *
+    * @param ifName interface name to stop clatd
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void clatdStop(in @utf8InCpp String ifName);
+
+   /**
+    * Get status of IP forwarding
+    *
+    * @return true if IP forwarding is enabled, false otherwise.
+    */
+    boolean ipfwdEnabled();
+
+   /**
+    * Get requester list of IP forwarding
+    *
+    * @return An array of strings containing requester list of IP forwarding
+    */
+    @utf8InCpp String[] ipfwdGetRequesterList();
+
+   /**
+    * Enable IP forwarding for specific requester
+    *
+    * @param requester requester name to enable IP forwarding. It is a unique name which will be
+    *                  stored in Netd to make sure if any requester needs IP forwarding.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdEnableForwarding(in @utf8InCpp String requester);
+
+   /**
+    * Disable IP forwarding for specific requester
+    *
+    * @param requester requester name to disable IP forwarding. This name should match the
+    *                  names which are set by ipfwdEnableForwarding.
+    *                  IP forwarding would be disabled if it is the last requester.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdDisableForwarding(in @utf8InCpp String requester);
+
+   /**
+    * Add forwarding ip rule
+    *
+    * @param fromIface interface name to add forwarding ip rule
+    * @param toIface interface name to add forwarding ip rule
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+
+   /**
+    * Remove forwarding ip rule
+    *
+    * @param fromIface interface name to remove forwarding ip rule
+    * @param toIface interface name to remove forwarding ip rule
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+
+   /**
+    * Set quota for interface
+    *
+    * @param ifName Name of target interface
+    * @param bytes Quota value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+
+   /**
+    * Remove quota for interface
+    *
+    * @param ifName Name of target interface
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+
+   /**
+    * Set alert for interface
+    *
+    * @param ifName Name of target interface
+    * @param bytes Alert value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+
+   /**
+    * Remove alert for interface
+    *
+    * @param ifName Name of target interface
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+
+   /**
+    * Set global alert
+    *
+    * @param bytes Alert value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetGlobalAlert(long bytes);
+
+   /**
+    * Add naughty app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthAddNaughtyApp(int uid);
+
+   /**
+    * Remove naughty app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveNaughtyApp(int uid);
+
+   /**
+    * Add nice app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthAddNiceApp(int uid);
+
+   /**
+    * Remove nice app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveNiceApp(int uid);
+
+   /**
+    * Start tethering
+    *
+    * @param dhcpRanges dhcp ranges to set.
+    *                   dhcpRanges might contain many addresss {addr1, addr2, aadr3, addr4...}
+    *                   Netd splits them into ranges: addr1-addr2, addr3-addr4, etc.
+    *                   An odd number of addrs will fail.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStart(in @utf8InCpp String[] dhcpRanges);
+
+   /**
+    * Stop tethering
+    *
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStop();
+
+   /**
+    * Get status of tethering
+    *
+    * @return true if tethering is enabled, false otherwise.
+    */
+    boolean tetherIsEnabled();
+
+   /**
+    * Setup interface for tethering
+    *
+    * @param ifName interface name to add
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherInterfaceAdd(in @utf8InCpp String ifName);
+
+   /**
+    * Reset interface for tethering
+    *
+    * @param ifName interface name to remove
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherInterfaceRemove(in @utf8InCpp String ifName);
+
+   /**
+    * Get the interface list which is stored in netd
+    * The list contains the interfaces managed by tetherInterfaceAdd/tetherInterfaceRemove
+    *
+    * @return An array of strings containing interface list result
+    */
+    @utf8InCpp String[] tetherInterfaceList();
+
+   /**
+    * Set DNS forwarder server
+    *
+    * @param netId the upstream network to forward DNS queries to
+    * @param dnsAddrs DNS server address to set
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+
+   /**
+    * Return the DNS list set by tetherDnsSet
+    *
+    * @return An array of strings containing the list of DNS servers
+    */
+    @utf8InCpp String[] tetherDnsList();
+
+    const int LOCAL_NET_ID = 99;
+
+    // Route does not specify a next hop
+    const String NEXTHOP_NONE = "";
+    // Route next hop is unreachable
+    const String NEXTHOP_UNREACHABLE = "unreachable";
+    // Route next hop is throw
+    const String NEXTHOP_THROW = "throw";
+
+   /**
+    * Add a route for specific network
+    *
+    * @param netId the network to add the route to
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop);
+
+   /**
+    * Remove a route for specific network
+    *
+    * @param netId the network to remove the route from
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop);
+
+   /**
+    * Add a route to legacy routing table for specific network
+    *
+    * @param netId the network to add the route to
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @param uid uid of the user
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddLegacyRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop,
+            int uid);
+
+   /**
+    * Remove a route from legacy routing table for specific network
+    *
+    * @param netId the network to remove the route from
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @param uid uid of the user
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveLegacyRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop,
+            int uid);
+
+   /**
+    * Get default network
+    *
+    * @return netId of default network
+    */
+    int networkGetDefault();
+
+   /**
+    * Set network as default network
+    *
+    * @param netId the network to set as the default
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkSetDefault(int netId);
+
+   /**
+    * Clear default network
+    *
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkClearDefault();
+
+   /**
+    * PERMISSION_NONE is used for regular networks and apps. TODO: use PERMISSION_INTERNET
+    * for this instead, and use PERMISSION_NONE to indicate no network permissions at all.
+    */
+    const int PERMISSION_NONE = 0;
+
+   /**
+    * PERMISSION_NETWORK represents the CHANGE_NETWORK_STATE permission.
+    */
+    const int PERMISSION_NETWORK = 1;
+
+   /**
+    * PERMISSION_SYSTEM represents the ability to use restricted networks. This is mostly
+    * equivalent to the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
+    */
+    const int PERMISSION_SYSTEM = 2;
+
+   /**
+    * NO_PERMISSIONS indicates that this app is installed and doesn't have either
+    * PERMISSION_INTERNET or PERMISSION_UPDATE_DEVICE_STATS.
+    * TODO: use PERMISSION_NONE to represent this case
+    */
+    const int NO_PERMISSIONS = 0;
+
+   /**
+    * PERMISSION_INTERNET indicates that the app can create AF_INET and AF_INET6 sockets
+    */
+    const int PERMISSION_INTERNET = 4;
+
+   /**
+    * PERMISSION_UPDATE_DEVICE_STATS is used for system UIDs and privileged apps
+    * that have the UPDATE_DEVICE_STATS permission
+    */
+    const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+
+   /**
+    * PERMISSION_UNINSTALLED is used when an app is uninstalled from the device. All internet
+    * related permissions need to be cleaned
+    */
+    const int PERMISSION_UNINSTALLED = -1;
+
+
+   /**
+    * Sets the permission required to access a specific network.
+    *
+    * @param netId the network to set
+    * @param permission network permission to use
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkSetPermissionForNetwork(int netId, int permission);
+
+   /**
+    * Assigns network access permissions to the specified users.
+    *
+    * @param permission network permission to use
+    * @param uids uid of users to set permission
+    */
+    void networkSetPermissionForUser(int permission, in int[] uids);
+
+   /**
+    * Clears network access permissions for the specified users.
+    *
+    * @param uids uid of users to clear permission
+    */
+    void networkClearPermissionForUser(in int[] uids);
+
+   /**
+    * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+    * specified. Or remove all permissions from the uids.
+    *
+    * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
+    *                   PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+    *                   revoke all permissions for the uids.
+    * @param uids uid of users to grant permission
+    */
+    void trafficSetNetPermForUids(int permission, in int[] uids);
+
+   /**
+    * Gives the specified user permission to protect sockets from VPNs.
+    * Typically used by VPN apps themselves, to ensure that the sockets
+    * they use to communicate with the VPN server aren't routed through
+    * the VPN network.
+    *
+    * @param uid uid of user to set
+    */
+    void networkSetProtectAllow(int uid);
+
+   /**
+    * Removes the permission to protect sockets from VPN.
+    *
+    * @param uid uid of user to set
+    */
+    void networkSetProtectDeny(int uid);
+
+   /**
+    * Get the status of network protect for user
+    *
+    * @param uids uid of user
+    * @return true if the user can protect sockets from VPN, false otherwise.
+    */
+    boolean networkCanProtect(int uid);
+
+    // Whitelist only allows packets from specific UID/Interface
+    const int FIREWALL_WHITELIST = 0;
+    // Blacklist blocks packets from specific UID/Interface
+    const int FIREWALL_BLACKLIST = 1;
+
+   /**
+    * Set type of firewall
+    * Type whitelist only allows packets from specific UID/Interface
+    * Type blacklist blocks packets from specific UID/Interface
+    *
+    * @param firewalltype type of firewall, either FIREWALL_WHITELIST or FIREWALL_BLACKLIST
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetFirewallType(int firewalltype);
+
+    // Specify allow Rule which allows packets
+    const int FIREWALL_RULE_ALLOW = 1;
+    // Specify deny Rule which drops packets
+    const int FIREWALL_RULE_DENY = 2;
+
+    // No specific chain is chosen, use general firewall chain(fw_input, fw_output)
+    const int FIREWALL_CHAIN_NONE = 0;
+    // Specify DOZABLE chain(fw_dozable) which is used in dozable mode
+    const int FIREWALL_CHAIN_DOZABLE = 1;
+    // Specify STANDBY chain(fw_standby) which is used in standby mode
+    const int FIREWALL_CHAIN_STANDBY = 2;
+    // Specify POWERSAVE chain(fw_powersave) which is used in power save mode
+    const int FIREWALL_CHAIN_POWERSAVE = 3;
+
+   /**
+    * Set firewall rule for interface
+    *
+    * @param ifName the interface to allow/deny
+    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+
+   /**
+    * Set firewall rule for uid
+    *
+    * @param childChain target chain
+    * @param uid uid to allow/deny
+    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetUidRule(int childChain, int uid, int firewallRule);
+
+   /**
+    * Enable/Disable target firewall child chain
+    *
+    * @param childChain target chain to enable
+    * @param enable whether to enable or disable child chain.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallEnableChildChain(int childChain, boolean enable);
+
+   /**
+    * Get interface list
+    *
+    * @return An array of strings containing all the interfaces on the system.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    @utf8InCpp String[] interfaceGetList();
+
+    // Must be kept in sync with constant in InterfaceConfiguration.java
+    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";
+
+   /**
+    * Get interface configuration
+    *
+    * @param ifName interface name
+    * @return An InterfaceConfigurationParcel for the specified interface.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+
+   /**
+    * Set interface configuration
+    *
+    * @param cfg Interface configuration to set
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    void interfaceSetCfg(in InterfaceConfigurationParcel cfg);
+
+   /**
+    * Set interface IPv6 privacy extensions
+    *
+    * @param ifName interface name
+    * @param enable whether to enable or disable this setting.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+
+   /**
+    * Clear all IP addresses on the given interface
+    *
+    * @param ifName interface name
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         POSIX errno.
+    */
+    void interfaceClearAddrs(in @utf8InCpp String ifName);
+
+   /**
+    * Enable or disable IPv6 on the given interface
+    *
+    * @param ifName interface name
+    * @param enable whether to enable or disable this setting.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+
+   /**
+    * Set interface MTU
+    *
+    * @param ifName interface name
+    * @param mtu MTU value
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+
+   /**
+    * Add forwarding rule/stats on given interface.
+    *
+    * @param intIface downstream interface
+    * @param extIface upstream interface
+    */
+    void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+
+   /**
+    * Remove forwarding rule/stats on given interface.
+    *
+    * @param intIface downstream interface
+    * @param extIface upstream interface
+    */
+    void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+
+   /**
+    * Set the values of tcp_{rmem,wmem}.
+    *
+    * @param rmemValues the target values of tcp_rmem, each value is separated by spaces
+    * @param wmemValues the target values of tcp_wmem, each value is separated by spaces
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+
+   /**
+    * Register unsolicited event listener
+    * Netd supports multiple unsolicited event listeners.
+    *
+    * @param listener unsolicited event listener to register
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void registerUnsolicitedEventListener(INetdUnsolicitedEventListener listener);
+
+    /**
+     * Add ingress interface filtering rules to a list of UIDs
+     *
+     * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
+     * whitelisted interface and loopback to be sent to the list of UIDs.
+     *
+     * Calling this method on one or more UIDs with an existing filtering rule but a different
+     * interface name will result in the filtering rule being updated to allow the new interface
+     * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
+     *
+     * @param ifName the name of the interface on which the filtering rules will allow packets to
+              be received.
+     * @param uids an array of UIDs which the filtering rules will be set
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+
+    /**
+     * Remove ingress interface filtering rules from a list of UIDs
+     *
+     * Clear the ingress interface filtering rules from the list of UIDs which were previously set
+     * by firewallAddUidInterfaceRules(). Ignore any uid which does not have filtering rule.
+     *
+     * @param uids an array of UIDs from which the filtering rules will be removed
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void firewallRemoveUidInterfaceRules(in int[] uids);
+
+   /**
+    * Request netd to change the current active network stats map.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void trafficSwapActiveStatsMap();
+
+   /**
+    * Retrieves OEM netd listener interface
+    *
+    * @return a IBinder object, it could be casted to oem specific interface.
+    */
+    IBinder getOemNetd();
 }
diff --git a/server/binder/android/net/INetdUnsolicitedEventListener.aidl b/server/binder/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..652a79c
--- /dev/null
+++ b/server/binder/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,145 @@
+/**
+ * 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.
+ */
+
+package android.net;
+
+/**
+ * Unsolicited netd events which are reported by the kernel via netlink.
+ * This one-way interface groups asynchronous notifications sent
+ * by netd to any process that registered itself via INetd.registerUnsolEventListener.
+ *
+ * {@hide}
+ */
+oneway interface INetdUnsolicitedEventListener {
+
+    /**
+     * Notifies that an interface has been idle/active for a certain period of time.
+     * It is the event for idletimer.
+     *
+     * @param isActive true for active status, false for idle
+     * @param timerLabel unique identifier of the idletimer.
+     *              Since NMS only set the identifier as int, only report event with int label.
+     * @param timestampNs kernel timestamp of this event, 0 for no timestamp
+     * @param uid uid of this event, -1 for no uid.
+     *            It represents the uid that was responsible for waking the radio.
+     */
+    void onInterfaceClassActivityChanged(
+            boolean isActive,
+            int timerLabel,
+            long timestampNs,
+            int uid);
+
+    /**
+     * Notifies that a specific interface reached its quota limit.
+     *
+     * @param alertName alert name of the quota limit
+     * @param ifName interface which reached the limit
+     */
+    void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+
+    /**
+     * Provides information on IPv6 DNS servers on a specific interface.
+     *
+     * @param ifName interface name
+     * @param lifetimeS lifetime for the DNS servers in seconds
+     * @param servers the address of servers.
+     *                  e.g. IpV6: "2001:4860:4860::6464"
+     *
+     */
+    void onInterfaceDnsServerInfo(
+            @utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+
+    /**
+     * Notifies that an address has updated on a specific interface.
+     *
+     * @param addr address that is being updated
+     * @param ifName the name of the interface on which the address is configured
+     * @param flags address flags, see ifa_flags in if_addr.h
+     * @param scope current scope of the address
+     */
+    void onInterfaceAddressUpdated(
+            @utf8InCpp String addr,
+            @utf8InCpp String ifName,
+            int flags,
+            int scope);
+
+    /**
+     * Notifies that an address has been removed on a specific interface.
+     *
+     * @param addr address of this change
+     * @param ifName the name of the interface that changed addresses
+     * @param flags address flags, see ifa_flags in if_addr.h
+     * @param scope address address scope
+     */
+    void onInterfaceAddressRemoved(
+            @utf8InCpp String addr,
+            @utf8InCpp String ifName,
+            int flags,
+            int scope);
+
+    /**
+     * Notifies that an interface has been added.
+     *
+     * @param ifName the name of the added interface
+     */
+    void onInterfaceAdded(@utf8InCpp String ifName);
+
+    /**
+     * Notifies that an interface has been removed.
+     *
+     * @param ifName the name of the removed interface
+     */
+    void onInterfaceRemoved(@utf8InCpp String ifName);
+
+    /**
+     * Notifies that the status of the specific interface has changed.
+     *
+     * @param ifName the name of the interface that changed status
+     * @param up true for interface up, false for down
+     */
+    void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+
+    /**
+     * Notifies that the link state of the specific interface has changed.
+     *
+     * @param ifName the name of the interface whose link state has changed
+     * @param up true for interface link state up, false for link state down
+     */
+    void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+
+    /**
+     * Notifies that an IP route has changed.
+     *
+     * @param updated true for update, false for remove
+     * @param route destination prefix of this route, e.g., "2001:db8::/64"
+     * @param gateway address of gateway, empty string for no gateway
+     * @param ifName interface name of this route, empty string for no interface
+     */
+    void onRouteChanged(
+            boolean updated,
+            @utf8InCpp String route,
+            @utf8InCpp String gateway,
+            @utf8InCpp String ifName);
+
+    /**
+     * Notifies that kernel has detected a socket sending data not wrapped
+     * inside a layer of SSL/TLS encryption.
+     *
+     * @param uid uid of this event
+     * @param hex packet content in hex format
+     */
+    void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/binder/android/net/UidRange.aidl b/server/binder/android/net/InterfaceConfigurationParcel.aidl
similarity index 61%
copy from server/binder/android/net/UidRange.aidl
copy to server/binder/android/net/InterfaceConfigurationParcel.aidl
index 55747d0..c20792c 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/server/binder/android/net/InterfaceConfigurationParcel.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -17,8 +17,17 @@
 package android.net;
 
 /**
- * An inclusive range of UIDs.
+ * Configuration details for a network interface.
  *
  * {@hide}
  */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+parcelable InterfaceConfigurationParcel {
+    @utf8InCpp String ifName;
+    @utf8InCpp String hwAddr;
+    @utf8InCpp String ipv4Addr;
+    int prefixLength;
+    /**
+    * Interface flags, String versions of IFF_* defined in netd/if.h
+    */
+    @utf8InCpp String[] flags;
+}
diff --git a/server/binder/android/net/UidRange.aidl b/server/binder/android/net/TetherStatsParcel.aidl
similarity index 72%
copy from server/binder/android/net/UidRange.aidl
copy to server/binder/android/net/TetherStatsParcel.aidl
index 55747d0..25e200c 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/server/binder/android/net/TetherStatsParcel.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -17,8 +17,14 @@
 package android.net;
 
 /**
- * An inclusive range of UIDs.
+ * The statistics of tethering interface
  *
  * {@hide}
  */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+parcelable TetherStatsParcel {
+    @utf8InCpp String iface;
+    long rxBytes;
+    long rxPackets;
+    long txBytes;
+    long txPackets;
+}
diff --git a/server/binder/android/net/UidRange.cpp b/server/binder/android/net/UidRange.cpp
deleted file mode 100644
index 4acb4c0..0000000
--- a/server/binder/android/net/UidRange.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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 "android/net/UidRange.h"
-
-#define LOG_TAG "UidRange"
-
-#include <binder/IBinder.h>
-#include <binder/Parcel.h>
-#include <log/log.h>
-#include <utils/Errors.h>
-
-using android::BAD_VALUE;
-using android::NO_ERROR;
-using android::Parcel;
-using android::status_t;
-
-namespace android {
-
-namespace net {
-
-UidRange::UidRange(int32_t start, int32_t stop) {
-    ALOG_ASSERT(start <= stop, "start UID must be less than or equal to stop UID");
-    mStart = start;
-    mStop = stop;
-}
-
-status_t UidRange::writeToParcel(Parcel* parcel) const {
-    /*
-     * Keep implementation in sync with writeToParcel() in
-     * frameworks/base/core/java/android/net/UidRange.java.
-     */
-    if (status_t err = parcel->writeInt32(mStart)) {
-        return err;
-    }
-    if (status_t err = parcel->writeInt32(mStop)) {
-        return err;
-    }
-    return NO_ERROR;
-}
-
-status_t UidRange::readFromParcel(const Parcel* parcel) {
-    /*
-     * Keep implementation in sync with readFromParcel() in
-     * frameworks/base/core/java/android/net/UidRange.java.
-     */
-    if (status_t err = parcel->readInt32(&mStart)) {
-        return err;
-    }
-    if (status_t err = parcel->readInt32(&mStop)) {
-        return err;
-    }
-    if (mStart > mStop) return BAD_VALUE;
-    return NO_ERROR;
-}
-
-void UidRange::setStart(int32_t uid) {
-    if (mStop != -1) {
-        ALOG_ASSERT(uid <= mStop, "start UID must be less than or equal to stop UID");
-    }
-    mStart = uid;
-}
-
-void UidRange::setStop(int32_t uid) {
-    if (mStart != -1) {
-        ALOG_ASSERT(mStart <= uid, "stop UID must be greater than or equal to start UID");
-    }
-    mStop = uid;
-}
-
-int32_t UidRange::getStart() const {
-    return mStart;
-}
-
-int32_t UidRange::getStop() const {
-    return mStop;
-}
-
-uint32_t UidRange::length() const {
-    if (mStart == -1 || mStop == -1) {
-        return 0;
-    }
-    return static_cast<uint32_t>(mStop - mStart + 1);
-}
-
-}  // namespace net
-
-}  // namespace android
diff --git a/server/binder/android/net/UidRange.h b/server/binder/android/net/UidRange.h
deleted file mode 100644
index c36beed..0000000
--- a/server/binder/android/net/UidRange.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 NETD_SERVER_ANDROID_NET_UID_RANGE_H
-#define NETD_SERVER_ANDROID_NET_UID_RANGE_H
-
-#include <binder/Parcelable.h>
-
-namespace android {
-
-namespace net {
-
-/*
- * C++ implementation of UidRange, a contiguous range of UIDs.
- */
-class UidRange : public Parcelable {
-public:
-    UidRange() = default;
-    virtual ~UidRange() = default;
-    UidRange(const UidRange& range) = default;
-    UidRange(int32_t start, int32_t stop);
-
-    status_t writeToParcel(Parcel* parcel) const override;
-    status_t readFromParcel(const Parcel* parcel) override;
-
-    /*
-     * Setters for UidRange start and stop UIDs.
-     */
-    void setStart(int32_t uid);
-    void setStop(int32_t uid);
-
-    /*
-     * Getters for UidRange start and stop UIDs.
-     */
-    int32_t getStart() const;
-    int32_t getStop() const;
-
-    /**
-     * Additional functions.
-     */
-    uint32_t length() const;
-
-
-    /**
-     * Operators and comparators.
-     */
-    friend bool operator<(const UidRange& lhs, const UidRange& rhs) {
-        return lhs.mStart != rhs.mStart ? (lhs.mStart < rhs.mStart) : (lhs.mStop < rhs.mStop);
-    }
-
-    friend bool operator==(const UidRange& lhs, const UidRange& rhs) {
-        return (lhs.mStart == rhs.mStart && lhs.mStop == rhs.mStop);
-    }
-
-    friend bool operator!=(const UidRange& lhs, const UidRange& rhs) {
-        return !(lhs == rhs);
-    }
-
-private:
-    int32_t mStart = -1;
-    int32_t mStop = -1;
-};
-
-}  // namespace net
-
-}  // namespace android
-
-#endif  // NETD_SERVER_ANDROID_NET_UID_RANGE_H
diff --git a/server/binder/android/net/UidRange.aidl b/server/binder/android/net/UidRangeParcel.aidl
similarity index 84%
rename from server/binder/android/net/UidRange.aidl
rename to server/binder/android/net/UidRangeParcel.aidl
index 55747d0..08fd491 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/server/binder/android/net/UidRangeParcel.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -21,4 +21,7 @@
  *
  * {@hide}
  */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+parcelable UidRangeParcel {
+    int start;
+    int stop;
+}
\ No newline at end of file
diff --git a/server/binder/android/net/metrics/INetdEventListener.aidl b/server/binder/android/net/metrics/INetdEventListener.aidl
index 7ec1f19..ef1b2cb 100644
--- a/server/binder/android/net/metrics/INetdEventListener.aidl
+++ b/server/binder/android/net/metrics/INetdEventListener.aidl
@@ -24,6 +24,8 @@
 oneway interface INetdEventListener {
     const int EVENT_GETADDRINFO = 1;
     const int EVENT_GETHOSTBYNAME = 2;
+    const int EVENT_GETHOSTBYADDR = 3;
+    const int EVENT_RES_NSEND = 4;
 
     const int REPORTING_LEVEL_NONE = 0;
     const int REPORTING_LEVEL_METRICS = 1;
@@ -46,8 +48,9 @@
      *        of ipAddresses if there were too many addresses to log.
      * @param uid the UID of the application that performed the query.
      */
-    void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, String hostname,
-            in String[] ipAddresses, int ipAddressesCount, int uid);
+    void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
+            @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses,
+            int ipAddressesCount, int uid);
 
     /**
      * Represents a private DNS validation success or failure.
@@ -109,4 +112,17 @@
      */
     void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets,
             in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+
+    /**
+     * Represents adding or removing a NAT64 prefix.
+     *
+     * @param netId the ID of the network the prefix was discovered on.
+     * @param added true if the NAT64 prefix was added, or false if the NAT64 prefix was removed.
+     *        There is only one prefix at a time for each netId. If a prefix is added, it replaces
+     *        the previous-added prefix.
+     * @param prefixString the detected NAT64 prefix as a string literal.
+     * @param prefixLength the prefix length associated with this NAT64 prefix.
+     */
+    void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString,
+            int prefixLength);
 }
diff --git a/server/binder/com/android/internal/net/IOemNetd.aidl b/server/binder/com/android/internal/net/IOemNetd.aidl
new file mode 100644
index 0000000..bd55106
--- /dev/null
+++ b/server/binder/com/android/internal/net/IOemNetd.aidl
@@ -0,0 +1,34 @@
+/**
+ * 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.internal.net;
+
+import com.android.internal.net.IOemNetdUnsolicitedEventListener;
+
+/** {@hide} */
+interface IOemNetd {
+   /**
+    * Returns true if the service is responding.
+    */
+    boolean isAlive();
+
+   /**
+    * Register oem unsolicited event listener
+    *
+    * @param listener oem unsolicited event listener to register
+    */
+    void registerOemUnsolicitedEventListener(IOemNetdUnsolicitedEventListener listener);
+}
diff --git a/server/binder/android/net/UidRange.aidl b/server/binder/com/android/internal/net/IOemNetdUnsolicitedEventListener.aidl
similarity index 60%
copy from server/binder/android/net/UidRange.aidl
copy to server/binder/com/android/internal/net/IOemNetdUnsolicitedEventListener.aidl
index 55747d0..e2c6882 100644
--- a/server/binder/android/net/UidRange.aidl
+++ b/server/binder/com/android/internal/net/IOemNetdUnsolicitedEventListener.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/**
+ * 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
+ *     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,
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
-package android.net;
+package com.android.internal.net;
 
 /**
- * An inclusive range of UIDs.
- *
  * {@hide}
  */
-parcelable UidRange cpp_header "binder/android/net/UidRange.h";
+oneway interface IOemNetdUnsolicitedEventListener {
+
+   /**
+    * Notifies that a listener is registered.
+    *
+    * It is a sample method and used for testing.
+    */
+    void onRegistered();
+}
diff --git a/server/main.cpp b/server/main.cpp
index aa65b15..b783ce5 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
+#include <chrono>
 #include <stdio.h>
 #include <stdlib.h>
 #include <signal.h>
 #include <errno.h>
 #include <string.h>
+#include <mutex>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -28,16 +30,14 @@
 
 #define LOG_TAG "Netd"
 
-#include "cutils/log.h"
-#include "utils/RWLock.h"
+#include "log/log.h"
 
+#include <android-base/properties.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
+#include <netdutils/Stopwatch.h>
 
-#include "CommandListener.h"
 #include "Controllers.h"
-#include "DnsProxyListener.h"
 #include "FwmarkServer.h"
 #include "MDnsSdListener.h"
 #include "NFLogListener.h"
@@ -45,50 +45,78 @@
 #include "NetdHwService.h"
 #include "NetdNativeService.h"
 #include "NetlinkManager.h"
-#include "Stopwatch.h"
+#include "Process.h"
 
-using android::status_t;
-using android::sp;
+#include "netd_resolv/resolv.h"
+#include "netd_resolv/resolv_stub.h"
+
 using android::IPCThreadState;
-using android::ProcessState;
-using android::defaultServiceManager;
-using android::net::CommandListener;
-using android::net::DnsProxyListener;
+using android::status_t;
+using android::String16;
 using android::net::FwmarkServer;
+using android::net::gCtls;
+using android::net::gLog;
+using android::net::makeNFLogListener;
 using android::net::NetdHwService;
 using android::net::NetdNativeService;
 using android::net::NetlinkManager;
 using android::net::NFLogListener;
-using android::net::makeNFLogListener;
-
-static void remove_pid_file();
-static bool write_pid_file();
+using android::netdutils::Stopwatch;
 
 const char* const PID_FILE_PATH = "/data/misc/net/netd_pid";
-const int PID_FILE_FLAGS = O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
-const mode_t PID_FILE_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;  // mode 0644, rw-r--r--
+constexpr const char DNSPROXYLISTENER_SOCKET_NAME[] = "dnsproxyd";
 
-android::RWLock android::net::gBigNetdLock;
+std::mutex android::net::gBigNetdLock;
+
+namespace {
+
+void getNetworkContextCallback(uint32_t netId, uint32_t uid, android_net_context* netcontext) {
+    gCtls->netCtrl.getNetworkContext(netId, uid, netcontext);
+}
+
+bool checkCallingPermissionCallback(const char* permission) {
+    return checkCallingPermission(String16(permission));
+}
+
+void logCallback(const char* msg) {
+    gLog.info(std::string(msg));
+}
+
+bool initDnsResolver() {
+    ResolverNetdCallbacks callbacks = {
+            .get_network_context = &getNetworkContextCallback,
+            .log = &logCallback,
+            .check_calling_permission = &checkCallingPermissionCallback,
+    };
+    return RESOLV_STUB.resolv_init(callbacks);
+}
+
+}  // namespace
 
 int main() {
-    using android::net::gCtls;
     Stopwatch s;
+    gLog.info("netd 1.0 starting");
 
-    ALOGI("Netd 1.0 starting");
-    remove_pid_file();
-
-    blockSigpipe();
+    android::net::process::removePidFile(PID_FILE_PATH);
+    android::net::process::blockSigPipe();
 
     // Before we do anything that could fork, mark CLOEXEC the UNIX sockets that we get from init.
     // FrameworkListener does this on initialization as well, but we only initialize these
     // components after having initialized other subsystems that can fork.
-    for (const auto& sock : { CommandListener::SOCKET_NAME,
-                              DnsProxyListener::SOCKET_NAME,
-                              FwmarkServer::SOCKET_NAME,
-                              MDnsSdListener::SOCKET_NAME }) {
+    for (const auto& sock :
+         {DNSPROXYLISTENER_SOCKET_NAME, FwmarkServer::SOCKET_NAME, MDnsSdListener::SOCKET_NAME}) {
         setCloseOnExec(sock);
     }
 
+    // Before we start any threads, populate the resolver stub pointers.
+    resolv_stub_init();
+
+    // Make sure BPF programs are loaded before doing anything
+    while (!android::base::WaitForProperty("bpf.progs_loaded", "1",
+           std::chrono::seconds(5))) {
+        ALOGD("netd waited 5s for bpf.progs_loaded, still waiting...");
+    }
+
     NetlinkManager *nm = NetlinkManager::Instance();
     if (nm == nullptr) {
         ALOGE("Unable to create NetlinkManager");
@@ -98,9 +126,6 @@
     gCtls = new android::net::Controllers();
     gCtls->init();
 
-    CommandListener cl;
-    nm->setBroadcaster((SocketListener *) &cl);
-
     if (nm->start()) {
         ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
         exit(1);
@@ -116,17 +141,19 @@
         logListener = std::move(result.value());
         auto status = gCtls->wakeupCtrl.init(logListener.get());
         if (!isOk(result)) {
-            ALOGE("Unable to init WakeupController: %s", toString(result).c_str());
+            gLog.error("Unable to init WakeupController: %s", toString(result).c_str());
             // We can still continue without wakeup packet logging.
         }
     }
 
     // Set local DNS mode, to prevent bionic from proxying
     // back to this service, recursively.
+    // TODO: Check if we could remove it since resolver cache no loger
+    // checks this environment variable after aosp/838050.
     setenv("ANDROID_DNS_MODE", "local", 1);
-    DnsProxyListener dpl(&gCtls->netCtrl, &gCtls->eventReporter);
-    if (dpl.startListener()) {
-        ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
+    // Note that only call initDnsResolver after gCtls initializing.
+    if (!initDnsResolver()) {
+        ALOGE("Unable to init resolver");
         exit(1);
     }
 
@@ -148,19 +175,9 @@
         ALOGE("Unable to start NetdNativeService: %d", ret);
         exit(1);
     }
-    ALOGI("Registering NetdNativeService: %.1fms", subTime.getTimeAndReset());
+    gLog.info("Registering NetdNativeService: %.1fms", subTime.getTimeAndReset());
 
-    /*
-     * Now that we're up, we can respond to commands. Starting the listener also tells
-     * NetworkManagementService that we are up and that our binder interface is ready.
-     */
-    if (cl.startListener()) {
-        ALOGE("Unable to start CommandListener (%s)", strerror(errno));
-        exit(1);
-    }
-    ALOGI("Starting CommandListener: %.1fms", subTime.getTimeAndReset());
-
-    write_pid_file();
+    android::net::process::ScopedPidFile pidFile(PID_FILE_PATH);
 
     // Now that netd is ready to process commands, advertise service
     // availability for HAL clients.
@@ -169,47 +186,13 @@
         ALOGE("Unable to start NetdHwService: %d", ret);
         exit(1);
     }
-    ALOGI("Registering NetdHwService: %.1fms", subTime.getTimeAndReset());
+    gLog.info("Registering NetdHwService: %.1fms", subTime.getTimeAndReset());
 
-    ALOGI("Netd started in %dms", static_cast<int>(s.timeTaken()));
+    gLog.info("Netd started in %dms", static_cast<int>(s.timeTaken()));
 
     IPCThreadState::self()->joinThreadPool();
 
-    ALOGI("Netd exiting");
-
-    remove_pid_file();
+    gLog.info("netd exiting");
 
     exit(0);
 }
-
-static bool write_pid_file() {
-    char pid_buf[INT32_STRLEN];
-    snprintf(pid_buf, sizeof(pid_buf), "%d\n", (int) getpid());
-
-    int fd = open(PID_FILE_PATH, PID_FILE_FLAGS, PID_FILE_MODE);
-    if (fd == -1) {
-        ALOGE("Unable to create pid file (%s)", strerror(errno));
-        return false;
-    }
-
-    // File creation is affected by umask, so make sure the right mode bits are set.
-    if (fchmod(fd, PID_FILE_MODE) == -1) {
-        ALOGE("failed to set mode 0%o on %s (%s)", PID_FILE_MODE, PID_FILE_PATH, strerror(errno));
-        close(fd);
-        remove_pid_file();
-        return false;
-    }
-
-    if (write(fd, pid_buf, strlen(pid_buf)) != (ssize_t)strlen(pid_buf)) {
-        ALOGE("Unable to write to pid file (%s)", strerror(errno));
-        close(fd);
-        remove_pid_file();
-        return false;
-    }
-    close(fd);
-    return true;
-}
-
-static void remove_pid_file() {
-    unlink(PID_FILE_PATH);
-}
diff --git a/server/ndc.cpp b/server/ndc.cpp
index 14f6654..dbf6458 100644
--- a/server/ndc.cpp
+++ b/server/ndc.cpp
@@ -16,174 +16,23 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <signal.h>
-#include <errno.h>
-#include <fcntl.h>
 
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/un.h>
+#include "NdcDispatcher.h"
 
-#include <cutils/sockets.h>
-#include <private/android_filesystem_config.h>
+namespace {
 
-static void usage(char *progname);
-static int do_monitor(int sock, int stop_after_cmd);
-static int do_cmd(int sock, int argc, char **argv);
-
-int main(int argc, char **argv) {
-    int sock;
-    int cmdOffset = 0;
-
-    if (argc < 2)
-        usage(argv[0]);
-
-    // try interpreting the first arg as the socket name - if it fails go back to netd
-
-    if ((sock = socket_local_client(argv[1],
-                                     ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                     SOCK_STREAM)) < 0) {
-        if ((sock = socket_local_client("netd",
-                                         ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                         SOCK_STREAM)) < 0) {
-            fprintf(stderr, "Error connecting (%s)\n", strerror(errno));
-            exit(4);
-        }
-    } else {
-        if (argc < 3) usage(argv[0]);
-        printf("Using alt socket %s\n", argv[1]);
-        cmdOffset = 1;
-    }
-
-    if (!strcmp(argv[1+cmdOffset], "monitor"))
-        exit(do_monitor(sock, 0));
-    exit(do_cmd(sock, argc-cmdOffset, &(argv[cmdOffset])));
-}
-
-static int do_cmd(int sock, int argc, char **argv) {
-    char *final_cmd;
-    char *conv_ptr;
-    int i;
-
-    /* Check if 1st arg is cmd sequence number */ 
-    strtol(argv[1], &conv_ptr, 10);
-    if (conv_ptr == argv[1]) {
-        final_cmd = strdup("0 ");
-    } else {
-        final_cmd = strdup("");
-    }
-    if (final_cmd == NULL) {
-        int res = errno;
-        perror("strdup failed");
-        return res;
-    }
-
-    for (i = 1; i < argc; i++) {
-        if (strchr(argv[i], '"')) {
-            perror("argument with embedded quotes not allowed");
-            free(final_cmd);
-            return 1;
-        }
-        bool needs_quoting = strchr(argv[i], ' ');
-        const char *format = needs_quoting ? "%s\"%s\"%s" : "%s%s%s";
-        char *tmp_final_cmd;
-
-        if (asprintf(&tmp_final_cmd, format, final_cmd, argv[i],
-                     (i == (argc - 1)) ? "" : " ") < 0) {
-            int res = errno;
-            perror("failed asprintf");
-            free(final_cmd);
-            return res;
-        }
-        free(final_cmd);
-        final_cmd = tmp_final_cmd;
-    }
-
-    if (write(sock, final_cmd, strlen(final_cmd) + 1) < 0) {
-        int res = errno;
-        perror("write");
-        free(final_cmd);
-        return res;
-    }
-    free(final_cmd);
-
-    return do_monitor(sock, 1);
-}
-
-static int do_monitor(int sock, int stop_after_cmd) {
-    char *buffer = (char *)malloc(4096);
-
-    if (!stop_after_cmd)
-        printf("[Connected to Netd]\n");
-
-    while(1) {
-        fd_set read_fds;
-        struct timeval to;
-        int rc = 0;
-
-        to.tv_sec = 10;
-        to.tv_usec = 0;
-
-        FD_ZERO(&read_fds);
-        FD_SET(sock, &read_fds);
-
-        rc = TEMP_FAILURE_RETRY(select(sock +1, &read_fds, NULL, NULL, &to));
-        if (rc < 0) {
-            int res = errno;
-            fprintf(stderr, "Error in select (%s)\n", strerror(res));
-            free(buffer);
-            return res;
-        }
-        if (rc == 0) {
-            continue;
-        }
-        if (!FD_ISSET(sock, &read_fds)) {
-            continue;
-        }
-
-        memset(buffer, 0, 4096);
-        if ((rc = read(sock, buffer, 4096)) <= 0) {
-            int res = errno;
-            if (rc == 0)
-                fprintf(stderr, "Lost connection to Netd - did it crash?\n");
-            else
-                fprintf(stderr, "Error reading data (%s)\n", strerror(res));
-            free(buffer);
-            if (rc == 0)
-                return ECONNRESET;
-            return res;
-        }
-
-        int offset = 0;
-        int i = 0;
-
-        for (i = 0; i < rc; i++) {
-            if (buffer[i] == '\0') {
-                int code;
-                char tmp[4];
-
-                strncpy(tmp, buffer + offset, 3);
-                tmp[3] = '\0';
-                code = atoi(tmp);
-
-                printf("%s\n", buffer + offset);
-                if (stop_after_cmd) {
-                    if (code >= 200 && code < 600)
-                        return 0;
-                }
-                offset = i + 1;
-            }
-        }
-    }
-    free(buffer);
-    return 0;
-}
-
-static void usage(char *progname) {
-    fprintf(stderr, "Usage: %s [<sockname>] ([monitor] | ([<cmd_seq_num>] <cmd> [arg ...]))\n", progname);
+void usage(char* progname) {
+    fprintf(stderr, "Usage: %s (<cmd> [arg ...])\n", progname);
     exit(1);
 }
+
+}  // namespace
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        usage(argv[0]);
+    }
+
+    android::net::NdcDispatcher nd;
+    exit(nd.dispatchCommand(argc - 1, argv + 1));
+}
diff --git a/server/netd.rc b/server/netd.rc
index 8100636..1fc6ab5 100644
--- a/server/netd.rc
+++ b/server/netd.rc
@@ -1,8 +1,11 @@
 service netd /system/bin/netd
     class main
-    socket netd stream 0660 root system
     socket dnsproxyd stream 0660 root inet
     socket mdns stream 0660 root system
     socket fwmarkd stream 0660 root inet
     onrestart restart zygote
     onrestart restart zygote_secondary
+    # b/121354779: netd itself is not updatable, but on startup it dlopen()s the resolver library
+    # from the DNS resolver APEX. Mark it as updatable so init won't start it until all APEX
+    # packages are ready.
+    updatable
diff --git a/server/oem_iptables_hook.cpp b/server/oem_iptables_hook.cpp
index 4c839a2..c7f8dbf 100644
--- a/server/oem_iptables_hook.cpp
+++ b/server/oem_iptables_hook.cpp
@@ -22,26 +22,38 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <string>
+
 #define LOG_TAG "OemIptablesHook"
-#include <cutils/log.h>
+#include <log/log.h>
 #include <logwrap/logwrap.h>
 #include "NetdConstants.h"
 
-static bool oemCleanupHooks() {
-    std::string cmd =
-        "*filter\n"
-        ":oem_out -\n"
-        ":oem_fwd -\n"
-        "COMMIT\n"
-        "*nat\n"
-        ":oem_nat_pre -\n"
-        "COMMIT\n";
+namespace {
 
-    return (execIptablesRestore(V4V6, cmd) == 0);
+const char OEM_SCRIPT_PATH[] = "/system/bin/oem-iptables-init.sh";
+
+bool oemCleanupHooks() {
+    static const std::string cmd4 =
+            "*filter\n"
+            ":oem_out -\n"
+            ":oem_fwd -\n"
+            "COMMIT\n"
+            "*nat\n"
+            ":oem_nat_pre -\n"
+            "COMMIT\n";
+
+    static const std::string cmd6 =
+            "*filter\n"
+            ":oem_out -\n"
+            ":oem_fwd -\n"
+            "COMMIT\n";
+
+    return (execIptablesRestore(V4, cmd4) == 0 && execIptablesRestore(V6, cmd6) == 0);
 }
 
-static bool oemInitChains() {
-    int ret = system(OEM_SCRIPT_PATH);
+bool oemInitChains() {
+    int ret = system(OEM_SCRIPT_PATH);  // NOLINT(cert-env33-c)
     if ((-1 == ret) || (0 != WEXITSTATUS(ret))) {
         ALOGE("%s failed: %s", OEM_SCRIPT_PATH, strerror(errno));
         oemCleanupHooks();
@@ -50,6 +62,7 @@
     return true;
 }
 
+}  // namespace
 
 void setupOemIptablesHook() {
     if (0 == access(OEM_SCRIPT_PATH, R_OK | X_OK)) {
diff --git a/tests/Android.bp b/tests/Android.bp
index 6c4bbb0..3c35ceb 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -13,8 +13,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_library {
+cc_test_library {
     name: "libnetd_test_tun_interface",
+    defaults: ["netd_defaults"],
     srcs: [
         "tun_interface.cpp"
     ],
@@ -25,3 +26,75 @@
     ],
 }
 
+cc_test_library {
+    name: "libnetd_test_unsol_service",
+    defaults: ["netd_defaults"],
+    srcs: [
+        "TestUnsolService.cpp"
+    ],
+    include_dirs: [
+        "system/netd/include",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libcutils",
+        "liblog",
+        "libnetutils",
+        "libsysutils",
+        "libutils",
+        "netd_aidl_interface-cpp",
+    ],
+}
+
+cc_test {
+    name: "netd_integration_test",
+    test_suites: ["device-tests"],
+    defaults: ["netd_defaults"],
+    srcs: [
+        ":netd_integration_test_shared",
+        "binder_test.cpp",
+        "bpf_base_test.cpp",
+        "netd_test.cpp",
+        "netlink_listener_test.cpp",
+    ],
+    include_dirs: ["system/netd/server"],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libcrypto",
+        "libcutils",
+        "liblog",
+        "libnetd_client",
+        "libnetutils",
+        "libprocessgroup",
+        "libssl",
+        "libutils",
+    ],
+    static_libs: [
+        "libcap",
+        "libnetd_test_tun_interface",
+        "libnetd_test_unsol_service",
+        "libbpf_android",
+        "liblogwrap",
+        "libnetdbpf",
+        "libnetdutils",
+        "libqtaguid",
+        "netd_aidl_interface-cpp",
+        "netd_event_listener_interface-cpp",
+        "oemnetd_aidl_interface-cpp",
+    ],
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    sanitize: {
+        address: true,
+        recover: [ "all" ],
+    },
+}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index 302ad1e..0000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# 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.
-#
-LOCAL_PATH := $(call my-dir)
-
-# APCT build target
-include $(CLEAR_VARS)
-LOCAL_MODULE := netd_integration_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CFLAGS += -Wno-varargs
-
-EXTRA_LDLIBS := -lpthread
-LOCAL_SHARED_LIBRARIES += libbase libbinder libbpf libcrypto libcutils liblog \
-                          libnetd_client libnetutils libssl libutils
-LOCAL_STATIC_LIBRARIES += libnetd_test_dnsresponder liblogwrap libnetdaidl_static \
-                          libnetdutils libnetd_test_tun_interface libbpf
-LOCAL_AIDL_INCLUDES := system/netd/server/binder
-LOCAL_C_INCLUDES += system/netd/include system/netd/binder/include \
-                    system/netd/server system/core/logwrapper/include \
-                    system/netd/tests/dns_responder \
-                    system/core/libnetutils/include \
-                    bionic/libc/dns/include
-# netd_integration_test.cpp is currently empty and exists only so that we can do:
-# runtest -x system/netd/tests/netd_integration_test.cpp
-LOCAL_SRC_FILES := binder_test.cpp \
-                   bpf_base_test.cpp \
-                   dns_responder/dns_responder.cpp \
-                   dns_tls_test.cpp \
-                   netd_integration_test.cpp \
-                   netd_test.cpp \
-                   ../server/NetdConstants.cpp \
-                   ../server/binder/android/net/metrics/INetdEventListener.aidl \
-                   ../server/dns/DnsTlsDispatcher.cpp \
-                   ../server/dns/DnsTlsQueryMap.cpp \
-                   ../server/dns/DnsTlsTransport.cpp \
-                   ../server/dns/DnsTlsServer.cpp \
-                   ../server/dns/DnsTlsSessionCache.cpp \
-                   ../server/dns/DnsTlsSocket.cpp \
-                   ../server/InterfaceController.cpp \
-                   ../server/NetlinkCommands.cpp \
-                   ../server/XfrmController.cpp
-LOCAL_MODULE_TAGS := eng tests
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-include $(BUILD_NATIVE_TEST)
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index be02773..7b18f9f 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -13,6 +13,14 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!--
+    To run test manually:
+        make -j netd_integration_test && \
+        tradefed.sh run template/local_min -\-template:map test=netd_integration_test
+
+    For a list of options, see:
+        tools/tradefederation/core/src/com/android/tradefed/testtype/GTest.java
+-->
 <configuration description="Config for netd_integration_test">
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
diff --git a/tests/TestUnsolService.cpp b/tests/TestUnsolService.cpp
new file mode 100644
index 0000000..134328b
--- /dev/null
+++ b/tests/TestUnsolService.cpp
@@ -0,0 +1,149 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "TestUnsolService"
+
+#include <cinttypes>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include "android/net/BnNetdUnsolicitedEventListener.h"
+
+#include "TestUnsolService.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace net {
+
+TestUnsolService* TestUnsolService::start() {
+    IPCThreadState::self()->disableBackgroundScheduling(true);
+    sp<IServiceManager> sm(defaultServiceManager());
+
+    auto service = new TestUnsolService();
+    const status_t ret = sm->addService(String16(getServiceName()), service, false,
+                                        IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
+    if (ret != android::OK) {
+        return nullptr;
+    }
+    sp<ProcessState> ps(ProcessState::self());
+    ps->startThreadPool();
+    ps->giveThreadPoolName();
+
+    return service;
+}
+
+namespace {
+
+bool containsSubstring(const std::vector<std::string>& vec, const std::string& subStr) {
+    return std::any_of(vec.begin(), vec.end(), [&subStr](const std::string& str) {
+        return (str.find(subStr) != std::string::npos);
+    });
+}
+
+}  // namespace
+
+void TestUnsolService::checkTarget(const std::string& ifName, uint32_t flag) {
+    if (containsSubstring(tarVec, ifName)) {
+        received_ |= flag;
+        maybeNotify();
+    };
+}
+
+void TestUnsolService::maybeNotify() {
+    // We only have test case for below event currently.
+    if (received_ == (InterfaceAddressUpdated | InterfaceAdded | InterfaceRemoved |
+                      InterfaceLinkStatusChanged | RouteChanged)) {
+        std::lock_guard lock(cv_mutex_);
+        cv_.notify_one();
+    }
+}
+
+binder::Status TestUnsolService::onInterfaceClassActivityChanged(bool isActive, int label,
+                                                                 int64_t timestamp, int uid) {
+    events_.push_back(StringPrintf("onInterfaceClassActivityChanged %d %d %" PRId64 "%d", isActive,
+                                   label, timestamp, uid));
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onQuotaLimitReached(const std::string& alertName,
+                                                     const std::string& ifName) {
+    events_.push_back(StringPrintf("onQuotaLimitReached %s %s", alertName.c_str(), ifName.c_str()));
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceDnsServerInfo(const std::string& ifName,
+                                                          int64_t lifetime,
+                                                          const std::vector<std::string>& servers) {
+    events_.push_back(StringPrintf("onInterfaceDnsServerInfo %s %" PRId64 "%s", ifName.c_str(),
+                                   lifetime, base::Join(servers, ", ").c_str()));
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceAddressUpdated(const std::string&,
+                                                           const std::string& ifName, int, int) {
+    checkTarget(ifName, InterfaceAddressUpdated);
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceAddressRemoved(const std::string& addr,
+                                                           const std::string& ifName, int flags,
+                                                           int scope) {
+    events_.push_back(StringPrintf("onInterfaceAddressRemoved %s %s %d %d", addr.c_str(),
+                                   ifName.c_str(), flags, scope));
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceAdded(const std::string& ifName) {
+    checkTarget(ifName, InterfaceAdded);
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceRemoved(const std::string& ifName) {
+    checkTarget(ifName, InterfaceRemoved);
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceChanged(const std::string& ifName, bool status) {
+    events_.push_back(StringPrintf("onInterfaceChanged %s %d", ifName.c_str(), status));
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onInterfaceLinkStateChanged(const std::string& ifName, bool) {
+    checkTarget(ifName, InterfaceLinkStatusChanged);
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onRouteChanged(bool, const std::string&, const std::string&,
+                                                const std::string& ifName) {
+    checkTarget(ifName, RouteChanged);
+    return binder::Status::ok();
+}
+
+binder::Status TestUnsolService::onStrictCleartextDetected(int uid, const std::string& hex) {
+    events_.push_back(StringPrintf("onStrictCleartextDetected %d %s", uid, hex.c_str()));
+    return binder::Status::ok();
+}
+
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/tests/TestUnsolService.h b/tests/TestUnsolService.h
new file mode 100644
index 0000000..cbd2a06
--- /dev/null
+++ b/tests/TestUnsolService.h
@@ -0,0 +1,89 @@
+/**
+ * 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 _NETD_TEST_UNSOLSERVICE_H_
+#define _NETD_TEST_UNSOLSERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include <binder/BinderService.h>
+
+#include "android/net/BnNetdUnsolicitedEventListener.h"
+
+enum UnsolEvent : uint32_t {
+    InterfaceClassActivity = 1 << 0,
+    QuotaLimitReached = 1 << 1,
+    InterfaceDnsServersAdded = 1 << 2,
+    InterfaceAddressUpdated = 1 << 3,
+    InterfaceAddressRemoved = 1 << 4,
+    InterfaceAdded = 1 << 5,
+    InterfaceRemoved = 1 << 6,
+    InterfaceChanged = 1 << 7,
+    InterfaceLinkStatusChanged = 1 << 8,
+    RouteChanged = 1 << 9,
+    StrictCleartextDetected = 1 << 10,
+};
+
+namespace android {
+namespace net {
+
+class TestUnsolService : public BinderService<TestUnsolService>,
+                         public BnNetdUnsolicitedEventListener {
+  public:
+    TestUnsolService() = default;
+    ~TestUnsolService() = default;
+    static TestUnsolService* start();
+    static char const* getServiceName() { return "testUnsol"; }
+    const std::vector<std::string>& getEvents() const { return events_; }
+    void clearEvents() { events_.clear(); }
+    uint32_t getReceived() { return received_; }
+    std::condition_variable& getCv() { return cv_; }
+    std::mutex& getCvMutex() { return cv_mutex_; }
+    binder::Status onInterfaceClassActivityChanged(bool isActive, int label, int64_t timestamp,
+                                                   int uid) override;
+    binder::Status onQuotaLimitReached(const std::string& alertName,
+                                       const std::string& ifName) override;
+    binder::Status onInterfaceDnsServerInfo(const std::string& ifName, int64_t lifetime,
+                                            const std::vector<std::string>& servers) override;
+    binder::Status onInterfaceAddressUpdated(const std::string& addr, const std::string& ifName,
+                                             int flags, int scope) override;
+    binder::Status onInterfaceAddressRemoved(const std::string& addr, const std::string& ifName,
+                                             int flags, int scope) override;
+    binder::Status onInterfaceAdded(const std::string& ifName) override;
+    binder::Status onInterfaceRemoved(const std::string& ifName) override;
+    binder::Status onInterfaceChanged(const std::string& ifName, bool status) override;
+    binder::Status onInterfaceLinkStateChanged(const std::string& ifName, bool status) override;
+    binder::Status onRouteChanged(bool updated, const std::string& route,
+                                  const std::string& gateway, const std::string& ifName) override;
+    binder::Status onStrictCleartextDetected(int uid, const std::string& hex) override;
+
+    std::vector<std::string> tarVec;
+
+  private:
+    void maybeNotify();
+    void checkTarget(const std::string& ifName, uint32_t flag);
+
+    std::vector<std::string> events_;
+    std::mutex cv_mutex_;
+    std::condition_variable cv_;
+    uint32_t received_{};
+};
+
+}  // namespace net
+}  // namespace android
+
+#endif  // _NETD_TEST_UNSOLSERVICE_H_
diff --git a/tests/benchmarks/Android.bp b/tests/benchmarks/Android.bp
new file mode 100644
index 0000000..c14253d
--- /dev/null
+++ b/tests/benchmarks/Android.bp
@@ -0,0 +1,52 @@
+// APCT build target for metrics tests
+
+cc_benchmark {
+    name: "netd_benchmark",
+    defaults: ["netd_defaults"],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libnetd_client",
+        "libnetdutils",
+    ],
+    static_libs: [
+        "libnetd_test_dnsresponder",
+        "libutils",
+        "dnsresolver_aidl_interface-cpp",
+        "netd_aidl_interface-cpp",
+        "netd_event_listener_interface-cpp",
+    ],
+    aidl: {
+        include_dirs: ["system/netd/server/binder"],
+    },
+    include_dirs: [
+        "system/netd/include",
+        "system/netd/client",
+        "system/netd/resolv/include",
+        "system/netd/server",
+        "system/netd/server/binder",
+        "system/netd/resolv/dns_responder",
+    ],
+    srcs: [
+        "main.cpp",
+        "connect_benchmark.cpp",
+        "dns_benchmark.cpp",
+    ],
+}
+
+cc_benchmark {
+    name: "bpf_benchmark",
+    defaults: ["netd_defaults"],
+    shared_libs: [
+        "libbase",
+        "libbpf_android",
+        "libnetdutils",
+    ],
+    static_libs: [
+        "libutils",
+    ],
+    srcs: [
+        "bpf_benchmark.cpp",
+    ],
+}
diff --git a/tests/benchmarks/Android.mk b/tests/benchmarks/Android.mk
deleted file mode 100644
index fdc6e7c..0000000
--- a/tests/benchmarks/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# 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.
-#
-LOCAL_PATH := $(call my-dir)
-
-# APCT build target for metrics tests
-include $(CLEAR_VARS)
-LOCAL_MODULE := netd_benchmark
-LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CFLAGS += -Wno-varargs
-
-EXTRA_LDLIBS := -lpthread
-LOCAL_SHARED_LIBRARIES += libbase libbinder liblog libnetd_client libnetdutils
-LOCAL_STATIC_LIBRARIES += libnetd_test_dnsresponder libutils
-
-LOCAL_AIDL_INCLUDES := system/netd/server/binder
-LOCAL_C_INCLUDES += system/netd/include \
-                    system/netd/client \
-                    system/netd/server \
-                    system/netd/server/binder \
-                    system/netd/tests/dns_responder \
-                    bionic/libc/dns/include
-
-LOCAL_SRC_FILES := main.cpp \
-                   connect_benchmark.cpp \
-                   dns_benchmark.cpp \
-                   ../../server/binder/android/net/metrics/INetdEventListener.aidl
-
-LOCAL_MODULE_TAGS := eng tests
-
-include $(BUILD_NATIVE_BENCHMARK)
diff --git a/tests/benchmarks/bpf_benchmark.cpp b/tests/benchmarks/bpf_benchmark.cpp
new file mode 100644
index 0000000..2c1ed1e
--- /dev/null
+++ b/tests/benchmarks/bpf_benchmark.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 <android-base/stringprintf.h>
+#include <benchmark/benchmark.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+
+constexpr uint32_t TEST_MAP_SIZE = 10000;
+
+using android::base::StringPrintf;
+using android::bpf::BpfMap;
+
+class BpfBenchMark : public ::benchmark::Fixture {
+  public:
+    BpfBenchMark() : mBpfTestMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC) {}
+    BpfMap<uint32_t, uint32_t> mBpfTestMap;
+};
+
+BENCHMARK_DEFINE_F(BpfBenchMark, MapWriteNewEntry)(benchmark::State& state) {
+    for (auto _ : state) {
+        expectOk(mBpfTestMap.writeValue(state.range(0), state.range(0), BPF_NOEXIST));
+    }
+}
+
+BENCHMARK_DEFINE_F(BpfBenchMark, MapUpdateEntry)(benchmark::State& state) {
+    for (uint32_t i = 0; i < TEST_MAP_SIZE; i++) {
+        expectOk(mBpfTestMap.writeValue(i, i, BPF_NOEXIST));
+    }
+    for (auto _ : state) {
+        expectOk(mBpfTestMap.writeValue(state.range(0), state.range(0) + 1, BPF_EXIST));
+    }
+}
+
+BENCHMARK_DEFINE_F(BpfBenchMark, MapDeleteAddEntry)(benchmark::State& state) {
+    for (uint32_t i = 0; i < TEST_MAP_SIZE; i++) {
+        expectOk(mBpfTestMap.writeValue(i, i, BPF_NOEXIST));
+    }
+    for (auto _ : state) {
+        expectOk(mBpfTestMap.deleteValue(state.range(0)));
+        expectOk(mBpfTestMap.writeValue(state.range(0), state.range(0) + 1, BPF_NOEXIST));
+    }
+}
+
+BENCHMARK_DEFINE_F(BpfBenchMark, WaitForRcu)(benchmark::State& state) {
+    for (auto _ : state) {
+        int ret = android::bpf::synchronizeKernelRCU();
+        if (ret) {
+            state.SkipWithError(
+                    StringPrintf("synchronizeKernelRCU() failed with errno=%d", -ret).c_str());
+        }
+    }
+}
+
+BENCHMARK_REGISTER_F(BpfBenchMark, MapUpdateEntry)->Arg(1);
+BENCHMARK_REGISTER_F(BpfBenchMark, MapWriteNewEntry)->Arg(1);
+BENCHMARK_REGISTER_F(BpfBenchMark, MapDeleteAddEntry)->Arg(1);
+BENCHMARK_REGISTER_F(BpfBenchMark, WaitForRcu)->Arg(1);
+BENCHMARK_MAIN();
diff --git a/tests/benchmarks/connect_benchmark.cpp b/tests/benchmarks/connect_benchmark.cpp
index 384feb1..e08fb01 100644
--- a/tests/benchmarks/connect_benchmark.cpp
+++ b/tests/benchmarks/connect_benchmark.cpp
@@ -20,28 +20,7 @@
  * See README.md for general notes.
  *
  * This set of benchmarks measures the throughput of connect() calls on a single thread for IPv4 and
- * IPv6 under the following scenarios:
- *
- *  - FWmark disabled (::ANDROID_NO_USE_FWMARK_CLIENT).
- *
- *      The control case for other high load benchmarks. Essentially just testing performance of
- *      the kernel connect call. In real world use fwmark should stay on in order for traffic to
- *      be routed properly.
- *
- *  - FWmark enabled only for metrics (::ANDROID_FWMARK_METRICS_ONLY).
- *
- *      The default mode up to and including 7.1. Every time connect() is called on an AF_INET or
- *      AF_INET6 socket, netdclient sends a synchronous message to fwmarkserver to get the socket
- *      marked. Only the fields that are useful for marking or for metrics are sent in this mode;
- *      other fields are set to null for the RPC and ignored.
- *
- *  - FWmark enabled for all events.
- *
- *      The default mode starting from 7.1.2. As well as the normal connect() reporting, extra
- *      fields are filled in to log the IP and port of the connection.
- *
- *      A second synchronous message is sent to fwmarkserver after the connection completes, to
- *      record latency. This message is forwarded to the system server over a oneway binder call.
+ * IPv6.
  *
  * Realtime timed tests
  * ====================
@@ -91,15 +70,14 @@
 #include <android-base/stringprintf.h>
 #include <benchmark/benchmark.h>
 #include <log/log.h>
+#include <netdutils/Stopwatch.h>
 #include <utils/StrongPointer.h>
 
 #include "FwmarkClient.h"
 #include "SockDiag.h"
-#include "Stopwatch.h"
-#include "android/net/metrics/INetdEventListener.h"
 
 using android::base::StringPrintf;
-using android::net::metrics::INetdEventListener;
+using android::netdutils::Stopwatch;
 
 static int bindAndListen(int s) {
     sockaddr_in6 sin6 = { .sin6_family = AF_INET6 };
@@ -119,7 +97,7 @@
 }
 
 static void ipv4_loopback(benchmark::State& state, const bool waitBetweenRuns) {
-    const int listensocket = socket(AF_INET6, SOCK_STREAM, 0);
+    const int listensocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     const int port = bindAndListen(listensocket);
     if (port == -1) {
         state.SkipWithError("Unable to bind server socket");
@@ -131,7 +109,7 @@
     uint64_t iterations = 0;
 
     while (state.KeepRunning()) {
-        int sock = socket(AF_INET, SOCK_STREAM, 0);
+        int sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
         if (sock < 0) {
             state.SkipWithError(StringPrintf("socket() failed with errno=%d", errno).c_str());
             break;
@@ -155,7 +133,7 @@
 
         sockaddr_in6 client;
         socklen_t clientlen = sizeof(client);
-        int accepted = accept(listensocket, (sockaddr *) &client, &clientlen);
+        int accepted = accept4(listensocket, (sockaddr*) &client, &clientlen, SOCK_CLOEXEC);
         if (accepted < 0) {
             state.SkipWithError(StringPrintf("accept() failed with errno=%d", errno).c_str());
             close(sock);
@@ -176,7 +154,7 @@
 }
 
 static void ipv6_loopback(benchmark::State& state, const bool waitBetweenRuns) {
-    const int listensocket = socket(AF_INET6, SOCK_STREAM, 0);
+    const int listensocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     const int port = bindAndListen(listensocket);
     if (port == -1) {
         state.SkipWithError("Unable to bind server socket");
@@ -188,7 +166,7 @@
     uint64_t iterations = 0;
 
     while (state.KeepRunning()) {
-        int sock = socket(AF_INET6, SOCK_STREAM, 0);
+        int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
         if (sock < 0) {
             state.SkipWithError(StringPrintf("socket() failed with errno=%d", errno).c_str());
             break;
@@ -212,7 +190,7 @@
 
         sockaddr_in6 client;
         socklen_t clientlen = sizeof(client);
-        int accepted = accept(listensocket, (sockaddr *) &client, &clientlen);
+        int accepted = accept4(listensocket, (sockaddr*) &client, &clientlen, SOCK_CLOEXEC);
         if (accepted < 0) {
             state.SkipWithError(StringPrintf("accept() failed with errno=%d", errno).c_str());
             close(sock);
@@ -232,118 +210,35 @@
     }
 }
 
-static void run_at_reporting_level(decltype(ipv4_loopback) benchmarkFunction,
-                                   ::benchmark::State& state, const int reportingLevel,
-                                   const bool waitBetweenRuns) {
-    // Our master thread (thread_index == 0) will control setup and teardown for other threads.
-    const bool isMaster = (state.thread_index == 0);
-
-    // Previous values of env variables used by fwmarkclient (only read/written by master thread)
-    const std::string savedSettings[] = {
-        FwmarkClient::ANDROID_NO_USE_FWMARK_CLIENT,
-        FwmarkClient::ANDROID_FWMARK_METRICS_ONLY
-    };
-    std::map<std::string, std::string> prevSettings;
-
-    // SETUP
-    if (isMaster) {
-        for (const auto setting : savedSettings) {
-            const char* prevEnvStr = getenv(setting.c_str());
-            if (prevEnvStr != nullptr) {
-                prevSettings[setting.c_str()] = prevEnvStr;
-            }
-        }
-        switch (reportingLevel) {
-            case INetdEventListener::REPORTING_LEVEL_NONE:
-                setenv(FwmarkClient::ANDROID_NO_USE_FWMARK_CLIENT, "", 1);
-                break;
-            case INetdEventListener::REPORTING_LEVEL_METRICS:
-                unsetenv(FwmarkClient::ANDROID_NO_USE_FWMARK_CLIENT);
-                setenv(FwmarkClient::ANDROID_FWMARK_METRICS_ONLY, "", 1);
-                break;
-            case INetdEventListener::REPORTING_LEVEL_FULL:
-                unsetenv(FwmarkClient::ANDROID_NO_USE_FWMARK_CLIENT);
-                unsetenv(FwmarkClient::ANDROID_FWMARK_METRICS_ONLY);
-                break;
-        }
-    }
-
-    // TEST
+static void run(decltype(ipv4_loopback) benchmarkFunction, ::benchmark::State& state,
+                const bool waitBetweenRuns) {
     benchmarkFunction(state, waitBetweenRuns);
-
-    // TEARDOWN
-    if (isMaster) {
-        for (const auto setting : savedSettings) {
-            if (prevSettings.count(setting)) {
-                setenv(setting.c_str(), prevSettings[setting].c_str(), 1);
-            } else {
-                unsetenv(setting.c_str());
-            }
-        }
-    }
 }
 
 constexpr int MIN_THREADS = 1;
 constexpr int MAX_THREADS = 1;
 constexpr double MIN_TIME = 0.5 /* seconds */;
 
-static void ipv4_metrics_reporting_no_fwmark(::benchmark::State& state) {
-    run_at_reporting_level(ipv4_loopback, state, INetdEventListener::REPORTING_LEVEL_NONE, true);
+// IPv4 benchmarks under no load
+static void ipv4_no_load(::benchmark::State& state) {
+    run(ipv4_loopback, state, true);
 }
-BENCHMARK(ipv4_metrics_reporting_no_fwmark)->MinTime(MIN_TIME)->UseManualTime();
-
-// IPv4 metrics under low load
-static void ipv4_metrics_reporting_no_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv4_loopback, state, INetdEventListener::REPORTING_LEVEL_METRICS, true);
-}
-BENCHMARK(ipv4_metrics_reporting_no_load)->MinTime(MIN_TIME)->UseManualTime();
-
-static void ipv4_full_reporting_no_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv4_loopback, state, INetdEventListener::REPORTING_LEVEL_FULL, true);
-}
-BENCHMARK(ipv4_full_reporting_no_load)->MinTime(MIN_TIME)->UseManualTime();
+BENCHMARK(ipv4_no_load)->MinTime(MIN_TIME)->UseManualTime();
 
 // IPv4 benchmarks under high load
-static void ipv4_metrics_reporting_high_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv4_loopback, state, INetdEventListener::REPORTING_LEVEL_METRICS,
-            false);
+static void ipv4_high_load(::benchmark::State& state) {
+    run(ipv4_loopback, state, false);
 }
-BENCHMARK(ipv4_metrics_reporting_high_load)
-    ->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
-
-static void ipv4_full_reporting_high_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv4_loopback, state, INetdEventListener::REPORTING_LEVEL_FULL, false);
-}
-BENCHMARK(ipv4_full_reporting_high_load)
-    ->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
+BENCHMARK(ipv4_high_load)->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
 
 // IPv6 raw connect() without using fwmark
-static void ipv6_metrics_reporting_no_fwmark(::benchmark::State& state) {
-    run_at_reporting_level(ipv6_loopback, state, INetdEventListener::REPORTING_LEVEL_NONE, true);
+static void ipv6_no_load(::benchmark::State& state) {
+    run(ipv6_loopback, state, true);
 }
-BENCHMARK(ipv6_metrics_reporting_no_fwmark)->MinTime(MIN_TIME)->UseManualTime();
-
-// IPv6 metrics under low load
-static void ipv6_metrics_reporting_no_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv6_loopback, state, INetdEventListener::REPORTING_LEVEL_METRICS, true);
-}
-BENCHMARK(ipv6_metrics_reporting_no_load)->MinTime(MIN_TIME)->UseManualTime();
-
-static void ipv6_full_reporting_no_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv6_loopback, state, INetdEventListener::REPORTING_LEVEL_FULL, true);
-}
-BENCHMARK(ipv6_full_reporting_no_load)->MinTime(MIN_TIME)->UseManualTime();
+BENCHMARK(ipv6_no_load)->MinTime(MIN_TIME)->UseManualTime();
 
 // IPv6 benchmarks under high load
-static void ipv6_metrics_reporting_high_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv6_loopback, state, INetdEventListener::REPORTING_LEVEL_METRICS,
-            false);
+static void ipv6_high_load(::benchmark::State& state) {
+    run(ipv6_loopback, state, false);
 }
-BENCHMARK(ipv6_metrics_reporting_high_load)
-    ->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
-
-static void ipv6_full_reporting_high_load(::benchmark::State& state) {
-    run_at_reporting_level(ipv6_loopback, state, INetdEventListener::REPORTING_LEVEL_FULL, false);
-}
-BENCHMARK(ipv6_full_reporting_high_load)
-    ->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
+BENCHMARK(ipv6_high_load)->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
diff --git a/tests/benchmarks/dns_benchmark.cpp b/tests/benchmarks/dns_benchmark.cpp
index f412176..d25f536 100644
--- a/tests/benchmarks/dns_benchmark.cpp
+++ b/tests/benchmarks/dns_benchmark.cpp
@@ -22,31 +22,6 @@
  * This set of benchmarks measures the throughput of getaddrinfo() on between 1 and 32 threads for
  * the purpose of keeping track of the maximum load that netd can reasonably handle.
  *
- * The benchmark fixture runs in 3 different modes:
- *
- *  - getaddrinfo_log_nothing
- *
- *      The control case. Switches all kinds of DNS events reporting off and runs getaddrinfo() in a
- *      loop until the timer expires.
- *
- *      This was the default and only mode in all versions before 7.0.
- *
- *  - getaddrinfo_log_metrics
- *
- *      DNS Logging Liteâ„¢ includes staple favourites such as event type (getaddrinfo/gethostbyname),
- *      return code, and latency, but misses out in-depth information such as resolved IP addresses.
- *
- *      It is expected that this is a little slower than getaddrinfo_log_nothing because of the
- *      overhead, but not particularly worse, since it is a oneway binder call without too much data
- *      being sent per event.
- *
- *      This was the default mode between versions 7.0 and 7.1 inclusive.
- *
- *  - getaddrinfo_log_everything
- *
- *      DNS Logging, in full HD, includes extra non-metrics fields such as hostname, a truncated
- *      list of resolved addresses, total resolved address count, and originating UID.
- *
  * Useful measurements
  * ===================
  *
@@ -66,15 +41,12 @@
 
 #include <android-base/stringprintf.h>
 #include <benchmark/benchmark.h>
-#include <utils/String16.h>
-#include <utils/StrongPointer.h>
 
-#include "dns_responder_client.h"
 #include "NetdClient.h"
-#include "android/net/metrics/INetdEventListener.h"
+#include "dns_responder_client.h"
+#include "netd_resolv/params.h"  // MAXNS
 
 using android::base::StringPrintf;
-using android::net::metrics::INetdEventListener;
 
 constexpr int MIN_THREADS = 1;
 constexpr int MAX_THREADS = 32;
@@ -97,14 +69,13 @@
 
             dns.SetupDNSServers(MAXNS, mappings, &mDns, &servers);
 
-            const std::vector<int> mDefaultParams_Binder = { 300, 25, 8, 8 };
+            const std::vector<int> mDefaultParams_Binder = {300, 25, 8, 8, 1000};
             dns.SetResolversForNetwork(servers, domains, mDefaultParams_Binder);
         }
     }
 
     void TearDown(const ::benchmark::State& state) override {
         if (state.thread_index == 0) {
-            dns.ShutdownDNSServers(&mDns);
             dns.TearDown();
         }
     }
@@ -113,11 +84,7 @@
         return mappings;
     }
 
-    android::sp<android::net::INetd> getNetd() const {
-        return dns.mNetdSrv;
-    }
-
-    void getaddrinfo_until_done(benchmark::State &state) {
+    void benchmark(benchmark::State& state) {
         while (state.KeepRunning()) {
             const uint32_t ofs = arc4random_uniform(getMappings().size());
             const auto& mapping = getMappings()[ofs];
@@ -133,62 +100,11 @@
             }
         }
     }
-
-    void benchmark_at_reporting_level(benchmark::State &state, int metricsLevel) {
-        const bool isMaster = (state.thread_index == 0);
-        int oldMetricsLevel;
-
-        // SETUP
-        if (isMaster) {
-            auto rv = getNetd()->getMetricsReportingLevel(&oldMetricsLevel);
-            if (!rv.isOk()) {
-                state.SkipWithError(StringPrintf("Failed saving metrics reporting level: %s",
-                        rv.toString8().string()).c_str());
-                return;
-            }
-            rv = getNetd()->setMetricsReportingLevel(metricsLevel);
-            if (!rv.isOk()) {
-                state.SkipWithError(StringPrintf("Failed changing metrics reporting: %s",
-                        rv.toString8().string()).c_str());
-                return;
-            }
-        }
-
-        // TEST
-        getaddrinfo_until_done(state);
-
-        // TEARDOWN
-        if (isMaster) {
-            auto rv = getNetd()->setMetricsReportingLevel(oldMetricsLevel);
-            if (!rv.isOk()) {
-                state.SkipWithError(StringPrintf("Failed restoring metrics reporting level: %s",
-                        rv.toString8().string()).c_str());
-                return;
-            }
-        }
-    }
 };
 
-// DNS calls without any metrics logged or sent.
-BENCHMARK_DEFINE_F(DnsFixture, getaddrinfo_log_nothing)(benchmark::State& state) {
-    benchmark_at_reporting_level(state, INetdEventListener::REPORTING_LEVEL_NONE);
+BENCHMARK_DEFINE_F(DnsFixture, getaddrinfo)(benchmark::State& state) {
+    benchmark(state);
 }
-BENCHMARK_REGISTER_F(DnsFixture, getaddrinfo_log_nothing)
-    ->ThreadRange(MIN_THREADS, MAX_THREADS)
-    ->UseRealTime();
-
-// DNS calls with metrics only (netId, latency, return code) sent to the system server.
-BENCHMARK_DEFINE_F(DnsFixture, getaddrinfo_log_metrics)(benchmark::State& state) {
-    benchmark_at_reporting_level(state, INetdEventListener::REPORTING_LEVEL_METRICS);
-}
-BENCHMARK_REGISTER_F(DnsFixture, getaddrinfo_log_metrics)
-    ->ThreadRange(MIN_THREADS, MAX_THREADS)
-    ->UseRealTime();
-
-// DNS calls with all information logged and sent to the system server.
-BENCHMARK_DEFINE_F(DnsFixture, getaddrinfo_log_everything)(benchmark::State& state) {
-    benchmark_at_reporting_level(state, INetdEventListener::REPORTING_LEVEL_FULL);
-}
-BENCHMARK_REGISTER_F(DnsFixture, getaddrinfo_log_everything)
+BENCHMARK_REGISTER_F(DnsFixture, getaddrinfo)
     ->ThreadRange(MIN_THREADS, MAX_THREADS)
     ->UseRealTime();
diff --git a/tests/binder_test.cpp b/tests/binder_test.cpp
index 257a076..917bf5d 100644
--- a/tests/binder_test.cpp
+++ b/tests/binder_test.cpp
@@ -17,81 +17,107 @@
  */
 
 #include <cerrno>
+#include <chrono>
 #include <cinttypes>
+#include <condition_variable>
 #include <cstdint>
 #include <cstdio>
 #include <cstdlib>
+#include <mutex>
 #include <set>
 #include <vector>
 
+#include <dirent.h>
 #include <fcntl.h>
 #include <ifaddrs.h>
-#include <netdb.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/if_tun.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
 #include <openssl/base64.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <binder/IPCThreadState.h>
+#include <bpf/BpfMap.h>
 #include <bpf/BpfUtils.h>
+#include <com/android/internal/net/BnOemNetdUnsolicitedEventListener.h>
+#include <com/android/internal/net/IOemNetd.h>
 #include <cutils/multiuser.h>
 #include <gtest/gtest.h>
 #include <logwrap/logwrap.h>
+#include <netdbpf/bpf_shared.h>
 #include <netutils/ifc.h>
-
+#include "Fwmark.h"
 #include "InterfaceController.h"
+#include "NetdClient.h"
 #include "NetdConstants.h"
-#include "Stopwatch.h"
+#include "TestUnsolService.h"
 #include "XfrmController.h"
-#include "tun_interface.h"
 #include "android/net/INetd.h"
-#include "android/net/UidRange.h"
 #include "binder/IServiceManager.h"
+#include "netdutils/Stopwatch.h"
 #include "netdutils/Syscalls.h"
+#include "netid_client.h"  // NETID_UNSET
+#include "tun_interface.h"
 
 #define IP_PATH "/system/bin/ip"
 #define IP6TABLES_PATH "/system/bin/ip6tables"
 #define IPTABLES_PATH "/system/bin/iptables"
 #define TUN_DEV "/dev/tun"
+#define RAW_TABLE "raw"
+#define MANGLE_TABLE "mangle"
+#define FILTER_TABLE "filter"
+#define NAT_TABLE "nat"
 
-using namespace android;
-using namespace android::base;
-using namespace android::binder;
+namespace binder = android::binder;
+
+using android::IBinder;
+using android::IServiceManager;
+using android::sp;
+using android::String16;
+using android::String8;
+using android::base::Join;
+using android::base::ReadFileToString;
 using android::base::StartsWith;
-using android::bpf::hasBpfSupport;
+using android::base::StringPrintf;
+using android::base::Trim;
 using android::net::INetd;
+using android::net::InterfaceConfigurationParcel;
+using android::net::InterfaceController;
+using android::net::TetherStatsParcel;
 using android::net::TunInterface;
-using android::net::UidRange;
-using android::net::XfrmController;
+using android::net::UidRangeParcel;
 using android::netdutils::sSyscalls;
-using android::os::PersistableBundle;
-
-#define SKIP_IF_BPF_SUPPORTED         \
-    do {                              \
-        if (hasBpfSupport()) return;  \
-    } while (0);
+using android::netdutils::Stopwatch;
 
 static const char* IP_RULE_V4 = "-4";
 static const char* IP_RULE_V6 = "-6";
 static const int TEST_NETID1 = 65501;
 static const int TEST_NETID2 = 65502;
+
+// Use maximum reserved appId for applications to avoid conflict with existing
+// uids.
+static const int TEST_UID1 = 99999;
+static const int TEST_UID2 = 99998;
+
 constexpr int BASE_UID = AID_USER_OFFSET * 5;
 
 static const std::string NO_SOCKET_ALLOW_RULE("! owner UID match 0-4294967294");
 static const std::string ESP_ALLOW_RULE("esp");
 
 class BinderTest : public ::testing::Test {
-
-public:
+  public:
     BinderTest() {
-        sp<IServiceManager> sm = defaultServiceManager();
+        sp<IServiceManager> sm = android::defaultServiceManager();
         sp<IBinder> binder = sm->getService(String16("netd"));
         if (binder != nullptr) {
-            mNetd = interface_cast<INetd>(binder);
+            mNetd = android::interface_cast<INetd>(binder);
         }
     }
 
@@ -102,42 +128,56 @@
     void TearDown() override {
         mNetd->networkDestroy(TEST_NETID1);
         mNetd->networkDestroy(TEST_NETID2);
+        setNetworkForProcess(NETID_UNSET);
+        // Restore default network
+        if (mStoredDefaultNetwork >= 0) mNetd->networkSetDefault(mStoredDefaultNetwork);
     }
 
-    bool allocateIpSecResources(bool expectOk, int32_t *spi);
+    bool allocateIpSecResources(bool expectOk, int32_t* spi);
 
     // Static because setting up the tun interface takes about 40ms.
     static void SetUpTestCase() {
         ASSERT_EQ(0, sTun.init());
+        ASSERT_EQ(0, sTun2.init());
         ASSERT_LE(sTun.name().size(), static_cast<size_t>(IFNAMSIZ));
+        ASSERT_LE(sTun2.name().size(), static_cast<size_t>(IFNAMSIZ));
     }
 
     static void TearDownTestCase() {
         // Closing the socket removes the interface and IP addresses.
         sTun.destroy();
+        sTun2.destroy();
     }
 
     static void fakeRemoteSocketPair(int *clientSocket, int *serverSocket, int *acceptedSocket);
 
-protected:
+    void createVpnNetworkWithUid(bool secure, uid_t uid, int vpnNetId = TEST_NETID2,
+                                 int fallthroughNetId = TEST_NETID1);
+
+  protected:
+    // Use -1 to represent that default network was not modified because
+    // real netId must be an unsigned value.
+    int mStoredDefaultNetwork = -1;
     sp<INetd> mNetd;
     static TunInterface sTun;
+    static TunInterface sTun2;
 };
 
 TunInterface BinderTest::sTun;
+TunInterface BinderTest::sTun2;
 
 class TimedOperation : public Stopwatch {
-public:
+  public:
     explicit TimedOperation(const std::string &name): mName(name) {}
     virtual ~TimedOperation() {
         fprintf(stderr, "    %s: %6.1f ms\n", mName.c_str(), timeTaken());
     }
 
-private:
+  private:
     std::string mName;
 };
 
-TEST_F(BinderTest, TestIsAlive) {
+TEST_F(BinderTest, IsAlive) {
     TimedOperation t("isAlive RPC");
     bool isAlive = false;
     mNetd->isAlive(&isAlive);
@@ -150,9 +190,8 @@
 
 static std::vector<std::string> runCommand(const std::string& command) {
     std::vector<std::string> lines;
-    FILE *f;
-
-    if ((f = popen(command.c_str(), "r")) == nullptr) {
+    FILE *f = popen(command.c_str(), "r");  // NOLINT(cert-env33-c)
+    if (f == nullptr) {
         perror("popen");
         return lines;
     }
@@ -186,9 +225,9 @@
 
 static bool iptablesRuleExists(const char *binary,
                                const char *chainName,
-                               const std::string expectedRule) {
+                               const std::string& expectedRule) {
     std::vector<std::string> rules = listIptablesRule(binary, chainName);
-    for(std::string &rule: rules) {
+    for (const auto& rule : rules) {
         if(rule.find(expectedRule) != std::string::npos) {
             return true;
         }
@@ -206,7 +245,7 @@
            iptablesRuleExists(IP6TABLES_PATH, chainName, ESP_ALLOW_RULE);
 }
 
-TEST_F(BinderTest, TestFirewallReplaceUidChain) {
+TEST_F(BinderTest, FirewallReplaceUidChain) {
     SKIP_IF_BPF_SUPPORTED;
 
     std::string chainName = StringPrintf("netd_binder_test_%u", arc4random_uniform(10000));
@@ -220,7 +259,7 @@
     bool ret;
     {
         TimedOperation op(StringPrintf("Programming %d-UID whitelist chain", kNumUids));
-        mNetd->firewallReplaceUidChain(String16(chainName.c_str()), true, uids, &ret);
+        mNetd->firewallReplaceUidChain(chainName, true, uids, &ret);
     }
     EXPECT_EQ(true, ret);
     EXPECT_EQ((int) uids.size() + 9, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
@@ -229,7 +268,7 @@
     EXPECT_EQ(true, iptablesEspAllowRuleExists(chainName.c_str()));
     {
         TimedOperation op("Clearing whitelist chain");
-        mNetd->firewallReplaceUidChain(String16(chainName.c_str()), false, noUids, &ret);
+        mNetd->firewallReplaceUidChain(chainName, false, noUids, &ret);
     }
     EXPECT_EQ(true, ret);
     EXPECT_EQ(5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
@@ -237,7 +276,7 @@
 
     {
         TimedOperation op(StringPrintf("Programming %d-UID blacklist chain", kNumUids));
-        mNetd->firewallReplaceUidChain(String16(chainName.c_str()), false, uids, &ret);
+        mNetd->firewallReplaceUidChain(chainName, false, uids, &ret);
     }
     EXPECT_EQ(true, ret);
     EXPECT_EQ((int) uids.size() + 5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
@@ -247,7 +286,7 @@
 
     {
         TimedOperation op("Clearing blacklist chain");
-        mNetd->firewallReplaceUidChain(String16(chainName.c_str()), false, noUids, &ret);
+        mNetd->firewallReplaceUidChain(chainName, false, noUids, &ret);
     }
     EXPECT_EQ(true, ret);
     EXPECT_EQ(5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
@@ -255,69 +294,145 @@
 
     // Check that the call fails if iptables returns an error.
     std::string veryLongStringName = "netd_binder_test_UnacceptablyLongIptablesChainName";
-    mNetd->firewallReplaceUidChain(String16(veryLongStringName.c_str()), true, noUids, &ret);
+    mNetd->firewallReplaceUidChain(veryLongStringName, true, noUids, &ret);
     EXPECT_EQ(false, ret);
 }
 
-TEST_F(BinderTest, TestVirtualTunnelInterface) {
-    static const struct TestData {
-        const std::string& family;
-        const std::string& deviceName;
-        const std::string& localAddress;
-        const std::string& remoteAddress;
+TEST_F(BinderTest, IpSecTunnelInterface) {
+    const struct TestData {
+        const std::string family;
+        const std::string deviceName;
+        const std::string localAddress;
+        const std::string remoteAddress;
         int32_t iKey;
         int32_t oKey;
+        int32_t ifId;
     } kTestData[] = {
-        {"IPV4", "test_vti", "127.0.0.1", "8.8.8.8", 0x1234 + 53, 0x1234 + 53},
-        {"IPV6", "test_vti6", "::1", "2001:4860:4860::8888", 0x1234 + 50, 0x1234 + 50},
+            {"IPV4", "ipsec_test", "127.0.0.1", "8.8.8.8", 0x1234 + 53, 0x1234 + 53, 0xFFFE},
+            {"IPV6", "ipsec_test6", "::1", "2001:4860:4860::8888", 0x1234 + 50, 0x1234 + 50,
+             0xFFFE},
     };
 
-    for (unsigned int i = 0; i < arraysize(kTestData); i++) {
+    for (size_t i = 0; i < std::size(kTestData); i++) {
         const auto& td = kTestData[i];
 
         binder::Status status;
 
-        // Create Virtual Tunnel Interface.
-        status = mNetd->addVirtualTunnelInterface(td.deviceName, td.localAddress, td.remoteAddress,
-                                                  td.iKey, td.oKey);
+        // Create Tunnel Interface.
+        status = mNetd->ipSecAddTunnelInterface(td.deviceName, td.localAddress, td.remoteAddress,
+                                                td.iKey, td.oKey, td.ifId);
         EXPECT_TRUE(status.isOk()) << td.family << status.exceptionMessage();
 
-        // Update Virtual Tunnel Interface.
-        status = mNetd->updateVirtualTunnelInterface(td.deviceName, td.localAddress,
-                                                     td.remoteAddress, td.iKey, td.oKey);
+        // Check that the interface exists
+        EXPECT_NE(0U, if_nametoindex(td.deviceName.c_str()));
+
+        // Update Tunnel Interface.
+        status = mNetd->ipSecUpdateTunnelInterface(td.deviceName, td.localAddress, td.remoteAddress,
+                                                   td.iKey, td.oKey, td.ifId);
         EXPECT_TRUE(status.isOk()) << td.family << status.exceptionMessage();
 
-        // Remove Virtual Tunnel Interface.
-        status = mNetd->removeVirtualTunnelInterface(td.deviceName);
+        // Remove Tunnel Interface.
+        status = mNetd->ipSecRemoveTunnelInterface(td.deviceName);
         EXPECT_TRUE(status.isOk()) << td.family << status.exceptionMessage();
+
+        // Check that the interface no longer exists
+        EXPECT_EQ(0U, if_nametoindex(td.deviceName.c_str()));
     }
 }
 
+TEST_F(BinderTest, IpSecSetEncapSocketOwner) {
+    android::base::unique_fd uniqueFd(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+    android::os::ParcelFileDescriptor sockFd(std::move(uniqueFd));
+
+    int sockOptVal = UDP_ENCAP_ESPINUDP;
+    setsockopt(sockFd.get(), IPPROTO_UDP, UDP_ENCAP, &sockOptVal, sizeof(sockOptVal));
+
+    binder::Status res = mNetd->ipSecSetEncapSocketOwner(sockFd, 1001);
+    EXPECT_TRUE(res.isOk());
+
+    struct stat info;
+    EXPECT_EQ(0, fstat(sockFd.get(), &info));
+    EXPECT_EQ(1001, (int) info.st_uid);
+}
+
 // IPsec tests are not run in 32 bit mode; both 32-bit kernels and
 // mismatched ABIs (64-bit kernel with 32-bit userspace) are unsupported.
 #if INTPTR_MAX != INT32_MAX
+
+using android::net::XfrmController;
+
+static const int XFRM_DIRECTIONS[] = {static_cast<int>(android::net::XfrmDirection::IN),
+                                      static_cast<int>(android::net::XfrmDirection::OUT)};
+static const int ADDRESS_FAMILIES[] = {AF_INET, AF_INET6};
+
 #define RETURN_FALSE_IF_NEQ(_expect_, _ret_) \
         do { if ((_expect_) != (_ret_)) return false; } while(false)
-bool BinderTest::allocateIpSecResources(bool expectOk, int32_t *spi) {
-    netdutils::Status status = XfrmController::ipSecAllocateSpi(0, "::", "::1", 123, spi);
+bool BinderTest::allocateIpSecResources(bool expectOk, int32_t* spi) {
+    android::netdutils::Status status = XfrmController::ipSecAllocateSpi(0, "::", "::1", 123, spi);
     SCOPED_TRACE(status);
     RETURN_FALSE_IF_NEQ(status.ok(), expectOk);
 
     // Add a policy
-    status = XfrmController::ipSecAddSecurityPolicy(0, 0, "::", "::1", 123, 0, 0);
+    status = XfrmController::ipSecAddSecurityPolicy(0, AF_INET6, 0, "::", "::1", 123, 0, 0, 0);
     SCOPED_TRACE(status);
     RETURN_FALSE_IF_NEQ(status.ok(), expectOk);
 
     // Add an ipsec interface
-    status = netdutils::statusFromErrno(
-            XfrmController::addVirtualTunnelInterface(
-                    "ipsec_test", "::", "::1", 0xF00D, 0xD00D, false),
-            "addVirtualTunnelInterface");
-    return (status.ok() == expectOk);
+    return expectOk == XfrmController::ipSecAddTunnelInterface("ipsec_test", "::", "::1", 0xF00D,
+                                                               0xD00D, 0xE00D, false)
+                               .ok();
 }
 
-TEST_F(BinderTest, TestXfrmControllerInit) {
-    netdutils::Status status;
+TEST_F(BinderTest, XfrmDualSelectorTunnelModePoliciesV4) {
+    android::binder::Status status;
+
+    // Repeat to ensure cleanup and recreation works correctly
+    for (int i = 0; i < 2; i++) {
+        for (int direction : XFRM_DIRECTIONS) {
+            for (int addrFamily : ADDRESS_FAMILIES) {
+                status = mNetd->ipSecAddSecurityPolicy(0, addrFamily, direction, "127.0.0.5",
+                                                       "127.0.0.6", 123, 0, 0, 0);
+                EXPECT_TRUE(status.isOk())
+                        << " family: " << addrFamily << " direction: " << direction;
+            }
+        }
+
+        // Cleanup
+        for (int direction : XFRM_DIRECTIONS) {
+            for (int addrFamily : ADDRESS_FAMILIES) {
+                status = mNetd->ipSecDeleteSecurityPolicy(0, addrFamily, direction, 0, 0, 0);
+                EXPECT_TRUE(status.isOk());
+            }
+        }
+    }
+}
+
+TEST_F(BinderTest, XfrmDualSelectorTunnelModePoliciesV6) {
+    binder::Status status;
+
+    // Repeat to ensure cleanup and recreation works correctly
+    for (int i = 0; i < 2; i++) {
+        for (int direction : XFRM_DIRECTIONS) {
+            for (int addrFamily : ADDRESS_FAMILIES) {
+                status = mNetd->ipSecAddSecurityPolicy(0, addrFamily, direction, "2001:db8::f00d",
+                                                       "2001:db8::d00d", 123, 0, 0, 0);
+                EXPECT_TRUE(status.isOk())
+                        << " family: " << addrFamily << " direction: " << direction;
+            }
+        }
+
+        // Cleanup
+        for (int direction : XFRM_DIRECTIONS) {
+            for (int addrFamily : ADDRESS_FAMILIES) {
+                status = mNetd->ipSecDeleteSecurityPolicy(0, addrFamily, direction, 0, 0, 0);
+                EXPECT_TRUE(status.isOk());
+            }
+        }
+    }
+}
+
+TEST_F(BinderTest, XfrmControllerInit) {
+    android::netdutils::Status status;
     status = XfrmController::Init();
     SCOPED_TRACE(status);
 
@@ -337,22 +452,19 @@
     ASSERT_TRUE(allocateIpSecResources(true, &spi));
 
     // Clean up
-    status = XfrmController::ipSecDeleteSecurityAssociation(0, "::", "::1", 123, spi, 0);
+    status = XfrmController::ipSecDeleteSecurityAssociation(0, "::", "::1", 123, spi, 0, 0);
     SCOPED_TRACE(status);
     ASSERT_TRUE(status.ok());
 
-    status = XfrmController::ipSecDeleteSecurityPolicy(0, 0, "::", "::1", 0, 0);
+    status = XfrmController::ipSecDeleteSecurityPolicy(0, AF_INET6, 0, 0, 0, 0);
     SCOPED_TRACE(status);
     ASSERT_TRUE(status.ok());
 
     // Remove Virtual Tunnel Interface.
-    status = netdutils::statusFromErrno(
-            XfrmController::removeVirtualTunnelInterface("ipsec_test"),
-            "removeVirtualTunnelInterface");
-
-    ASSERT_TRUE(status.ok());
+    ASSERT_TRUE(XfrmController::ipSecRemoveTunnelInterface("ipsec_test").ok());
 }
-#endif
+
+#endif  // INTPTR_MAX != INT32_MAX
 
 static int bandwidthDataSaverEnabled(const char *binary) {
     std::vector<std::string> lines = listIptablesRule(binary, "bw_data_saver");
@@ -406,7 +518,7 @@
     return enabled6;
 }
 
-TEST_F(BinderTest, TestBandwidthEnableDataSaver) {
+TEST_F(BinderTest, BandwidthEnableDataSaver) {
     const int wasEnabled = getDataSaverState();
     ASSERT_NE(-1, wasEnabled);
 
@@ -430,16 +542,16 @@
     }
 }
 
-static bool ipRuleExistsForRange(const uint32_t priority, const UidRange& range,
-        const std::string& action, const char* ipVersion) {
+static bool ipRuleExistsForRange(const uint32_t priority, const UidRangeParcel& range,
+                                 const std::string& action, const char* ipVersion) {
     // Output looks like this:
     //   "12500:\tfrom all fwmark 0x0/0x20000 iif lo uidrange 1000-2000 prohibit"
     std::vector<std::string> rules = listIpRules(ipVersion);
 
     std::string prefix = StringPrintf("%" PRIu32 ":", priority);
-    std::string suffix = StringPrintf(" iif lo uidrange %d-%d %s\n",
-            range.getStart(), range.getStop(), action.c_str());
-    for (std::string line : rules) {
+    std::string suffix =
+            StringPrintf(" iif lo uidrange %d-%d %s\n", range.start, range.stop, action.c_str());
+    for (const auto& line : rules) {
         if (android::base::StartsWith(line, prefix) && android::base::EndsWith(line, suffix)) {
             return true;
         }
@@ -447,19 +559,32 @@
     return false;
 }
 
-static bool ipRuleExistsForRange(const uint32_t priority, const UidRange& range,
-        const std::string& action) {
+static bool ipRuleExistsForRange(const uint32_t priority, const UidRangeParcel& range,
+                                 const std::string& action) {
     bool existsIp4 = ipRuleExistsForRange(priority, range, action, IP_RULE_V4);
     bool existsIp6 = ipRuleExistsForRange(priority, range, action, IP_RULE_V6);
     EXPECT_EQ(existsIp4, existsIp6);
     return existsIp4;
 }
 
-TEST_F(BinderTest, TestNetworkInterfaces) {
-    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, "").isOk());
-    EXPECT_EQ(EEXIST, mNetd->networkCreatePhysical(TEST_NETID1, "").serviceSpecificErrorCode());
-    EXPECT_EQ(EEXIST, mNetd->networkCreateVpn(TEST_NETID1, false, true).serviceSpecificErrorCode());
-    EXPECT_TRUE(mNetd->networkCreateVpn(TEST_NETID2, false, true).isOk());
+namespace {
+
+UidRangeParcel makeUidRangeParcel(int start, int stop) {
+    UidRangeParcel res;
+    res.start = start;
+    res.stop = stop;
+
+    return res;
+}
+
+}  // namespace
+
+TEST_F(BinderTest, NetworkInterfaces) {
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_EQ(EEXIST, mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE)
+                              .serviceSpecificErrorCode());
+    EXPECT_EQ(EEXIST, mNetd->networkCreateVpn(TEST_NETID1, true).serviceSpecificErrorCode());
+    EXPECT_TRUE(mNetd->networkCreateVpn(TEST_NETID2, true).isOk());
 
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
     EXPECT_EQ(EBUSY,
@@ -468,20 +593,19 @@
     EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID2, sTun.name()).isOk());
     EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID2).isOk());
+    EXPECT_EQ(ENONET, mNetd->networkDestroy(TEST_NETID1).serviceSpecificErrorCode());
 }
 
-TEST_F(BinderTest, TestNetworkUidRules) {
+TEST_F(BinderTest, NetworkUidRules) {
     const uint32_t RULE_PRIORITY_SECURE_VPN = 12000;
 
-    EXPECT_TRUE(mNetd->networkCreateVpn(TEST_NETID1, false, true).isOk());
-    EXPECT_EQ(EEXIST, mNetd->networkCreateVpn(TEST_NETID1, false, true).serviceSpecificErrorCode());
+    EXPECT_TRUE(mNetd->networkCreateVpn(TEST_NETID1, true).isOk());
+    EXPECT_EQ(EEXIST, mNetd->networkCreateVpn(TEST_NETID1, true).serviceSpecificErrorCode());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
-    std::vector<UidRange> uidRanges = {
-        {BASE_UID + 8005, BASE_UID + 8012},
-        {BASE_UID + 8090, BASE_UID + 8099}
-    };
-    UidRange otherRange(BASE_UID + 8190, BASE_UID + 8299);
+    std::vector<UidRangeParcel> uidRanges = {makeUidRangeParcel(BASE_UID + 8005, BASE_UID + 8012),
+                                             makeUidRangeParcel(BASE_UID + 8090, BASE_UID + 8099)};
+    UidRangeParcel otherRange = makeUidRangeParcel(BASE_UID + 8190, BASE_UID + 8299);
     std::string suffix = StringPrintf("lookup %s ", sTun.name().c_str());
 
     EXPECT_TRUE(mNetd->networkAddUidRanges(TEST_NETID1, uidRanges).isOk());
@@ -499,13 +623,11 @@
     EXPECT_EQ(ENONET, mNetd->networkDestroy(TEST_NETID1).serviceSpecificErrorCode());
 }
 
-TEST_F(BinderTest, TestNetworkRejectNonSecureVpn) {
+TEST_F(BinderTest, NetworkRejectNonSecureVpn) {
     constexpr uint32_t RULE_PRIORITY = 12500;
 
-    std::vector<UidRange> uidRanges = {
-        {BASE_UID + 150, BASE_UID + 224},
-        {BASE_UID + 226, BASE_UID + 300}
-    };
+    std::vector<UidRangeParcel> uidRanges = {makeUidRangeParcel(BASE_UID + 150, BASE_UID + 224),
+                                             makeUidRangeParcel(BASE_UID + 226, BASE_UID + 300)};
 
     const std::vector<std::string> initialRulesV4 = listIpRules(IP_RULE_V4);
     const std::vector<std::string> initialRulesV6 = listIpRules(IP_RULE_V6);
@@ -538,7 +660,7 @@
 
 // Create a socket pair that isLoopbackSocket won't think is local.
 void BinderTest::fakeRemoteSocketPair(int *clientSocket, int *serverSocket, int *acceptedSocket) {
-    *serverSocket = socket(AF_INET6, SOCK_STREAM, 0);
+    *serverSocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     struct sockaddr_in6 server6 = { .sin6_family = AF_INET6, .sin6_addr = sTun.dstAddr() };
     ASSERT_EQ(0, bind(*serverSocket, (struct sockaddr *) &server6, sizeof(server6)));
 
@@ -546,13 +668,13 @@
     ASSERT_EQ(0, getsockname(*serverSocket, (struct sockaddr *) &server6, &addrlen));
     ASSERT_EQ(0, listen(*serverSocket, 10));
 
-    *clientSocket = socket(AF_INET6, SOCK_STREAM, 0);
+    *clientSocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     struct sockaddr_in6 client6 = { .sin6_family = AF_INET6, .sin6_addr = sTun.srcAddr() };
     ASSERT_EQ(0, bind(*clientSocket, (struct sockaddr *) &client6, sizeof(client6)));
     ASSERT_EQ(0, connect(*clientSocket, (struct sockaddr *) &server6, sizeof(server6)));
     ASSERT_EQ(0, getsockname(*clientSocket, (struct sockaddr *) &client6, &addrlen));
 
-    *acceptedSocket = accept(*serverSocket, (struct sockaddr *) &server6, &addrlen);
+    *acceptedSocket = accept4(*serverSocket, (struct sockaddr *) &server6, &addrlen, SOCK_CLOEXEC);
     ASSERT_NE(-1, *acceptedSocket);
 
     ASSERT_EQ(0, memcmp(&client6, &server6, sizeof(client6)));
@@ -579,7 +701,7 @@
     EXPECT_EQ(ECONNRESET, err);
 }
 
-TEST_F(BinderTest, TestSocketDestroy) {
+TEST_F(BinderTest, SocketDestroy) {
     int clientSocket, serverSocket, acceptedSocket;
     ASSERT_NO_FATAL_FAILURE(fakeRemoteSocketPair(&clientSocket, &serverSocket, &acceptedSocket));
 
@@ -590,11 +712,11 @@
     EXPECT_EQ(0, fchown(clientSocket, uid, -1));
 
     // UID ranges that don't contain uid.
-    std::vector<UidRange> uidRanges = {
-        {baseUid + 42, baseUid + 449},
-        {baseUid + 1536, AID_APP - 4},
-        {baseUid + 498, uid - 1},
-        {uid + 1, baseUid + 1520},
+    std::vector<UidRangeParcel> uidRanges = {
+            makeUidRangeParcel(baseUid + 42, baseUid + 449),
+            makeUidRangeParcel(baseUid + 1536, AID_APP - 4),
+            makeUidRangeParcel(baseUid + 498, uid - 1),
+            makeUidRangeParcel(uid + 1, baseUid + 1520),
     };
     // A skip list that doesn't contain UID.
     std::vector<int32_t> skipUids { baseUid + 123, baseUid + 1600 };
@@ -605,9 +727,9 @@
 
     // UID ranges that do contain uid.
     uidRanges = {
-        {baseUid + 42, baseUid + 449},
-        {baseUid + 1536, AID_APP - 4},
-        {baseUid + 498, baseUid + 1520},
+            makeUidRangeParcel(baseUid + 42, baseUid + 449),
+            makeUidRangeParcel(baseUid + 1536, AID_APP - 4),
+            makeUidRangeParcel(baseUid + 498, baseUid + 1520),
     };
     // Add uid to the skip list.
     skipUids.push_back(uid);
@@ -662,7 +784,6 @@
 static bool interfaceHasAddress(
         const std::string &ifname, const char *addrString, int prefixLength) {
     struct addrinfo *addrinfoList = nullptr;
-    ScopedAddrinfo addrinfoCleanup(addrinfoList);
 
     const struct addrinfo hints = {
         .ai_flags    = AI_NUMERICHOST,
@@ -673,6 +794,7 @@
         addrinfoList == nullptr || addrinfoList->ai_addr == nullptr) {
         return false;
     }
+    ScopedAddrinfo addrinfoCleanup(addrinfoList);
 
     struct ifaddrs *ifaddrsList = nullptr;
     ScopedIfaddrs ifaddrsCleanup(ifaddrsList);
@@ -729,7 +851,7 @@
 
 }  // namespace
 
-TEST_F(BinderTest, TestInterfaceAddRemoveAddress) {
+TEST_F(BinderTest, InterfaceAddRemoveAddress) {
     static const struct TestData {
         const char *addrString;
         const int   prefixLength;
@@ -747,7 +869,7 @@
         { "foo:bar::bad", 64, false },
     };
 
-    for (unsigned int i = 0; i < arraysize(kTestData); i++) {
+    for (size_t i = 0; i < std::size(kTestData); i++) {
         const auto &td = kTestData[i];
 
         // [1.a] Add the address.
@@ -781,106 +903,104 @@
     }
 }
 
-TEST_F(BinderTest, TestSetProcSysNet) {
-    static const struct TestData {
-        const int family;
+TEST_F(BinderTest, GetProcSysNet) {
+    const char LOOPBACK[] = "lo";
+    static const struct {
+        const int ipversion;
         const int which;
-        const char *ifname;
-        const char *parameter;
-        const char *value;
+        const char* ifname;
+        const char* parameter;
+        const char* expectedValue;
         const int expectedReturnCode;
     } kTestData[] = {
-        { INetd::IPV4, INetd::CONF, sTun.name().c_str(), "arp_ignore", "1", 0 },
-        { -1, INetd::CONF, sTun.name().c_str(), "arp_ignore", "1", EAFNOSUPPORT },
-        { INetd::IPV4, -1, sTun.name().c_str(), "arp_ignore", "1", EINVAL },
-        { INetd::IPV4, INetd::CONF, "..", "conf/lo/arp_ignore", "1", EINVAL },
-        { INetd::IPV4, INetd::CONF, ".", "lo/arp_ignore", "1", EINVAL },
-        { INetd::IPV4, INetd::CONF, sTun.name().c_str(), "../all/arp_ignore", "1", EINVAL },
-        { INetd::IPV6, INetd::NEIGH, sTun.name().c_str(), "ucast_solicit", "7", 0 },
+            {INetd::IPV4, INetd::CONF, LOOPBACK, "arp_ignore", "0", 0},
+            {-1, INetd::CONF, sTun.name().c_str(), "arp_ignore", nullptr, EAFNOSUPPORT},
+            {INetd::IPV4, -1, sTun.name().c_str(), "arp_ignore", nullptr, EINVAL},
+            {INetd::IPV4, INetd::CONF, "..", "conf/lo/arp_ignore", nullptr, EINVAL},
+            {INetd::IPV4, INetd::CONF, ".", "lo/arp_ignore", nullptr, EINVAL},
+            {INetd::IPV4, INetd::CONF, sTun.name().c_str(), "../all/arp_ignore", nullptr, EINVAL},
+            {INetd::IPV6, INetd::NEIGH, LOOPBACK, "ucast_solicit", "3", 0},
     };
 
-    for (unsigned int i = 0; i < arraysize(kTestData); i++) {
-        const auto &td = kTestData[i];
+    for (size_t i = 0; i < std::size(kTestData); i++) {
+        const auto& td = kTestData[i];
 
-        const binder::Status status = mNetd->setProcSysNet(
-                    td.family, td.which, td.ifname, td.parameter,
-                    td.value);
+        std::string value;
+        const binder::Status status =
+                mNetd->getProcSysNet(td.ipversion, td.which, td.ifname, td.parameter, &value);
 
         if (td.expectedReturnCode == 0) {
-            SCOPED_TRACE(String8::format("test case %d should have passed", i));
+            SCOPED_TRACE(String8::format("test case %zu should have passed", i));
+            EXPECT_EQ(0, status.exceptionCode());
+            EXPECT_EQ(0, status.serviceSpecificErrorCode());
+            EXPECT_EQ(td.expectedValue, value);
+        } else {
+            SCOPED_TRACE(String8::format("test case %zu should have failed", i));
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_EQ(td.expectedReturnCode, status.serviceSpecificErrorCode());
+        }
+    }
+}
+
+TEST_F(BinderTest, SetProcSysNet) {
+    static const struct {
+        const int ipversion;
+        const int which;
+        const char* ifname;
+        const char* parameter;
+        const char* value;
+        const int expectedReturnCode;
+    } kTestData[] = {
+            {INetd::IPV4, INetd::CONF, sTun.name().c_str(), "arp_ignore", "1", 0},
+            {-1, INetd::CONF, sTun.name().c_str(), "arp_ignore", "1", EAFNOSUPPORT},
+            {INetd::IPV4, -1, sTun.name().c_str(), "arp_ignore", "1", EINVAL},
+            {INetd::IPV4, INetd::CONF, "..", "conf/lo/arp_ignore", "1", EINVAL},
+            {INetd::IPV4, INetd::CONF, ".", "lo/arp_ignore", "1", EINVAL},
+            {INetd::IPV4, INetd::CONF, sTun.name().c_str(), "../all/arp_ignore", "1", EINVAL},
+            {INetd::IPV6, INetd::NEIGH, sTun.name().c_str(), "ucast_solicit", "7", 0},
+    };
+
+    for (size_t i = 0; i < std::size(kTestData); i++) {
+        const auto& td = kTestData[i];
+        const binder::Status status =
+                mNetd->setProcSysNet(td.ipversion, td.which, td.ifname, td.parameter, td.value);
+
+        if (td.expectedReturnCode == 0) {
+            SCOPED_TRACE(String8::format("test case %zu should have passed", i));
             EXPECT_EQ(0, status.exceptionCode());
             EXPECT_EQ(0, status.serviceSpecificErrorCode());
         } else {
-            SCOPED_TRACE(String8::format("test case %d should have failed", i));
+            SCOPED_TRACE(String8::format("test case %zu should have failed", i));
             EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
             EXPECT_EQ(td.expectedReturnCode, status.serviceSpecificErrorCode());
         }
     }
 }
 
-static std::string base64Encode(const std::vector<uint8_t>& input) {
-    size_t out_len;
-    EXPECT_EQ(1, EVP_EncodedLength(&out_len, input.size()));
-    // out_len includes the trailing NULL.
-    uint8_t output_bytes[out_len];
-    EXPECT_EQ(out_len - 1, EVP_EncodeBlock(output_bytes, input.data(), input.size()));
-    return std::string(reinterpret_cast<char*>(output_bytes));
+TEST_F(BinderTest, GetSetProcSysNet) {
+    const int ipversion = INetd::IPV6;
+    const int category = INetd::NEIGH;
+    const std::string& tun = sTun.name();
+    const std::string parameter("ucast_solicit");
+
+    std::string value{};
+    EXPECT_TRUE(mNetd->getProcSysNet(ipversion, category, tun, parameter, &value).isOk());
+    ASSERT_FALSE(value.empty());
+    const int ival = std::stoi(value);
+    EXPECT_GT(ival, 0);
+    // Try doubling the parameter value (always best!).
+    EXPECT_TRUE(mNetd->setProcSysNet(ipversion, category, tun, parameter, std::to_string(2 * ival))
+            .isOk());
+    EXPECT_TRUE(mNetd->getProcSysNet(ipversion, category, tun, parameter, &value).isOk());
+    EXPECT_EQ(2 * ival, std::stoi(value));
+    // Try resetting the parameter.
+    EXPECT_TRUE(mNetd->setProcSysNet(ipversion, category, tun, parameter, std::to_string(ival))
+            .isOk());
+    EXPECT_TRUE(mNetd->getProcSysNet(ipversion, category, tun, parameter, &value).isOk());
+    EXPECT_EQ(ival, std::stoi(value));
 }
 
-TEST_F(BinderTest, TestSetResolverConfiguration_Tls) {
-    const std::vector<std::string> LOCALLY_ASSIGNED_DNS{"8.8.8.8", "2001:4860:4860::8888"};
-    std::vector<uint8_t> fp(SHA256_SIZE);
-    std::vector<uint8_t> short_fp(1);
-    std::vector<uint8_t> long_fp(SHA256_SIZE + 1);
-    std::vector<std::string> test_domains;
-    std::vector<int> test_params = { 300, 25, 8, 8 };
-    unsigned test_netid = 0;
-    static const struct TestData {
-        const std::vector<std::string> servers;
-        const std::string tlsName;
-        const std::vector<std::vector<uint8_t>> tlsFingerprints;
-        const int expectedReturnCode;
-    } kTlsTestData[] = {
-        { {"192.0.2.1"}, "", {}, 0 },
-        { {"2001:db8::2"}, "host.name", {}, 0 },
-        { {"192.0.2.3"}, "@@@@", { fp }, 0 },
-        { {"2001:db8::4"}, "", { fp }, 0 },
-        { {}, "", {}, 0 },
-        { {""}, "", {}, EINVAL },
-        { {"192.0.*.5"}, "", {}, EINVAL },
-        { {"2001:dg8::6"}, "", {}, EINVAL },
-        { {"2001:db8::c"}, "", { short_fp }, EINVAL },
-        { {"192.0.2.12"}, "", { long_fp }, EINVAL },
-        { {"2001:db8::e"}, "", { fp, fp, fp }, 0 },
-        { {"192.0.2.14"}, "", { fp, short_fp }, EINVAL },
-    };
-
-    for (unsigned int i = 0; i < arraysize(kTlsTestData); i++) {
-        const auto &td = kTlsTestData[i];
-
-        std::vector<std::string> fingerprints;
-        for (const auto& fingerprint : td.tlsFingerprints) {
-            fingerprints.push_back(base64Encode(fingerprint));
-        }
-        binder::Status status = mNetd->setResolverConfiguration(
-                test_netid, LOCALLY_ASSIGNED_DNS, test_domains, test_params,
-                td.tlsName, td.servers, fingerprints);
-
-        if (td.expectedReturnCode == 0) {
-            SCOPED_TRACE(String8::format("test case %d should have passed", i));
-            SCOPED_TRACE(status.toString8());
-            EXPECT_EQ(0, status.exceptionCode());
-        } else {
-            SCOPED_TRACE(String8::format("test case %d should have failed", i));
-            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
-            EXPECT_EQ(td.expectedReturnCode, status.serviceSpecificErrorCode());
-        }
-    }
-    // Ensure TLS is disabled before the start of the next test.
-    mNetd->setResolverConfiguration(
-        test_netid, kTlsTestData[0].servers, test_domains, test_params,
-        "", {}, {});
-}
+namespace {
 
 void expectNoTestCounterRules() {
     for (const auto& binary : { IPTABLES_PATH, IP6TABLES_PATH }) {
@@ -890,19 +1010,32 @@
     }
 }
 
-void addTetherCounterValues(const char *path, std::string if1, std::string if2, int byte, int pkt) {
+void addTetherCounterValues(const char* path, const std::string& if1, const std::string& if2,
+                            int byte, int pkt) {
     runCommand(StringPrintf("%s -w -A tetherctrl_counters -i %s -o %s -j RETURN -c %d %d",
                             path, if1.c_str(), if2.c_str(), pkt, byte));
 }
 
-void delTetherCounterValues(const char *path, std::string if1, std::string if2) {
+void delTetherCounterValues(const char* path, const std::string& if1, const std::string& if2) {
     runCommand(StringPrintf("%s -w -D tetherctrl_counters -i %s -o %s -j RETURN",
                             path, if1.c_str(), if2.c_str()));
     runCommand(StringPrintf("%s -w -D tetherctrl_counters -i %s -o %s -j RETURN",
                             path, if2.c_str(), if1.c_str()));
 }
 
-TEST_F(BinderTest, TestTetherGetStats) {
+std::vector<int64_t> getStatsVectorByIf(const std::vector<TetherStatsParcel>& statsVec,
+                                        const std::string& iface) {
+    for (auto& stats : statsVec) {
+        if (stats.iface == iface) {
+            return {stats.rxBytes, stats.rxPackets, stats.txBytes, stats.txPackets};
+        }
+    }
+    return {};
+}
+
+}  // namespace
+
+TEST_F(BinderTest, TetherGetStats) {
     expectNoTestCounterRules();
 
     // TODO: fold this into more comprehensive tests once we have binder RPCs for enabling and
@@ -935,17 +1068,13 @@
     addTetherCounterValues(IP6TABLES_PATH, extIface2, intIface3, 2000,  35);
     std::vector<int64_t> expected2 = { 8000, 519, 5000, 388 };
 
-    PersistableBundle stats;
-    binder::Status status = mNetd->tetherGetStats(&stats);
+    std::vector<TetherStatsParcel> statsVec;
+    binder::Status status = mNetd->tetherGetStats(&statsVec);
     EXPECT_TRUE(status.isOk()) << "Getting tethering stats failed: " << status;
 
-    std::vector<int64_t> actual1;
-    EXPECT_TRUE(stats.getLongVector(String16(extIface1.c_str()), &actual1));
-    EXPECT_EQ(expected1, actual1);
+    EXPECT_EQ(expected1, getStatsVectorByIf(statsVec, extIface1));
 
-    std::vector<int64_t> actual2;
-    EXPECT_TRUE(stats.getLongVector(String16(extIface2.c_str()), &actual2));
-    EXPECT_EQ(expected2, actual2);
+    EXPECT_EQ(expected2, getStatsVectorByIf(statsVec, extIface2));
 
     for (const auto& path : { IPTABLES_PATH, IP6TABLES_PATH }) {
         delTetherCounterValues(path, intIface1, extIface1);
@@ -957,3 +1086,2200 @@
 
     expectNoTestCounterRules();
 }
+
+namespace {
+
+constexpr char IDLETIMER_RAW_PREROUTING[] = "idletimer_raw_PREROUTING";
+constexpr char IDLETIMER_MANGLE_POSTROUTING[] = "idletimer_mangle_POSTROUTING";
+
+static std::vector<std::string> listIptablesRuleByTable(const char* binary, const char* table,
+                                                        const char* chainName) {
+    std::string command = StringPrintf("%s -t %s -w -n -v -L %s", binary, table, chainName);
+    return runCommand(command);
+}
+
+// TODO: It is a duplicate function, need to remove it
+bool iptablesIdleTimerInterfaceRuleExists(const char* binary, const char* chainName,
+                                          const std::string& expectedInterface,
+                                          const std::string& expectedRule, const char* table) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, table, chainName);
+    for (const auto& rule : rules) {
+        if (rule.find(expectedInterface) != std::string::npos) {
+            if (rule.find(expectedRule) != std::string::npos) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void expectIdletimerInterfaceRuleExists(const std::string& ifname, int timeout,
+                                        const std::string& classLabel) {
+    std::string IdletimerRule =
+            StringPrintf("timeout:%u label:%s send_nl_msg:1", timeout, classLabel.c_str());
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesIdleTimerInterfaceRuleExists(binary, IDLETIMER_RAW_PREROUTING, ifname,
+                                                         IdletimerRule, RAW_TABLE));
+        EXPECT_TRUE(iptablesIdleTimerInterfaceRuleExists(binary, IDLETIMER_MANGLE_POSTROUTING,
+                                                         ifname, IdletimerRule, MANGLE_TABLE));
+    }
+}
+
+void expectIdletimerInterfaceRuleNotExists(const std::string& ifname, int timeout,
+                                           const std::string& classLabel) {
+    std::string IdletimerRule =
+            StringPrintf("timeout:%u label:%s send_nl_msg:1", timeout, classLabel.c_str());
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesIdleTimerInterfaceRuleExists(binary, IDLETIMER_RAW_PREROUTING, ifname,
+                                                          IdletimerRule, RAW_TABLE));
+        EXPECT_FALSE(iptablesIdleTimerInterfaceRuleExists(binary, IDLETIMER_MANGLE_POSTROUTING,
+                                                          ifname, IdletimerRule, MANGLE_TABLE));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, IdletimerAddRemoveInterface) {
+    // TODO: We will get error in if expectIdletimerInterfaceRuleNotExists if there are the same
+    // rule in the table. Because we only check the result after calling remove function. We might
+    // check the actual rule which is removed by our function (maybe compare the results between
+    // calling function before and after)
+    binder::Status status;
+    const struct TestData {
+        const std::string ifname;
+        int32_t timeout;
+        const std::string classLabel;
+    } idleTestData[] = {
+            {"wlan0", 1234, "happyday"},
+            {"rmnet_data0", 4567, "friday"},
+    };
+    for (const auto& td : idleTestData) {
+        status = mNetd->idletimerAddInterface(td.ifname, td.timeout, td.classLabel);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        expectIdletimerInterfaceRuleExists(td.ifname, td.timeout, td.classLabel);
+
+        status = mNetd->idletimerRemoveInterface(td.ifname, td.timeout, td.classLabel);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        expectIdletimerInterfaceRuleNotExists(td.ifname, td.timeout, td.classLabel);
+    }
+}
+
+namespace {
+
+constexpr char STRICT_OUTPUT[] = "st_OUTPUT";
+constexpr char STRICT_CLEAR_CAUGHT[] = "st_clear_caught";
+
+void expectStrictSetUidAccept(const int uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    std::string perUidChain = StringPrintf("st_clear_caught_%u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesRuleExists(binary, STRICT_OUTPUT, uidRule));
+        EXPECT_FALSE(iptablesRuleExists(binary, STRICT_CLEAR_CAUGHT, uidRule));
+        EXPECT_EQ(0, iptablesRuleLineLength(binary, perUidChain.c_str()));
+    }
+}
+
+void expectStrictSetUidLog(const int uid) {
+    static const char logRule[] = "st_penalty_log  all";
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    std::string perUidChain = StringPrintf("st_clear_caught_%u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesRuleExists(binary, STRICT_OUTPUT, uidRule));
+        EXPECT_TRUE(iptablesRuleExists(binary, STRICT_CLEAR_CAUGHT, uidRule));
+        EXPECT_TRUE(iptablesRuleExists(binary, perUidChain.c_str(), logRule));
+    }
+}
+
+void expectStrictSetUidReject(const int uid) {
+    static const char rejectRule[] = "st_penalty_reject  all";
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    std::string perUidChain = StringPrintf("st_clear_caught_%u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesRuleExists(binary, STRICT_OUTPUT, uidRule));
+        EXPECT_TRUE(iptablesRuleExists(binary, STRICT_CLEAR_CAUGHT, uidRule));
+        EXPECT_TRUE(iptablesRuleExists(binary, perUidChain.c_str(), rejectRule));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, StrictSetUidCleartextPenalty) {
+    binder::Status status;
+    int32_t uid = randomUid();
+
+    // setUidCleartextPenalty Policy:Log with randomUid
+    status = mNetd->strictUidCleartextPenalty(uid, INetd::PENALTY_POLICY_LOG);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectStrictSetUidLog(uid);
+
+    // setUidCleartextPenalty Policy:Accept with randomUid
+    status = mNetd->strictUidCleartextPenalty(uid, INetd::PENALTY_POLICY_ACCEPT);
+    expectStrictSetUidAccept(uid);
+
+    // setUidCleartextPenalty Policy:Reject with randomUid
+    status = mNetd->strictUidCleartextPenalty(uid, INetd::PENALTY_POLICY_REJECT);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectStrictSetUidReject(uid);
+
+    // setUidCleartextPenalty Policy:Accept with randomUid
+    status = mNetd->strictUidCleartextPenalty(uid, INetd::PENALTY_POLICY_ACCEPT);
+    expectStrictSetUidAccept(uid);
+
+    // test wrong policy
+    int32_t wrongPolicy = -123;
+    status = mNetd->strictUidCleartextPenalty(uid, wrongPolicy);
+    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
+}
+
+namespace {
+
+std::vector<std::string> tryToFindProcesses(const std::string& processName, uint32_t maxTries = 1,
+                                            uint32_t intervalMs = 50) {
+    // Output looks like:(clatd)
+    // clat          4963   850 1 12:16:51 ?     00:00:00 clatd-netd10a88 -i netd10a88 ...
+    // ...
+    // root          5221  5219 0 12:18:12 ?     00:00:00 sh -c ps -Af | grep ' clatd-netdcc1a0'
+
+    // (dnsmasq)
+    // dns_tether    4620   792 0 16:51:28 ?     00:00:00 dnsmasq --keep-in-foreground ...
+
+    if (maxTries == 0) return {};
+
+    std::string cmd = StringPrintf("ps -Af | grep '[0-9] %s'", processName.c_str());
+    std::vector<std::string> result;
+    for (uint32_t run = 1;;) {
+        result = runCommand(cmd);
+        if (result.size() || ++run > maxTries) {
+            break;
+        }
+
+        usleep(intervalMs * 1000);
+    }
+    return result;
+}
+
+void expectProcessExists(const std::string& processName) {
+    EXPECT_EQ(1U, tryToFindProcesses(processName, 5 /*maxTries*/).size());
+}
+
+void expectProcessDoesNotExist(const std::string& processName) {
+    EXPECT_FALSE(tryToFindProcesses(processName).size());
+}
+
+}  // namespace
+
+TEST_F(BinderTest, ClatdStartStop) {
+    binder::Status status;
+
+    const std::string clatdName = StringPrintf("clatd-%s", sTun.name().c_str());
+    std::string clatAddress;
+    std::string nat64Prefix = "2001:db8:cafe:f00d:1:2::/96";
+
+    // Can't start clatd on an interface that's not part of any network...
+    status = mNetd->clatdStart(sTun.name(), nat64Prefix, &clatAddress);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(ENODEV, status.serviceSpecificErrorCode());
+
+    // ... so create a test physical network and add our tun to it.
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    // Prefix must be 96 bits long.
+    status = mNetd->clatdStart(sTun.name(), "2001:db8:cafe:f00d::/64", &clatAddress);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
+
+    // Can't start clatd unless there's a default route...
+    status = mNetd->clatdStart(sTun.name(), nat64Prefix, &clatAddress);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(EADDRNOTAVAIL, status.serviceSpecificErrorCode());
+
+    // so add a default route.
+    EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID1, sTun.name(), "::/0", "").isOk());
+
+    // Can't start clatd unless there's a global address...
+    status = mNetd->clatdStart(sTun.name(), nat64Prefix, &clatAddress);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(EADDRNOTAVAIL, status.serviceSpecificErrorCode());
+
+    // ... so add a global address.
+    const std::string v6 = "2001:db8:1:2:f076:ae99:124e:aa99";
+    EXPECT_EQ(0, sTun.addAddress(v6.c_str(), 64));
+
+    // Now expect clatd to start successfully.
+    status = mNetd->clatdStart(sTun.name(), nat64Prefix, &clatAddress);
+    EXPECT_TRUE(status.isOk());
+    EXPECT_EQ(0, status.serviceSpecificErrorCode());
+
+    // Starting it again returns EBUSY.
+    status = mNetd->clatdStart(sTun.name(), nat64Prefix, &clatAddress);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(EBUSY, status.serviceSpecificErrorCode());
+
+    expectProcessExists(clatdName);
+
+    // Expect clatd to stop successfully.
+    status = mNetd->clatdStop(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectProcessDoesNotExist(clatdName);
+
+    // Stopping a clatd that doesn't exist returns ENODEV.
+    status = mNetd->clatdStop(sTun.name());
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(ENODEV, status.serviceSpecificErrorCode());
+    expectProcessDoesNotExist(clatdName);
+
+    // Clean up.
+    EXPECT_TRUE(mNetd->networkRemoveRoute(TEST_NETID1, sTun.name(), "::/0", "").isOk());
+    EXPECT_EQ(0, ifc_del_address(sTun.name().c_str(), v6.c_str(), 64));
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+namespace {
+
+bool getIpfwdV4Enable() {
+    static const char ipv4IpfwdCmd[] = "cat /proc/sys/net/ipv4/ip_forward";
+    std::vector<std::string> result = runCommand(ipv4IpfwdCmd);
+    EXPECT_TRUE(!result.empty());
+    int v4Enable = std::stoi(result[0]);
+    return v4Enable;
+}
+
+bool getIpfwdV6Enable() {
+    static const char ipv6IpfwdCmd[] = "cat /proc/sys/net/ipv6/conf/all/forwarding";
+    std::vector<std::string> result = runCommand(ipv6IpfwdCmd);
+    EXPECT_TRUE(!result.empty());
+    int v6Enable = std::stoi(result[0]);
+    return v6Enable;
+}
+
+void expectIpfwdEnable(bool enable) {
+    int enableIPv4 = getIpfwdV4Enable();
+    int enableIPv6 = getIpfwdV6Enable();
+    EXPECT_EQ(enable, enableIPv4);
+    EXPECT_EQ(enable, enableIPv6);
+}
+
+bool ipRuleIpfwdExists(const char* ipVersion, const std::string& ipfwdRule) {
+    std::vector<std::string> rules = listIpRules(ipVersion);
+    for (const auto& rule : rules) {
+        if (rule.find(ipfwdRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectIpfwdRuleExists(const char* fromIf, const char* toIf) {
+    std::string ipfwdRule = StringPrintf("18000:\tfrom all iif %s lookup %s ", fromIf, toIf);
+
+    for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
+        EXPECT_TRUE(ipRuleIpfwdExists(ipVersion, ipfwdRule));
+    }
+}
+
+void expectIpfwdRuleNotExists(const char* fromIf, const char* toIf) {
+    std::string ipfwdRule = StringPrintf("18000:\tfrom all iif %s lookup %s ", fromIf, toIf);
+
+    for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
+        EXPECT_FALSE(ipRuleIpfwdExists(ipVersion, ipfwdRule));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, TestIpfwdEnableDisableStatusForwarding) {
+    // Get ipfwd requester list from Netd
+    std::vector<std::string> requesterList;
+    binder::Status status = mNetd->ipfwdGetRequesterList(&requesterList);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    bool ipfwdEnabled;
+    if (requesterList.size() == 0) {
+        // No requester in Netd, ipfwd should be disabled
+        // So add one test requester and verify
+        status = mNetd->ipfwdEnableForwarding("TestRequester");
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+        expectIpfwdEnable(true);
+        status = mNetd->ipfwdEnabled(&ipfwdEnabled);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        EXPECT_TRUE(ipfwdEnabled);
+
+        // Remove test one, verify again
+        status = mNetd->ipfwdDisableForwarding("TestRequester");
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+        expectIpfwdEnable(false);
+        status = mNetd->ipfwdEnabled(&ipfwdEnabled);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        EXPECT_FALSE(ipfwdEnabled);
+    } else {
+        // Disable all requesters
+        for (const auto& requester : requesterList) {
+            status = mNetd->ipfwdDisableForwarding(requester);
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        }
+
+        // After disable all requester, ipfwd should be disabled
+        expectIpfwdEnable(false);
+        status = mNetd->ipfwdEnabled(&ipfwdEnabled);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        EXPECT_FALSE(ipfwdEnabled);
+
+        // Enable them back
+        for (const auto& requester : requesterList) {
+            status = mNetd->ipfwdEnableForwarding(requester);
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        }
+
+        // ipfwd should be enabled
+        expectIpfwdEnable(true);
+        status = mNetd->ipfwdEnabled(&ipfwdEnabled);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        EXPECT_TRUE(ipfwdEnabled);
+    }
+}
+
+TEST_F(BinderTest, TestIpfwdAddRemoveInterfaceForward) {
+  // Add test physical network
+  EXPECT_TRUE(
+      mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+  EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+  EXPECT_TRUE(
+      mNetd->networkCreatePhysical(TEST_NETID2, INetd::PERMISSION_NONE).isOk());
+  EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID2, sTun2.name()).isOk());
+
+  binder::Status status =
+      mNetd->ipfwdAddInterfaceForward(sTun.name(), sTun2.name());
+  EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+  expectIpfwdRuleExists(sTun.name().c_str(), sTun2.name().c_str());
+
+  status = mNetd->ipfwdRemoveInterfaceForward(sTun.name(), sTun2.name());
+  EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+  expectIpfwdRuleNotExists(sTun.name().c_str(), sTun2.name().c_str());
+}
+
+namespace {
+
+constexpr char BANDWIDTH_INPUT[] = "bw_INPUT";
+constexpr char BANDWIDTH_OUTPUT[] = "bw_OUTPUT";
+constexpr char BANDWIDTH_FORWARD[] = "bw_FORWARD";
+constexpr char BANDWIDTH_NAUGHTY[] = "bw_penalty_box";
+constexpr char BANDWIDTH_NICE[] = "bw_happy_box";
+constexpr char BANDWIDTH_ALERT[] = "bw_global_alert";
+
+// TODO: Move iptablesTargetsExists and listIptablesRuleByTable to the top.
+//       Use either a std::vector<std::string> of things to match, or a variadic function.
+bool iptablesTargetsExists(const char* binary, int expectedCount, const char* table,
+                           const char* chainName, const std::string& expectedTargetA,
+                           const std::string& expectedTargetB) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, table, chainName);
+    int matchCount = 0;
+
+    for (const auto& rule : rules) {
+        if (rule.find(expectedTargetA) != std::string::npos) {
+            if (rule.find(expectedTargetB) != std::string::npos) {
+                matchCount++;
+            }
+        }
+    }
+    return matchCount == expectedCount;
+}
+
+void expectXtQuotaValueEqual(const char* ifname, long quotaBytes) {
+    std::string path = StringPrintf("/proc/net/xt_quota/%s", ifname);
+    std::string result = "";
+
+    EXPECT_TRUE(ReadFileToString(path, &result));
+    // Quota value might be decreased while matching packets
+    EXPECT_GE(quotaBytes, std::stol(Trim(result)));
+}
+
+void expectBandwidthInterfaceQuotaRuleExists(const char* ifname, long quotaBytes) {
+    std::string BANDWIDTH_COSTLY_IF = StringPrintf("bw_costly_%s", ifname);
+    std::string quotaRule = StringPrintf("quota %s", ifname);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesTargetsExists(binary, 1, FILTER_TABLE, BANDWIDTH_INPUT, ifname,
+                                          BANDWIDTH_COSTLY_IF));
+        EXPECT_TRUE(iptablesTargetsExists(binary, 1, FILTER_TABLE, BANDWIDTH_OUTPUT, ifname,
+                                          BANDWIDTH_COSTLY_IF));
+        EXPECT_TRUE(iptablesTargetsExists(binary, 2, FILTER_TABLE, BANDWIDTH_FORWARD, ifname,
+                                          BANDWIDTH_COSTLY_IF));
+        EXPECT_TRUE(iptablesRuleExists(binary, BANDWIDTH_COSTLY_IF.c_str(), BANDWIDTH_NAUGHTY));
+        EXPECT_TRUE(iptablesRuleExists(binary, BANDWIDTH_COSTLY_IF.c_str(), quotaRule));
+    }
+    expectXtQuotaValueEqual(ifname, quotaBytes);
+}
+
+void expectBandwidthInterfaceQuotaRuleDoesNotExist(const char* ifname) {
+    std::string BANDWIDTH_COSTLY_IF = StringPrintf("bw_costly_%s", ifname);
+    std::string quotaRule = StringPrintf("quota %s", ifname);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesTargetsExists(binary, 1, FILTER_TABLE, BANDWIDTH_INPUT, ifname,
+                                           BANDWIDTH_COSTLY_IF));
+        EXPECT_FALSE(iptablesTargetsExists(binary, 1, FILTER_TABLE, BANDWIDTH_OUTPUT, ifname,
+                                           BANDWIDTH_COSTLY_IF));
+        EXPECT_FALSE(iptablesTargetsExists(binary, 2, FILTER_TABLE, BANDWIDTH_FORWARD, ifname,
+                                           BANDWIDTH_COSTLY_IF));
+        EXPECT_FALSE(iptablesRuleExists(binary, BANDWIDTH_COSTLY_IF.c_str(), BANDWIDTH_NAUGHTY));
+        EXPECT_FALSE(iptablesRuleExists(binary, BANDWIDTH_COSTLY_IF.c_str(), quotaRule));
+    }
+}
+
+void expectBandwidthInterfaceAlertRuleExists(const char* ifname, long alertBytes) {
+    std::string BANDWIDTH_COSTLY_IF = StringPrintf("bw_costly_%s", ifname);
+    std::string alertRule = StringPrintf("quota %sAlert", ifname);
+    std::string alertName = StringPrintf("%sAlert", ifname);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesRuleExists(binary, BANDWIDTH_COSTLY_IF.c_str(), alertRule));
+    }
+    expectXtQuotaValueEqual(alertName.c_str(), alertBytes);
+}
+
+void expectBandwidthInterfaceAlertRuleDoesNotExist(const char* ifname) {
+    std::string BANDWIDTH_COSTLY_IF = StringPrintf("bw_costly_%s", ifname);
+    std::string alertRule = StringPrintf("quota %sAlert", ifname);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesRuleExists(binary, BANDWIDTH_COSTLY_IF.c_str(), alertRule));
+    }
+}
+
+void expectBandwidthGlobalAlertRuleExists(long alertBytes) {
+    static const char globalAlertRule[] = "quota globalAlert";
+    static const char globalAlertName[] = "globalAlert";
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesRuleExists(binary, BANDWIDTH_ALERT, globalAlertRule));
+    }
+    expectXtQuotaValueEqual(globalAlertName, alertBytes);
+}
+
+void expectBandwidthManipulateSpecialAppRuleExists(const char* chain, const char* target, int uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesTargetsExists(binary, 1, FILTER_TABLE, chain, target, uidRule));
+    }
+}
+
+void expectBandwidthManipulateSpecialAppRuleDoesNotExist(const char* chain, int uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesRuleExists(binary, chain, uidRule));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, BandwidthSetRemoveInterfaceQuota) {
+    long testQuotaBytes = 5550;
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    binder::Status status = mNetd->bandwidthSetInterfaceQuota(sTun.name(), testQuotaBytes);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthInterfaceQuotaRuleExists(sTun.name().c_str(), testQuotaBytes);
+
+    status = mNetd->bandwidthRemoveInterfaceQuota(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthInterfaceQuotaRuleDoesNotExist(sTun.name().c_str());
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, BandwidthSetRemoveInterfaceAlert) {
+    long testAlertBytes = 373;
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+    // Need to have a prior interface quota set to set an alert
+    binder::Status status = mNetd->bandwidthSetInterfaceQuota(sTun.name(), testAlertBytes);
+    status = mNetd->bandwidthSetInterfaceAlert(sTun.name(), testAlertBytes);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthInterfaceAlertRuleExists(sTun.name().c_str(), testAlertBytes);
+
+    status = mNetd->bandwidthRemoveInterfaceAlert(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthInterfaceAlertRuleDoesNotExist(sTun.name().c_str());
+
+    // Remove interface quota
+    status = mNetd->bandwidthRemoveInterfaceQuota(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthInterfaceQuotaRuleDoesNotExist(sTun.name().c_str());
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, BandwidthSetGlobalAlert) {
+    long testAlertBytes = 2097149;
+
+    binder::Status status = mNetd->bandwidthSetGlobalAlert(testAlertBytes);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthGlobalAlertRuleExists(testAlertBytes);
+
+    testAlertBytes = 2097152;
+    status = mNetd->bandwidthSetGlobalAlert(testAlertBytes);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthGlobalAlertRuleExists(testAlertBytes);
+}
+
+TEST_F(BinderTest, BandwidthManipulateSpecialApp) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    int32_t uid = randomUid();
+    static const char targetReject[] = "REJECT";
+    static const char targetReturn[] = "RETURN";
+
+    // add NaughtyApp
+    binder::Status status = mNetd->bandwidthAddNaughtyApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleExists(BANDWIDTH_NAUGHTY, targetReject, uid);
+
+    // remove NaughtyApp
+    status = mNetd->bandwidthRemoveNaughtyApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleDoesNotExist(BANDWIDTH_NAUGHTY, uid);
+
+    // add NiceApp
+    status = mNetd->bandwidthAddNiceApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleExists(BANDWIDTH_NICE, targetReturn, uid);
+
+    // remove NiceApp
+    status = mNetd->bandwidthRemoveNiceApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleDoesNotExist(BANDWIDTH_NICE, uid);
+}
+
+namespace {
+
+std::vector<std::string> listIpRoutes(const char* ipVersion, const char* table) {
+    std::string command = StringPrintf("%s %s route ls table %s", IP_PATH, ipVersion, table);
+    return runCommand(command);
+}
+
+bool ipRouteExists(const char* ipVersion, const char* table, const std::string& ipRoute) {
+    std::vector<std::string> routes = listIpRoutes(ipVersion, table);
+    for (const auto& route : routes) {
+        if (route.find(ipRoute) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+std::string ipRouteString(const std::string& ifName, const std::string& dst,
+                          const std::string& nextHop) {
+    std::string dstString = (dst == "0.0.0.0/0" || dst == "::/0") ? "default" : dst;
+
+    if (!nextHop.empty()) {
+        dstString += " via " + nextHop;
+    }
+
+    return dstString + " dev " + ifName;
+}
+
+void expectNetworkRouteExists(const char* ipVersion, const std::string& ifName,
+                              const std::string& dst, const std::string& nextHop,
+                              const char* table) {
+    EXPECT_TRUE(ipRouteExists(ipVersion, table, ipRouteString(ifName, dst, nextHop)));
+}
+
+void expectNetworkRouteDoesNotExist(const char* ipVersion, const std::string& ifName,
+                                    const std::string& dst, const std::string& nextHop,
+                                    const char* table) {
+    EXPECT_FALSE(ipRouteExists(ipVersion, table, ipRouteString(ifName, dst, nextHop)));
+}
+
+bool ipRuleExists(const char* ipVersion, const std::string& ipRule) {
+    std::vector<std::string> rules = listIpRules(ipVersion);
+    for (const auto& rule : rules) {
+        if (rule.find(ipRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectNetworkDefaultIpRuleExists(const char* ifName) {
+    std::string networkDefaultRule =
+            StringPrintf("22000:\tfrom all fwmark 0x0/0xffff iif lo lookup %s", ifName);
+
+    for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
+        EXPECT_TRUE(ipRuleExists(ipVersion, networkDefaultRule));
+    }
+}
+
+void expectNetworkDefaultIpRuleDoesNotExist() {
+    static const char networkDefaultRule[] = "22000:\tfrom all fwmark 0x0/0xffff iif lo";
+
+    for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
+        EXPECT_FALSE(ipRuleExists(ipVersion, networkDefaultRule));
+    }
+}
+
+void expectNetworkPermissionIpRuleExists(const char* ifName, int permission) {
+    std::string networkPermissionRule = "";
+    switch (permission) {
+        case INetd::PERMISSION_NONE:
+            networkPermissionRule = StringPrintf(
+                    "13000:\tfrom all fwmark 0x1ffdd/0x1ffff iif lo lookup %s", ifName);
+            break;
+        case INetd::PERMISSION_NETWORK:
+            networkPermissionRule = StringPrintf(
+                    "13000:\tfrom all fwmark 0x5ffdd/0x5ffff iif lo lookup %s", ifName);
+            break;
+        case INetd::PERMISSION_SYSTEM:
+            networkPermissionRule = StringPrintf(
+                    "13000:\tfrom all fwmark 0xdffdd/0xdffff iif lo lookup %s", ifName);
+            break;
+    }
+
+    for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
+        EXPECT_TRUE(ipRuleExists(ipVersion, networkPermissionRule));
+    }
+}
+
+// TODO: It is a duplicate function, need to remove it
+bool iptablesNetworkPermissionIptablesRuleExists(const char* binary, const char* chainName,
+                                                 const std::string& expectedInterface,
+                                                 const std::string& expectedRule,
+                                                 const char* table) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, table, chainName);
+    for (const auto& rule : rules) {
+        if (rule.find(expectedInterface) != std::string::npos) {
+            if (rule.find(expectedRule) != std::string::npos) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void expectNetworkPermissionIptablesRuleExists(const char* ifName, int permission) {
+    static const char ROUTECTRL_INPUT[] = "routectrl_mangle_INPUT";
+    std::string networkIncomingPacketMarkRule = "";
+    switch (permission) {
+        case INetd::PERMISSION_NONE:
+            networkIncomingPacketMarkRule = "MARK xset 0x3ffdd/0xffefffff";
+            break;
+        case INetd::PERMISSION_NETWORK:
+            networkIncomingPacketMarkRule = "MARK xset 0x7ffdd/0xffefffff";
+            break;
+        case INetd::PERMISSION_SYSTEM:
+            networkIncomingPacketMarkRule = "MARK xset 0xfffdd/0xffefffff";
+            break;
+    }
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesNetworkPermissionIptablesRuleExists(
+                binary, ROUTECTRL_INPUT, ifName, networkIncomingPacketMarkRule, MANGLE_TABLE));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, NetworkAddRemoveRouteUserPermission) {
+    static const struct {
+        const char* ipVersion;
+        const char* testDest;
+        const char* testNextHop;
+        const bool expectSuccess;
+    } kTestData[] = {
+            {IP_RULE_V4, "0.0.0.0/0", "", true},
+            {IP_RULE_V4, "0.0.0.0/0", "10.251.10.0", true},
+            {IP_RULE_V4, "10.251.0.0/16", "", true},
+            {IP_RULE_V4, "10.251.0.0/16", "10.251.10.0", true},
+            {IP_RULE_V4, "10.251.0.0/16", "fe80::/64", false},
+            {IP_RULE_V6, "::/0", "", true},
+            {IP_RULE_V6, "::/0", "2001:db8::", true},
+            {IP_RULE_V6, "2001:db8:cafe::/64", "2001:db8::", true},
+            {IP_RULE_V4, "fe80::/64", "0.0.0.0", false},
+    };
+
+    static const struct {
+        const char* ipVersion;
+        const char* testDest;
+        const char* testNextHop;
+    } kTestDataWithNextHop[] = {
+            {IP_RULE_V4, "10.251.10.0/30", ""},
+            {IP_RULE_V6, "2001:db8::/32", ""},
+    };
+
+    static const char testTableLegacySystem[] = "legacy_system";
+    static const char testTableLegacyNetwork[] = "legacy_network";
+    const int testUid = randomUid();
+    const std::vector<int32_t> testUids = {testUid};
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    // Setup route for testing nextHop
+    for (size_t i = 0; i < std::size(kTestDataWithNextHop); i++) {
+        const auto& td = kTestDataWithNextHop[i];
+
+        // All route for test tun will disappear once the tun interface is deleted.
+        binder::Status status =
+                mNetd->networkAddRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                 sTun.name().c_str());
+
+        // Add system permission for test uid, setup route in legacy system table.
+        EXPECT_TRUE(mNetd->networkSetPermissionForUser(INetd::PERMISSION_SYSTEM, testUids).isOk());
+
+        status = mNetd->networkAddLegacyRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop,
+                                              testUid);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                 testTableLegacySystem);
+
+        // Remove system permission for test uid, setup route in legacy network table.
+        EXPECT_TRUE(mNetd->networkClearPermissionForUser(testUids).isOk());
+
+        status = mNetd->networkAddLegacyRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop,
+                                              testUid);
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+        expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                 testTableLegacyNetwork);
+    }
+
+    for (size_t i = 0; i < std::size(kTestData); i++) {
+        const auto& td = kTestData[i];
+
+        binder::Status status =
+                mNetd->networkAddRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop);
+        if (td.expectSuccess) {
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+            expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                     sTun.name().c_str());
+        } else {
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_NE(0, status.serviceSpecificErrorCode());
+        }
+
+        status = mNetd->networkRemoveRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop);
+        if (td.expectSuccess) {
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+            expectNetworkRouteDoesNotExist(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                           sTun.name().c_str());
+        } else {
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_NE(0, status.serviceSpecificErrorCode());
+        }
+
+        // Add system permission for test uid, route will be added into legacy system table.
+        EXPECT_TRUE(mNetd->networkSetPermissionForUser(INetd::PERMISSION_SYSTEM, testUids).isOk());
+
+        status = mNetd->networkAddLegacyRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop,
+                                              testUid);
+        if (td.expectSuccess) {
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+            expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                     testTableLegacySystem);
+        } else {
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_NE(0, status.serviceSpecificErrorCode());
+        }
+
+        status = mNetd->networkRemoveLegacyRoute(TEST_NETID1, sTun.name(), td.testDest,
+                                                 td.testNextHop, testUid);
+        if (td.expectSuccess) {
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+            expectNetworkRouteDoesNotExist(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                           testTableLegacySystem);
+        } else {
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_NE(0, status.serviceSpecificErrorCode());
+        }
+
+        // Remove system permission for test uid, route will be added into legacy network table.
+        EXPECT_TRUE(mNetd->networkClearPermissionForUser(testUids).isOk());
+
+        status = mNetd->networkAddLegacyRoute(TEST_NETID1, sTun.name(), td.testDest, td.testNextHop,
+                                              testUid);
+        if (td.expectSuccess) {
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+            expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                     testTableLegacyNetwork);
+        } else {
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_NE(0, status.serviceSpecificErrorCode());
+        }
+
+        status = mNetd->networkRemoveLegacyRoute(TEST_NETID1, sTun.name(), td.testDest,
+                                                 td.testNextHop, testUid);
+        if (td.expectSuccess) {
+            EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+            expectNetworkRouteDoesNotExist(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                           testTableLegacyNetwork);
+        } else {
+            EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+            EXPECT_NE(0, status.serviceSpecificErrorCode());
+        }
+    }
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, NetworkPermissionDefault) {
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    // Get current default network NetId
+    binder::Status status = mNetd->networkGetDefault(&mStoredDefaultNetwork);
+    ASSERT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    // Test SetDefault
+    status = mNetd->networkSetDefault(TEST_NETID1);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectNetworkDefaultIpRuleExists(sTun.name().c_str());
+
+    status = mNetd->networkClearDefault();
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectNetworkDefaultIpRuleDoesNotExist();
+
+    // Set default network back
+    status = mNetd->networkSetDefault(mStoredDefaultNetwork);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    // Test SetPermission
+    status = mNetd->networkSetPermissionForNetwork(TEST_NETID1, INetd::PERMISSION_SYSTEM);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectNetworkPermissionIpRuleExists(sTun.name().c_str(), INetd::PERMISSION_SYSTEM);
+    expectNetworkPermissionIptablesRuleExists(sTun.name().c_str(), INetd::PERMISSION_SYSTEM);
+
+    status = mNetd->networkSetPermissionForNetwork(TEST_NETID1, INetd::PERMISSION_NONE);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectNetworkPermissionIpRuleExists(sTun.name().c_str(), INetd::PERMISSION_NONE);
+    expectNetworkPermissionIptablesRuleExists(sTun.name().c_str(), INetd::PERMISSION_NONE);
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, NetworkSetProtectAllowDeny) {
+    const int testUid = randomUid();
+    binder::Status status = mNetd->networkSetProtectAllow(testUid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    bool ret = false;
+    status = mNetd->networkCanProtect(testUid, &ret);
+    EXPECT_TRUE(ret);
+
+    status = mNetd->networkSetProtectDeny(testUid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    status = mNetd->networkCanProtect(testUid, &ret);
+    EXPECT_FALSE(ret);
+}
+
+namespace {
+
+int readIntFromPath(const std::string& path) {
+    std::string result = "";
+    EXPECT_TRUE(ReadFileToString(path, &result));
+    return std::stoi(result);
+}
+
+int getTetherAcceptIPv6Ra(const std::string& ifName) {
+    std::string path = StringPrintf("/proc/sys/net/ipv6/conf/%s/accept_ra", ifName.c_str());
+    return readIntFromPath(path);
+}
+
+bool getTetherAcceptIPv6Dad(const std::string& ifName) {
+    std::string path = StringPrintf("/proc/sys/net/ipv6/conf/%s/accept_dad", ifName.c_str());
+    return readIntFromPath(path);
+}
+
+int getTetherIPv6DadTransmits(const std::string& ifName) {
+    std::string path = StringPrintf("/proc/sys/net/ipv6/conf/%s/dad_transmits", ifName.c_str());
+    return readIntFromPath(path);
+}
+
+bool getTetherEnableIPv6(const std::string& ifName) {
+    std::string path = StringPrintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifName.c_str());
+    int disableIPv6 = readIntFromPath(path);
+    return !disableIPv6;
+}
+
+bool interfaceListContains(const std::vector<std::string>& ifList, const std::string& ifName) {
+    for (const auto& iface : ifList) {
+        if (iface == ifName) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectTetherInterfaceConfigureForIPv6Router(const std::string& ifName) {
+    EXPECT_EQ(getTetherAcceptIPv6Ra(ifName), 0);
+    EXPECT_FALSE(getTetherAcceptIPv6Dad(ifName));
+    EXPECT_EQ(getTetherIPv6DadTransmits(ifName), 0);
+    EXPECT_TRUE(getTetherEnableIPv6(ifName));
+}
+
+void expectTetherInterfaceConfigureForIPv6Client(const std::string& ifName) {
+    EXPECT_EQ(getTetherAcceptIPv6Ra(ifName), 2);
+    EXPECT_TRUE(getTetherAcceptIPv6Dad(ifName));
+    EXPECT_EQ(getTetherIPv6DadTransmits(ifName), 1);
+    EXPECT_FALSE(getTetherEnableIPv6(ifName));
+}
+
+void expectTetherInterfaceExists(const std::vector<std::string>& ifList,
+                                 const std::string& ifName) {
+    EXPECT_TRUE(interfaceListContains(ifList, ifName));
+}
+
+void expectTetherInterfaceNotExists(const std::vector<std::string>& ifList,
+                                    const std::string& ifName) {
+    EXPECT_FALSE(interfaceListContains(ifList, ifName));
+}
+
+void expectTetherDnsListEquals(const std::vector<std::string>& dnsList,
+                               const std::vector<std::string>& testDnsAddrs) {
+    EXPECT_TRUE(dnsList == testDnsAddrs);
+}
+
+}  // namespace
+
+TEST_F(BinderTest, TetherStartStopStatus) {
+    std::vector<std::string> noDhcpRange = {};
+    static const char dnsdName[] = "dnsmasq";
+
+    binder::Status status = mNetd->tetherStart(noDhcpRange);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectProcessExists(dnsdName);
+
+    bool tetherEnabled;
+    status = mNetd->tetherIsEnabled(&tetherEnabled);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    EXPECT_TRUE(tetherEnabled);
+
+    status = mNetd->tetherStop();
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectProcessDoesNotExist(dnsdName);
+
+    status = mNetd->tetherIsEnabled(&tetherEnabled);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    EXPECT_FALSE(tetherEnabled);
+}
+
+TEST_F(BinderTest, TetherInterfaceAddRemoveList) {
+    // TODO: verify if dnsmasq update interface successfully
+
+    binder::Status status = mNetd->tetherInterfaceAdd(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectTetherInterfaceConfigureForIPv6Router(sTun.name());
+
+    std::vector<std::string> ifList;
+    status = mNetd->tetherInterfaceList(&ifList);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectTetherInterfaceExists(ifList, sTun.name());
+
+    status = mNetd->tetherInterfaceRemove(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectTetherInterfaceConfigureForIPv6Client(sTun.name());
+
+    status = mNetd->tetherInterfaceList(&ifList);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectTetherInterfaceNotExists(ifList, sTun.name());
+}
+
+TEST_F(BinderTest, TetherDnsSetList) {
+    // TODO: verify if dnsmasq update dns successfully
+    std::vector<std::string> testDnsAddrs = {"192.168.1.37", "213.137.100.3",
+                                             "fe80::1%" + sTun.name()};
+
+    binder::Status status = mNetd->tetherDnsSet(TEST_NETID1, testDnsAddrs);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    std::vector<std::string> dnsList;
+    status = mNetd->tetherDnsList(&dnsList);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectTetherDnsListEquals(dnsList, testDnsAddrs);
+}
+
+namespace {
+
+constexpr char FIREWALL_INPUT[] = "fw_INPUT";
+constexpr char FIREWALL_OUTPUT[] = "fw_OUTPUT";
+constexpr char FIREWALL_FORWARD[] = "fw_FORWARD";
+constexpr char FIREWALL_DOZABLE[] = "fw_dozable";
+constexpr char FIREWALL_POWERSAVE[] = "fw_powersave";
+constexpr char FIREWALL_STANDBY[] = "fw_standby";
+constexpr char targetReturn[] = "RETURN";
+constexpr char targetDrop[] = "DROP";
+
+void expectFirewallWhitelistMode() {
+    static const char dropRule[] = "DROP       all";
+    static const char rejectRule[] = "REJECT     all";
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesRuleExists(binary, FIREWALL_INPUT, dropRule));
+        EXPECT_TRUE(iptablesRuleExists(binary, FIREWALL_OUTPUT, rejectRule));
+        EXPECT_TRUE(iptablesRuleExists(binary, FIREWALL_FORWARD, rejectRule));
+    }
+}
+
+void expectFirewallBlacklistMode() {
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_EQ(2, iptablesRuleLineLength(binary, FIREWALL_INPUT));
+        EXPECT_EQ(2, iptablesRuleLineLength(binary, FIREWALL_OUTPUT));
+        EXPECT_EQ(2, iptablesRuleLineLength(binary, FIREWALL_FORWARD));
+    }
+}
+
+bool iptablesFirewallInterfaceFirstRuleExists(const char* binary, const char* chainName,
+                                              const std::string& expectedInterface,
+                                              const std::string& expectedRule) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, FILTER_TABLE, chainName);
+    // Expected rule:
+    // Chain fw_INPUT (1 references)
+    // pkts bytes target     prot opt in     out     source               destination
+    // 0     0 RETURN     all  --  expectedInterface *       0.0.0.0/0            0.0.0.0/0
+    // 0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0
+    int firstRuleIndex = 2;
+    if (rules.size() < 4) return false;
+    if (rules[firstRuleIndex].find(expectedInterface) != std::string::npos) {
+        if (rules[firstRuleIndex].find(expectedRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// TODO: It is a duplicate function, need to remove it
+bool iptablesFirewallInterfaceRuleExists(const char* binary, const char* chainName,
+                                         const std::string& expectedInterface,
+                                         const std::string& expectedRule) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, FILTER_TABLE, chainName);
+    for (const auto& rule : rules) {
+        if (rule.find(expectedInterface) != std::string::npos) {
+            if (rule.find(expectedRule) != std::string::npos) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void expectFirewallInterfaceRuleAllowExists(const std::string& ifname) {
+    static const char returnRule[] = "RETURN     all";
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesFirewallInterfaceFirstRuleExists(binary, FIREWALL_INPUT, ifname,
+                                                             returnRule));
+        EXPECT_TRUE(iptablesFirewallInterfaceFirstRuleExists(binary, FIREWALL_OUTPUT, ifname,
+                                                             returnRule));
+    }
+}
+
+void expectFireWallInterfaceRuleAllowDoesNotExist(const std::string& ifname) {
+    static const char returnRule[] = "RETURN     all";
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(
+                iptablesFirewallInterfaceRuleExists(binary, FIREWALL_INPUT, ifname, returnRule));
+        EXPECT_FALSE(
+                iptablesFirewallInterfaceRuleExists(binary, FIREWALL_OUTPUT, ifname, returnRule));
+    }
+}
+
+bool iptablesFirewallUidFirstRuleExists(const char* binary, const char* chainName,
+                                        const std::string& expectedTarget,
+                                        const std::string& expectedRule) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, FILTER_TABLE, chainName);
+    int firstRuleIndex = 2;
+    if (rules.size() < 4) return false;
+    if (rules[firstRuleIndex].find(expectedTarget) != std::string::npos) {
+        if (rules[firstRuleIndex].find(expectedRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool iptablesFirewallUidLastRuleExists(const char* binary, const char* chainName,
+                                       const std::string& expectedTarget,
+                                       const std::string& expectedRule) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, FILTER_TABLE, chainName);
+    int lastRuleIndex = rules.size() - 1;
+    if (lastRuleIndex < 0) return false;
+    if (rules[lastRuleIndex].find(expectedTarget) != std::string::npos) {
+        if (rules[lastRuleIndex].find(expectedRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectFirewallUidFirstRuleExists(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_TRUE(iptablesFirewallUidFirstRuleExists(binary, chainName, targetReturn, uidRule));
+}
+
+void expectFirewallUidFirstRuleDoesNotExist(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_FALSE(iptablesFirewallUidFirstRuleExists(binary, chainName, targetReturn, uidRule));
+}
+
+void expectFirewallUidLastRuleExists(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_TRUE(iptablesFirewallUidLastRuleExists(binary, chainName, targetDrop, uidRule));
+}
+
+void expectFirewallUidLastRuleDoesNotExist(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_FALSE(iptablesFirewallUidLastRuleExists(binary, chainName, targetDrop, uidRule));
+}
+
+bool iptablesFirewallChildChainsLastRuleExists(const char* binary, const char* chainName) {
+    std::vector<std::string> inputRules =
+            listIptablesRuleByTable(binary, FILTER_TABLE, FIREWALL_INPUT);
+    std::vector<std::string> outputRules =
+            listIptablesRuleByTable(binary, FILTER_TABLE, FIREWALL_OUTPUT);
+    int inputLastRuleIndex = inputRules.size() - 1;
+    int outputLastRuleIndex = outputRules.size() - 1;
+
+    if (inputLastRuleIndex < 0 || outputLastRuleIndex < 0) return false;
+    if (inputRules[inputLastRuleIndex].find(chainName) != std::string::npos) {
+        if (outputRules[outputLastRuleIndex].find(chainName) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectFirewallChildChainsLastRuleExists(const char* chainRule) {
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_TRUE(iptablesFirewallChildChainsLastRuleExists(binary, chainRule));
+}
+
+void expectFirewallChildChainsLastRuleDoesNotExist(const char* chainRule) {
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesRuleExists(binary, FIREWALL_INPUT, chainRule));
+        EXPECT_FALSE(iptablesRuleExists(binary, FIREWALL_OUTPUT, chainRule));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, FirewallSetFirewallType) {
+    binder::Status status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallWhitelistMode();
+
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallBlacklistMode();
+
+    // set firewall type blacklist twice
+    mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallBlacklistMode();
+
+    // set firewall type whitelist twice
+    mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallWhitelistMode();
+
+    // reset firewall type to default
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallBlacklistMode();
+}
+
+TEST_F(BinderTest, FirewallSetInterfaceRule) {
+    // setinterfaceRule is not supported in BLACKLIST MODE
+    binder::Status status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    status = mNetd->firewallSetInterfaceRule(sTun.name(), INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_FALSE(status.isOk()) << status.exceptionMessage();
+
+    // set WHITELIST mode first
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    status = mNetd->firewallSetInterfaceRule(sTun.name(), INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallInterfaceRuleAllowExists(sTun.name());
+
+    status = mNetd->firewallSetInterfaceRule(sTun.name(), INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFireWallInterfaceRuleAllowDoesNotExist(sTun.name());
+
+    // reset firewall mode to default
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallBlacklistMode();
+}
+
+TEST_F(BinderTest, FirewallSetUidRule) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    int32_t uid = randomUid();
+
+    // Doze allow
+    binder::Status status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_DOZABLE, uid,
+                                                      INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleExists(FIREWALL_DOZABLE, uid);
+
+    // Doze deny
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_DOZABLE, uid,
+                                       INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_DOZABLE, uid);
+
+    // Powersave allow
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_POWERSAVE, uid,
+                                       INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleExists(FIREWALL_POWERSAVE, uid);
+
+    // Powersave deny
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_POWERSAVE, uid,
+                                       INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_POWERSAVE, uid);
+
+    // Standby deny
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_STANDBY, uid,
+                                       INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleExists(FIREWALL_STANDBY, uid);
+
+    // Standby allow
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_STANDBY, uid,
+                                       INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleDoesNotExist(FIREWALL_STANDBY, uid);
+
+    // None deny in BLACKLIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleExists(FIREWALL_INPUT, uid);
+    expectFirewallUidLastRuleExists(FIREWALL_OUTPUT, uid);
+
+    // None allow in BLACKLIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleDoesNotExist(FIREWALL_INPUT, uid);
+    expectFirewallUidLastRuleDoesNotExist(FIREWALL_OUTPUT, uid);
+
+    // set firewall type whitelist twice
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallWhitelistMode();
+
+    // None allow in WHITELIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleExists(FIREWALL_INPUT, uid);
+    expectFirewallUidFirstRuleExists(FIREWALL_OUTPUT, uid);
+
+    // None deny in WHITELIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_INPUT, uid);
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_OUTPUT, uid);
+
+    // reset firewall mode to default
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallBlacklistMode();
+}
+
+TEST_F(BinderTest, FirewallEnableDisableChildChains) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    binder::Status status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_DOZABLE, true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleExists(FIREWALL_DOZABLE);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_STANDBY, true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleExists(FIREWALL_STANDBY);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_POWERSAVE, true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleExists(FIREWALL_POWERSAVE);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_DOZABLE, false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleDoesNotExist(FIREWALL_DOZABLE);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_STANDBY, false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleDoesNotExist(FIREWALL_STANDBY);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_POWERSAVE, false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleDoesNotExist(FIREWALL_POWERSAVE);
+}
+
+namespace {
+
+std::string hwAddrToStr(unsigned char* hwaddr) {
+    return StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3],
+                        hwaddr[4], hwaddr[5]);
+}
+
+int ipv4NetmaskToPrefixLength(in_addr_t mask) {
+    int prefixLength = 0;
+    uint32_t m = ntohl(mask);
+    while (m & (1 << 31)) {
+        prefixLength++;
+        m = m << 1;
+    }
+    return prefixLength;
+}
+
+std::string toStdString(const String16& s) {
+    return std::string(String8(s.string()));
+}
+
+android::netdutils::StatusOr<ifreq> ioctlByIfName(const std::string& ifName, unsigned long flag) {
+    const auto& sys = sSyscalls.get();
+    auto fd = sys.socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    EXPECT_TRUE(isOk(fd.status()));
+
+    struct ifreq ifr = {};
+    strlcpy(ifr.ifr_name, ifName.c_str(), IFNAMSIZ);
+
+    return sys.ioctl(fd.value(), flag, &ifr);
+}
+
+std::string getInterfaceHwAddr(const std::string& ifName) {
+    auto res = ioctlByIfName(ifName, SIOCGIFHWADDR);
+
+    unsigned char hwaddr[ETH_ALEN] = {};
+    if (isOk(res.status())) {
+        memcpy((void*) hwaddr, &res.value().ifr_hwaddr.sa_data, ETH_ALEN);
+    }
+
+    return hwAddrToStr(hwaddr);
+}
+
+int getInterfaceIPv4Prefix(const std::string& ifName) {
+    auto res = ioctlByIfName(ifName, SIOCGIFNETMASK);
+
+    int prefixLength = 0;
+    if (isOk(res.status())) {
+        prefixLength = ipv4NetmaskToPrefixLength(
+                ((struct sockaddr_in*) &res.value().ifr_addr)->sin_addr.s_addr);
+    }
+
+    return prefixLength;
+}
+
+std::string getInterfaceIPv4Addr(const std::string& ifName) {
+    auto res = ioctlByIfName(ifName, SIOCGIFADDR);
+
+    struct in_addr addr = {};
+    if (isOk(res.status())) {
+        addr.s_addr = ((struct sockaddr_in*) &res.value().ifr_addr)->sin_addr.s_addr;
+    }
+
+    return std::string(inet_ntoa(addr));
+}
+
+std::vector<std::string> getInterfaceFlags(const std::string& ifName) {
+    auto res = ioctlByIfName(ifName, SIOCGIFFLAGS);
+
+    unsigned flags = 0;
+    if (isOk(res.status())) {
+        flags = res.value().ifr_flags;
+    }
+
+    std::vector<std::string> ifFlags;
+    ifFlags.push_back(flags & IFF_UP ? toStdString(INetd::IF_STATE_UP())
+                                     : toStdString(INetd::IF_STATE_DOWN()));
+
+    if (flags & IFF_BROADCAST) ifFlags.push_back(toStdString(INetd::IF_FLAG_BROADCAST()));
+    if (flags & IFF_LOOPBACK) ifFlags.push_back(toStdString(INetd::IF_FLAG_LOOPBACK()));
+    if (flags & IFF_POINTOPOINT) ifFlags.push_back(toStdString(INetd::IF_FLAG_POINTOPOINT()));
+    if (flags & IFF_RUNNING) ifFlags.push_back(toStdString(INetd::IF_FLAG_RUNNING()));
+    if (flags & IFF_MULTICAST) ifFlags.push_back(toStdString(INetd::IF_FLAG_MULTICAST()));
+
+    return ifFlags;
+}
+
+bool compareListInterface(const std::vector<std::string>& interfaceList) {
+    const auto& res = InterfaceController::getIfaceNames();
+    EXPECT_TRUE(isOk(res));
+
+    std::vector<std::string> resIfList;
+    resIfList.reserve(res.value().size());
+    resIfList.insert(end(resIfList), begin(res.value()), end(res.value()));
+
+    return resIfList == interfaceList;
+}
+
+int getInterfaceIPv6PrivacyExtensions(const std::string& ifName) {
+    std::string path = StringPrintf("/proc/sys/net/ipv6/conf/%s/use_tempaddr", ifName.c_str());
+    return readIntFromPath(path);
+}
+
+bool getInterfaceEnableIPv6(const std::string& ifName) {
+    std::string path = StringPrintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifName.c_str());
+
+    int disableIPv6 = readIntFromPath(path);
+    return !disableIPv6;
+}
+
+int getInterfaceMtu(const std::string& ifName) {
+    std::string path = StringPrintf("/sys/class/net/%s/mtu", ifName.c_str());
+    return readIntFromPath(path);
+}
+
+void expectInterfaceList(const std::vector<std::string>& interfaceList) {
+    EXPECT_TRUE(compareListInterface(interfaceList));
+}
+
+void expectCurrentInterfaceConfigurationEquals(const std::string& ifName,
+                                               const InterfaceConfigurationParcel& interfaceCfg) {
+    EXPECT_EQ(getInterfaceIPv4Addr(ifName), interfaceCfg.ipv4Addr);
+    EXPECT_EQ(getInterfaceIPv4Prefix(ifName), interfaceCfg.prefixLength);
+    EXPECT_EQ(getInterfaceHwAddr(ifName), interfaceCfg.hwAddr);
+    EXPECT_EQ(getInterfaceFlags(ifName), interfaceCfg.flags);
+}
+
+void expectCurrentInterfaceConfigurationAlmostEqual(const InterfaceConfigurationParcel& setCfg) {
+    EXPECT_EQ(getInterfaceIPv4Addr(setCfg.ifName), setCfg.ipv4Addr);
+    EXPECT_EQ(getInterfaceIPv4Prefix(setCfg.ifName), setCfg.prefixLength);
+
+    const auto& ifFlags = getInterfaceFlags(setCfg.ifName);
+    for (const auto& flag : setCfg.flags) {
+        EXPECT_TRUE(std::find(ifFlags.begin(), ifFlags.end(), flag) != ifFlags.end());
+    }
+}
+
+void expectInterfaceIPv6PrivacyExtensions(const std::string& ifName, bool enable) {
+    int v6PrivacyExtensions = getInterfaceIPv6PrivacyExtensions(ifName);
+    EXPECT_EQ(v6PrivacyExtensions, enable ? 2 : 0);
+}
+
+void expectInterfaceNoAddr(const std::string& ifName) {
+    // noAddr
+    EXPECT_EQ(getInterfaceIPv4Addr(ifName), "0.0.0.0");
+    // noPrefix
+    EXPECT_EQ(getInterfaceIPv4Prefix(ifName), 0);
+}
+
+void expectInterfaceEnableIPv6(const std::string& ifName, bool enable) {
+    int enableIPv6 = getInterfaceEnableIPv6(ifName);
+    EXPECT_EQ(enableIPv6, enable);
+}
+
+void expectInterfaceMtu(const std::string& ifName, const int mtu) {
+    int mtuSize = getInterfaceMtu(ifName);
+    EXPECT_EQ(mtu, mtuSize);
+}
+
+InterfaceConfigurationParcel makeInterfaceCfgParcel(const std::string& ifName,
+                                                    const std::string& addr, int prefixLength,
+                                                    const std::vector<std::string>& flags) {
+    InterfaceConfigurationParcel cfg;
+    cfg.ifName = ifName;
+    cfg.hwAddr = "";
+    cfg.ipv4Addr = addr;
+    cfg.prefixLength = prefixLength;
+    cfg.flags = flags;
+    return cfg;
+}
+
+void expectTunFlags(const InterfaceConfigurationParcel& interfaceCfg) {
+    std::vector<std::string> expectedFlags = {"up", "point-to-point", "running", "multicast"};
+    std::vector<std::string> unexpectedFlags = {"down", "broadcast"};
+
+    for (const auto& flag : expectedFlags) {
+        EXPECT_TRUE(std::find(interfaceCfg.flags.begin(), interfaceCfg.flags.end(), flag) !=
+                    interfaceCfg.flags.end());
+    }
+
+    for (const auto& flag : unexpectedFlags) {
+        EXPECT_TRUE(std::find(interfaceCfg.flags.begin(), interfaceCfg.flags.end(), flag) ==
+                    interfaceCfg.flags.end());
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, InterfaceList) {
+    std::vector<std::string> interfaceListResult;
+
+    binder::Status status = mNetd->interfaceGetList(&interfaceListResult);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceList(interfaceListResult);
+}
+
+TEST_F(BinderTest, InterfaceGetCfg) {
+    InterfaceConfigurationParcel interfaceCfgResult;
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    binder::Status status = mNetd->interfaceGetCfg(sTun.name(), &interfaceCfgResult);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectCurrentInterfaceConfigurationEquals(sTun.name(), interfaceCfgResult);
+    expectTunFlags(interfaceCfgResult);
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, InterfaceSetCfg) {
+    const std::string testAddr = "192.0.2.3";
+    const int testPrefixLength = 24;
+    std::vector<std::string> upFlags = {"up"};
+    std::vector<std::string> downFlags = {"down"};
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    // Set tun interface down.
+    auto interfaceCfg = makeInterfaceCfgParcel(sTun.name(), testAddr, testPrefixLength, downFlags);
+    binder::Status status = mNetd->interfaceSetCfg(interfaceCfg);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectCurrentInterfaceConfigurationAlmostEqual(interfaceCfg);
+
+    // Set tun interface up again.
+    interfaceCfg = makeInterfaceCfgParcel(sTun.name(), testAddr, testPrefixLength, upFlags);
+    status = mNetd->interfaceSetCfg(interfaceCfg);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    status = mNetd->interfaceClearAddrs(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, InterfaceSetIPv6PrivacyExtensions) {
+    // enable
+    binder::Status status = mNetd->interfaceSetIPv6PrivacyExtensions(sTun.name(), true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceIPv6PrivacyExtensions(sTun.name(), true);
+
+    // disable
+    status = mNetd->interfaceSetIPv6PrivacyExtensions(sTun.name(), false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceIPv6PrivacyExtensions(sTun.name(), false);
+}
+
+TEST_F(BinderTest, InterfaceClearAddr) {
+    const std::string testAddr = "192.0.2.3";
+    const int testPrefixLength = 24;
+    std::vector<std::string> noFlags{};
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    auto interfaceCfg = makeInterfaceCfgParcel(sTun.name(), testAddr, testPrefixLength, noFlags);
+    binder::Status status = mNetd->interfaceSetCfg(interfaceCfg);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectCurrentInterfaceConfigurationAlmostEqual(interfaceCfg);
+
+    status = mNetd->interfaceClearAddrs(sTun.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceNoAddr(sTun.name());
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, InterfaceSetEnableIPv6) {
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    // disable
+    binder::Status status = mNetd->interfaceSetEnableIPv6(sTun.name(), false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceEnableIPv6(sTun.name(), false);
+
+    // enable
+    status = mNetd->interfaceSetEnableIPv6(sTun.name(), true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceEnableIPv6(sTun.name(), true);
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, InterfaceSetMtu) {
+    const int testMtu = 1200;
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    binder::Status status = mNetd->interfaceSetMtu(sTun.name(), testMtu);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectInterfaceMtu(sTun.name(), testMtu);
+
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+namespace {
+
+constexpr const char TETHER_FORWARD[] = "tetherctrl_FORWARD";
+constexpr const char TETHER_NAT_POSTROUTING[] = "tetherctrl_nat_POSTROUTING";
+constexpr const char TETHER_RAW_PREROUTING[] = "tetherctrl_raw_PREROUTING";
+constexpr const char TETHER_COUNTERS_CHAIN[] = "tetherctrl_counters";
+
+int iptablesCountRules(const char* binary, const char* table, const char* chainName) {
+    return listIptablesRuleByTable(binary, table, chainName).size();
+}
+
+bool iptablesChainMatch(const char* binary, const char* table, const char* chainName,
+                        const std::vector<std::string>& targetVec) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, table, chainName);
+    if (targetVec.size() != rules.size() - 2) {
+        return false;
+    }
+
+    /*
+     * Check that the rules match. Note that this function matches substrings, not entire rules,
+     * because otherwise rules where "pkts" or "bytes" are nonzero would not match.
+     * Skip first two lines since rules start from third line.
+     * Chain chainName (x references)
+     * pkts bytes target     prot opt in     out     source               destination
+     * ...
+     */
+    int rIndex = 2;
+    for (const auto& target : targetVec) {
+        if (rules[rIndex].find(target) == std::string::npos) {
+            return false;
+        }
+        rIndex++;
+    }
+    return true;
+}
+
+void expectNatEnable(const std::string& intIf, const std::string& extIf) {
+    std::vector<std::string> postroutingV4Match = {"MASQUERADE"};
+    std::vector<std::string> preroutingV4Match = {"CT helper ftp", "CT helper pptp"};
+    std::vector<std::string> forwardV4Match = {
+            "bw_global_alert", "state RELATED", "state INVALID",
+            StringPrintf("tetherctrl_counters  all  --  %s %s", intIf.c_str(), extIf.c_str()),
+            "DROP"};
+
+    // V4
+    EXPECT_TRUE(iptablesChainMatch(IPTABLES_PATH, NAT_TABLE, TETHER_NAT_POSTROUTING,
+                                   postroutingV4Match));
+    EXPECT_TRUE(
+            iptablesChainMatch(IPTABLES_PATH, RAW_TABLE, TETHER_RAW_PREROUTING, preroutingV4Match));
+    EXPECT_TRUE(iptablesChainMatch(IPTABLES_PATH, FILTER_TABLE, TETHER_FORWARD, forwardV4Match));
+
+    std::vector<std::string> forwardV6Match = {"bw_global_alert", "tetherctrl_counters"};
+    std::vector<std::string> preroutingV6Match = {"rpfilter invert"};
+
+    // V6
+    EXPECT_TRUE(iptablesChainMatch(IP6TABLES_PATH, FILTER_TABLE, TETHER_FORWARD, forwardV6Match));
+    EXPECT_TRUE(iptablesChainMatch(IP6TABLES_PATH, RAW_TABLE, TETHER_RAW_PREROUTING,
+                                   preroutingV6Match));
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesTargetsExists(binary, 2, FILTER_TABLE, TETHER_COUNTERS_CHAIN, intIf,
+                                          extIf));
+    }
+}
+
+void expectNatDisable() {
+    // It is the default DROP rule with tethering disable.
+    // Chain tetherctrl_FORWARD (1 references)
+    // pkts bytes target     prot opt in     out     source               destination
+    //    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0
+    std::vector<std::string> forwardV4Match = {"DROP"};
+    EXPECT_TRUE(iptablesChainMatch(IPTABLES_PATH, FILTER_TABLE, TETHER_FORWARD, forwardV4Match));
+
+    // We expect that these chains should be empty.
+    EXPECT_EQ(2, iptablesCountRules(IPTABLES_PATH, NAT_TABLE, TETHER_NAT_POSTROUTING));
+    EXPECT_EQ(2, iptablesCountRules(IPTABLES_PATH, RAW_TABLE, TETHER_RAW_PREROUTING));
+
+    EXPECT_EQ(2, iptablesCountRules(IP6TABLES_PATH, FILTER_TABLE, TETHER_FORWARD));
+    EXPECT_EQ(2, iptablesCountRules(IP6TABLES_PATH, RAW_TABLE, TETHER_RAW_PREROUTING));
+
+    // Netd won't clear tether quota rule, we don't care rule in tetherctrl_counters.
+}
+
+}  // namespace
+
+TEST_F(BinderTest, TetherForwardAddRemove) {
+    binder::Status status = mNetd->tetherAddForward(sTun.name(), sTun2.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectNatEnable(sTun.name(), sTun2.name());
+
+    status = mNetd->tetherRemoveForward(sTun.name(), sTun2.name());
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectNatDisable();
+}
+
+namespace {
+
+using TripleInt = std::array<int, 3>;
+
+TripleInt readProcFileToTripleInt(const std::string& path) {
+    std::string valueString;
+    int min, def, max;
+    EXPECT_TRUE(ReadFileToString(path, &valueString));
+    EXPECT_EQ(3, sscanf(valueString.c_str(), "%d %d %d", &min, &def, &max));
+    return {min, def, max};
+}
+
+void updateAndCheckTcpBuffer(sp<INetd>& netd, TripleInt& rmemValues, TripleInt& wmemValues) {
+    std::string testRmemValues =
+            StringPrintf("%u %u %u", rmemValues[0], rmemValues[1], rmemValues[2]);
+    std::string testWmemValues =
+            StringPrintf("%u %u %u", wmemValues[0], wmemValues[1], wmemValues[2]);
+    EXPECT_TRUE(netd->setTcpRWmemorySize(testRmemValues, testWmemValues).isOk());
+
+    TripleInt newRmemValues = readProcFileToTripleInt(TCP_RMEM_PROC_FILE);
+    TripleInt newWmemValues = readProcFileToTripleInt(TCP_WMEM_PROC_FILE);
+
+    for (int i = 0; i < 3; i++) {
+        SCOPED_TRACE(StringPrintf("tcp_mem value %d should be equal", i));
+        EXPECT_EQ(rmemValues[i], newRmemValues[i]);
+        EXPECT_EQ(wmemValues[i], newWmemValues[i]);
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, TcpBufferSet) {
+    TripleInt rmemValue = readProcFileToTripleInt(TCP_RMEM_PROC_FILE);
+    TripleInt testRmemValue{rmemValue[0] + 42, rmemValue[1] + 42, rmemValue[2] + 42};
+    TripleInt wmemValue = readProcFileToTripleInt(TCP_WMEM_PROC_FILE);
+    TripleInt testWmemValue{wmemValue[0] + 42, wmemValue[1] + 42, wmemValue[2] + 42};
+
+    updateAndCheckTcpBuffer(mNetd, testRmemValue, testWmemValue);
+    updateAndCheckTcpBuffer(mNetd, rmemValue, wmemValue);
+}
+
+namespace {
+
+void checkUidsInPermissionMap(std::vector<int32_t>& uids, bool exist) {
+    android::bpf::BpfMap<uint32_t, uint8_t> uidPermissionMap(
+            android::bpf::mapRetrieve(UID_PERMISSION_MAP_PATH, 0));
+    for (int32_t uid : uids) {
+        android::netdutils::StatusOr<uint8_t> permission = uidPermissionMap.readValue(uid);
+        if (exist) {
+            EXPECT_TRUE(isOk(permission));
+            EXPECT_EQ(INetd::PERMISSION_NONE, permission.value());
+        } else {
+            EXPECT_FALSE(isOk(permission));
+            EXPECT_EQ(ENOENT, permission.status().code());
+        }
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, TestInternetPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<int32_t> appUids = {TEST_UID1, TEST_UID2};
+
+    mNetd->trafficSetNetPermForUids(INetd::PERMISSION_INTERNET, appUids);
+    checkUidsInPermissionMap(appUids, false);
+    mNetd->trafficSetNetPermForUids(INetd::PERMISSION_NONE, appUids);
+    checkUidsInPermissionMap(appUids, true);
+    mNetd->trafficSetNetPermForUids(INetd::PERMISSION_UNINSTALLED, appUids);
+    checkUidsInPermissionMap(appUids, false);
+}
+
+TEST_F(BinderTest, UnsolEvents) {
+    auto testUnsolService = android::net::TestUnsolService::start();
+    std::string oldTunName = sTun.name();
+    std::string newTunName = "unsolTest";
+    testUnsolService->tarVec.push_back(oldTunName);
+    testUnsolService->tarVec.push_back(newTunName);
+    auto& cv = testUnsolService->getCv();
+    auto& cvMutex = testUnsolService->getCvMutex();
+    binder::Status status = mNetd->registerUnsolicitedEventListener(
+            android::interface_cast<android::net::INetdUnsolicitedEventListener>(testUnsolService));
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+    // TODO: Add test for below events
+    //       StrictCleartextDetected / InterfaceDnsServersAdded
+    //       InterfaceClassActivity / QuotaLimitReached / InterfaceAddressRemoved
+
+    {
+        std::unique_lock lock(cvMutex);
+
+        // Re-init test Tun, and we expect that we will get some unsol events.
+        // Use the test Tun device name to verify if we receive its unsol events.
+        sTun.destroy();
+        // Use predefined name
+        sTun.init(newTunName);
+
+        EXPECT_EQ(std::cv_status::no_timeout, cv.wait_for(lock, std::chrono::seconds(2)));
+    }
+
+    // bit mask 1101101000
+    // Test only covers below events currently
+    const uint32_t kExpectedEvents = InterfaceAddressUpdated | InterfaceAdded | InterfaceRemoved |
+                                     InterfaceLinkStatusChanged | RouteChanged;
+    EXPECT_EQ(kExpectedEvents, testUnsolService->getReceived());
+
+    // Re-init sTun to clear predefined name
+    sTun.destroy();
+    sTun.init();
+}
+
+TEST_F(BinderTest, NDC) {
+    struct Command {
+        const std::string cmdString;
+        const std::string expectedResult;
+    };
+
+    // clang-format off
+    // Do not change the commands order
+    const Command networkCmds[] = {
+            {StringPrintf("ndc network create %d", TEST_NETID1),
+             "200 0 success"},
+            {StringPrintf("ndc network interface add %d %s", TEST_NETID1, sTun.name().c_str()),
+             "200 0 success"},
+            {StringPrintf("ndc network interface remove %d %s", TEST_NETID1, sTun.name().c_str()),
+             "200 0 success"},
+            {StringPrintf("ndc network interface add %d %s", TEST_NETID2, sTun.name().c_str()),
+             "400 0 addInterfaceToNetwork() failed (Machine is not on the network)"},
+            {StringPrintf("ndc network destroy %d", TEST_NETID1),
+             "200 0 success"},
+    };
+
+    const std::vector<Command> ipfwdCmds = {
+            {"ndc ipfwd enable " + sTun.name(),
+             "200 0 ipfwd operation succeeded"},
+            {"ndc ipfwd disable " + sTun.name(),
+             "200 0 ipfwd operation succeeded"},
+            {"ndc ipfwd add lo2 lo3",
+             "400 0 ipfwd operation failed (No such process)"},
+            {"ndc ipfwd add " + sTun.name() + " " + sTun2.name(),
+             "200 0 ipfwd operation succeeded"},
+            {"ndc ipfwd remove " + sTun.name() + " " + sTun2.name(),
+             "200 0 ipfwd operation succeeded"},
+    };
+
+    static const struct {
+        const char* ipVersion;
+        const char* testDest;
+        const char* testNextHop;
+        const bool expectSuccess;
+        const std::string expectedResult;
+    } kTestData[] = {
+            {IP_RULE_V4, "0.0.0.0/0",          "",            true,
+             "200 0 success"},
+            {IP_RULE_V4, "10.251.0.0/16",      "",            true,
+             "200 0 success"},
+            {IP_RULE_V4, "10.251.0.0/16",      "fe80::/64",   false,
+             "400 0 addRoute() failed (Invalid argument)",},
+            {IP_RULE_V6, "::/0",               "",            true,
+             "200 0 success"},
+            {IP_RULE_V6, "2001:db8:cafe::/64", "",            true,
+             "200 0 success"},
+            {IP_RULE_V6, "fe80::/64",          "0.0.0.0",     false,
+             "400 0 addRoute() failed (Invalid argument)"},
+    };
+    // clang-format on
+
+    for (const auto& cmd : networkCmds) {
+        const std::vector<std::string> result = runCommand(cmd.cmdString);
+        SCOPED_TRACE(cmd.cmdString);
+        EXPECT_EQ(result.size(), 1U);
+        EXPECT_EQ(cmd.expectedResult, Trim(result[0]));
+    }
+
+    for (const auto& cmd : ipfwdCmds) {
+        const std::vector<std::string> result = runCommand(cmd.cmdString);
+        SCOPED_TRACE(cmd.cmdString);
+        EXPECT_EQ(result.size(), 1U);
+        EXPECT_EQ(cmd.expectedResult, Trim(result[0]));
+    }
+
+    // Add test physical network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
+
+    for (const auto& td : kTestData) {
+        const std::string routeAddCmd =
+                StringPrintf("ndc network route add %d %s %s %s", TEST_NETID1, sTun.name().c_str(),
+                             td.testDest, td.testNextHop);
+        const std::string routeRemoveCmd =
+                StringPrintf("ndc network route remove %d %s %s %s", TEST_NETID1,
+                             sTun.name().c_str(), td.testDest, td.testNextHop);
+        std::vector<std::string> result = runCommand(routeAddCmd);
+        SCOPED_TRACE(routeAddCmd);
+        EXPECT_EQ(result.size(), 1U);
+        EXPECT_EQ(td.expectedResult, Trim(result[0]));
+        if (td.expectSuccess) {
+            expectNetworkRouteExists(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                     sTun.name().c_str());
+            result = runCommand(routeRemoveCmd);
+            EXPECT_EQ(result.size(), 1U);
+            EXPECT_EQ(td.expectedResult, Trim(result[0]));
+            expectNetworkRouteDoesNotExist(td.ipVersion, sTun.name(), td.testDest, td.testNextHop,
+                                           sTun.name().c_str());
+        }
+    }
+    // Remove test physical network
+    EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
+}
+
+TEST_F(BinderTest, OemNetdRelated) {
+    sp<IBinder> binder;
+    binder::Status status = mNetd->getOemNetd(&binder);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    sp<com::android::internal::net::IOemNetd> oemNetd;
+    if (binder != nullptr) {
+        oemNetd = android::interface_cast<com::android::internal::net::IOemNetd>(binder);
+    }
+    ASSERT_NE(nullptr, oemNetd.get());
+
+    TimedOperation t("OemNetd isAlive RPC");
+    bool isAlive = false;
+    oemNetd->isAlive(&isAlive);
+    ASSERT_TRUE(isAlive);
+
+    class TestOemUnsolListener
+        : public com::android::internal::net::BnOemNetdUnsolicitedEventListener {
+      public:
+        android::binder::Status onRegistered() override {
+            std::lock_guard lock(mCvMutex);
+            mCv.notify_one();
+            return android::binder::Status::ok();
+        }
+        std::condition_variable& getCv() { return mCv; }
+        std::mutex& getCvMutex() { return mCvMutex; }
+
+      private:
+        std::mutex mCvMutex;
+        std::condition_variable mCv;
+    };
+
+    // Start the Binder thread pool.
+    android::ProcessState::self()->startThreadPool();
+
+    android::sp<TestOemUnsolListener> testListener = new TestOemUnsolListener();
+
+    auto& cv = testListener->getCv();
+    auto& cvMutex = testListener->getCvMutex();
+
+    {
+        std::unique_lock lock(cvMutex);
+
+        status = oemNetd->registerOemUnsolicitedEventListener(
+                ::android::interface_cast<
+                        com::android::internal::net::IOemNetdUnsolicitedEventListener>(
+                        testListener));
+        EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+
+        // Wait for receiving expected events.
+        EXPECT_EQ(std::cv_status::no_timeout, cv.wait_for(lock, std::chrono::seconds(2)));
+    }
+}
+
+void BinderTest::createVpnNetworkWithUid(bool secure, uid_t uid, int vpnNetId,
+                                         int fallthroughNetId) {
+    // Re-init sTun* to ensure route rule exists.
+    sTun.destroy();
+    sTun.init();
+    sTun2.destroy();
+    sTun2.init();
+
+    // Create physical network with fallthroughNetId but not set it as default network
+    EXPECT_TRUE(mNetd->networkCreatePhysical(fallthroughNetId, INetd::PERMISSION_NONE).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(fallthroughNetId, sTun.name()).isOk());
+
+    // Create VPN with vpnNetId
+    EXPECT_TRUE(mNetd->networkCreateVpn(vpnNetId, secure).isOk());
+
+    // Add uid to VPN
+    EXPECT_TRUE(mNetd->networkAddUidRanges(vpnNetId, {makeUidRangeParcel(uid, uid)}).isOk());
+    EXPECT_TRUE(mNetd->networkAddInterface(vpnNetId, sTun2.name()).isOk());
+
+    // Add default route to fallthroughNetwork
+    EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID1, sTun.name(), "::/0", "").isOk());
+    // Add limited route
+    EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID2, sTun2.name(), "2001:db8::/32", "").isOk());
+}
+
+namespace {
+
+class ScopedUidChange {
+  public:
+    explicit ScopedUidChange(uid_t uid) : mInputUid(uid) {
+        mStoredUid = getuid();
+        if (mInputUid == mStoredUid) return;
+        EXPECT_TRUE(seteuid(uid) == 0);
+    }
+    ~ScopedUidChange() {
+        if (mInputUid == mStoredUid) return;
+        EXPECT_TRUE(seteuid(mStoredUid) == 0);
+    }
+
+  private:
+    uid_t mInputUid;
+    uid_t mStoredUid;
+};
+
+constexpr uint32_t RULE_PRIORITY_VPN_FALLTHROUGH = 21000;
+
+void clearQueue(int tunFd) {
+    char buf[4096];
+    int ret;
+    do {
+        ret = read(tunFd, buf, sizeof(buf));
+    } while (ret > 0);
+}
+
+void checkDataReceived(int udpSocket, int tunFd) {
+    char buf[4096] = {};
+    // Clear tunFd's queue before write something because there might be some
+    // arbitrary packets in the queue. (e.g. ICMPv6 packet)
+    clearQueue(tunFd);
+    EXPECT_EQ(4, write(udpSocket, "foo", sizeof("foo")));
+    // TODO: extract header and verify data
+    EXPECT_GT(read(tunFd, buf, sizeof(buf)), 0);
+}
+
+bool sendIPv6PacketFromUid(uid_t uid, const in6_addr& dstAddr, Fwmark* fwmark, int tunFd) {
+    ScopedUidChange scopedUidChange(uid);
+    android::base::unique_fd testSocket(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+    if (testSocket < 0) return false;
+
+    const sockaddr_in6 dst6 = {.sin6_family = AF_INET6, .sin6_addr = dstAddr, .sin6_port = 42};
+    int res = connect(testSocket, (sockaddr*)&dst6, sizeof(dst6));
+    socklen_t fwmarkLen = sizeof(fwmark->intValue);
+    EXPECT_NE(-1, getsockopt(testSocket, SOL_SOCKET, SO_MARK, &(fwmark->intValue), &fwmarkLen));
+    if (res == -1) return false;
+
+    char addr[INET6_ADDRSTRLEN];
+    inet_ntop(AF_INET6, &dstAddr, addr, INET6_ADDRSTRLEN);
+    SCOPED_TRACE(StringPrintf("sendIPv6PacketFromUid, addr: %s, uid: %u", addr, uid));
+    checkDataReceived(testSocket, tunFd);
+    return true;
+}
+
+void expectVpnFallthroughRuleExists(const std::string& ifName, int vpnNetId) {
+    std::string vpnFallthroughRule =
+            StringPrintf("%d:\tfrom all fwmark 0x%x/0xffff lookup %s",
+                         RULE_PRIORITY_VPN_FALLTHROUGH, vpnNetId, ifName.c_str());
+    for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
+        EXPECT_TRUE(ipRuleExists(ipVersion, vpnFallthroughRule));
+    }
+}
+
+void expectVpnFallthroughWorks(android::net::INetd* netdService, bool bypassable, uid_t uid,
+                               const TunInterface& fallthroughNetwork,
+                               const TunInterface& vpnNetwork, int vpnNetId = TEST_NETID2,
+                               int fallthroughNetId = TEST_NETID1) {
+    // Set default network to NETID_UNSET
+    EXPECT_TRUE(netdService->networkSetDefault(NETID_UNSET).isOk());
+
+    // insideVpnAddr based on the route we added in createVpnNetworkWithUid
+    in6_addr insideVpnAddr = {
+            {// 2001:db8:cafe::1
+             .u6_addr8 = {0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}};
+    // outsideVpnAddr will hit the route in the fallthrough network route table
+    // because we added default route in createVpnNetworkWithUid
+    in6_addr outsideVpnAddr = {
+            {// 2607:f0d0:1002::4
+             .u6_addr8 = {0x26, 0x07, 0xf0, 0xd0, 0x10, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}}};
+
+    int fallthroughFd = fallthroughNetwork.getFdForTesting();
+    int vpnFd = vpnNetwork.getFdForTesting();
+    // Expect all connections to fail because UID 0 is not routed to the VPN and there is no
+    // default network.
+    Fwmark fwmark;
+    EXPECT_FALSE(sendIPv6PacketFromUid(0, outsideVpnAddr, &fwmark, fallthroughFd));
+    EXPECT_FALSE(sendIPv6PacketFromUid(0, insideVpnAddr, &fwmark, fallthroughFd));
+
+    // Set default network
+    EXPECT_TRUE(netdService->networkSetDefault(fallthroughNetId).isOk());
+
+    // Connections go on the default network because UID 0 is not subject to the VPN.
+    EXPECT_TRUE(sendIPv6PacketFromUid(0, outsideVpnAddr, &fwmark, fallthroughFd));
+    EXPECT_EQ(fallthroughNetId | 0xC0000, static_cast<int>(fwmark.intValue));
+    EXPECT_TRUE(sendIPv6PacketFromUid(0, insideVpnAddr, &fwmark, fallthroughFd));
+    EXPECT_EQ(fallthroughNetId | 0xC0000, static_cast<int>(fwmark.intValue));
+
+    // Check if fallthrough rule exists
+    expectVpnFallthroughRuleExists(fallthroughNetwork.name(), vpnNetId);
+
+    // Expect fallthrough to default network
+    // The fwmark differs depending on whether the VPN is bypassable or not.
+    EXPECT_TRUE(sendIPv6PacketFromUid(uid, outsideVpnAddr, &fwmark, fallthroughFd));
+    EXPECT_EQ(bypassable ? vpnNetId : fallthroughNetId, static_cast<int>(fwmark.intValue));
+
+    // Expect connect success, packet will be sent to vpnFd.
+    EXPECT_TRUE(sendIPv6PacketFromUid(uid, insideVpnAddr, &fwmark, vpnFd));
+    EXPECT_EQ(bypassable ? vpnNetId : fallthroughNetId, static_cast<int>(fwmark.intValue));
+
+    // Explicitly select vpn network
+    setNetworkForProcess(vpnNetId);
+
+    // Expect fallthrough to default network
+    EXPECT_TRUE(sendIPv6PacketFromUid(0, outsideVpnAddr, &fwmark, fallthroughFd));
+    // Expect the mark contains all the bit because we've selected network.
+    EXPECT_EQ(vpnNetId | 0xF0000, static_cast<int>(fwmark.intValue));
+
+    // Expect connect success, packet will be sent to vpnFd.
+    EXPECT_TRUE(sendIPv6PacketFromUid(0, insideVpnAddr, &fwmark, vpnFd));
+    // Expect the mark contains all the bit because we've selected network.
+    EXPECT_EQ(vpnNetId | 0xF0000, static_cast<int>(fwmark.intValue));
+
+    // Explicitly select fallthrough network
+    setNetworkForProcess(fallthroughNetId);
+
+    // The mark is set to fallthrough network because we've selected it.
+    EXPECT_TRUE(sendIPv6PacketFromUid(0, outsideVpnAddr, &fwmark, fallthroughFd));
+    EXPECT_TRUE(sendIPv6PacketFromUid(0, insideVpnAddr, &fwmark, fallthroughFd));
+
+    // If vpn is BypassableVPN, connections can also go on the fallthrough network under vpn uid.
+    if (bypassable) {
+        EXPECT_TRUE(sendIPv6PacketFromUid(uid, outsideVpnAddr, &fwmark, fallthroughFd));
+        EXPECT_TRUE(sendIPv6PacketFromUid(uid, insideVpnAddr, &fwmark, fallthroughFd));
+    } else {
+        // If not, no permission to bypass vpn.
+        EXPECT_FALSE(sendIPv6PacketFromUid(uid, outsideVpnAddr, &fwmark, fallthroughFd));
+        EXPECT_FALSE(sendIPv6PacketFromUid(uid, insideVpnAddr, &fwmark, fallthroughFd));
+    }
+}
+
+}  // namespace
+
+TEST_F(BinderTest, SecureVPNFallthrough) {
+    createVpnNetworkWithUid(true /* secure */, TEST_UID1);
+    // Get current default network NetId
+    ASSERT_TRUE(mNetd->networkGetDefault(&mStoredDefaultNetwork).isOk());
+    expectVpnFallthroughWorks(mNetd.get(), false /* bypassable */, TEST_UID1, sTun, sTun2);
+}
+
+TEST_F(BinderTest, BypassableVPNFallthrough) {
+    createVpnNetworkWithUid(false /* secure */, TEST_UID1);
+    // Get current default network NetId
+    ASSERT_TRUE(mNetd->networkGetDefault(&mStoredDefaultNetwork).isOk());
+    expectVpnFallthroughWorks(mNetd.get(), true /* bypassable */, TEST_UID1, sTun, sTun2);
+}
\ No newline at end of file
diff --git a/tests/bpf_base_test.cpp b/tests/bpf_base_test.cpp
index e023052..46e4306 100644
--- a/tests/bpf_base_test.cpp
+++ b/tests/bpf_base_test.cpp
@@ -29,17 +29,15 @@
 #include <gtest/gtest.h>
 
 #include <cutils/qtaguid.h>
+#include <processgroup/processgroup.h>
 
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
 #include "bpf/BpfMap.h"
 #include "bpf/BpfUtils.h"
+#include "netdbpf/bpf_shared.h"
 
-using namespace android::bpf;
-
-using android::base::unique_fd;
-using android::netdutils::status::ok;
 using android::netdutils::StatusOr;
 
 namespace android {
@@ -52,6 +50,15 @@
 constexpr int TEST_COUNTERSET = 1;
 constexpr int DEFAULT_COUNTERSET = 0;
 
+#define SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED                                            \
+    do {                                                                              \
+        if (android::bpf::getBpfSupportLevel() != android::bpf::BpfLevel::EXTENDED) { \
+            GTEST_LOG_(INFO) << "This test is skipped since extended bpf feature"     \
+                             << "not supported\n";                                    \
+            return;                                                                   \
+        }                                                                             \
+    } while (0)
+
 class BpfBasicTest : public testing::Test {
   protected:
     BpfBasicTest() {}
@@ -60,8 +67,10 @@
 TEST_F(BpfBasicTest, TestCgroupMounted) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
-    ASSERT_EQ(0, access(CGROUP_ROOT_PATH, R_OK));
-    ASSERT_EQ(0, access("/dev/cg2_bpf/cgroup.controllers", R_OK));
+    std::string cg2_path;
+    ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cg2_path));
+    ASSERT_EQ(0, access(cg2_path.c_str(), R_OK));
+    ASSERT_EQ(0, access((cg2_path + "/cgroup.controllers").c_str(), R_OK));
 }
 
 TEST_F(BpfBasicTest, TestTrafficControllerSetUp) {
@@ -73,13 +82,19 @@
     ASSERT_EQ(0, access(XT_BPF_EGRESS_PROG_PATH, R_OK));
     ASSERT_EQ(0, access(COOKIE_TAG_MAP_PATH, R_OK));
     ASSERT_EQ(0, access(UID_COUNTERSET_MAP_PATH, R_OK));
-    ASSERT_EQ(0, access(UID_STATS_MAP_PATH, R_OK));
-    ASSERT_EQ(0, access(TAG_STATS_MAP_PATH, R_OK));
+    ASSERT_EQ(0, access(STATS_MAP_A_PATH, R_OK));
+    ASSERT_EQ(0, access(STATS_MAP_B_PATH, R_OK));
     ASSERT_EQ(0, access(IFACE_INDEX_NAME_MAP_PATH, R_OK));
     ASSERT_EQ(0, access(IFACE_STATS_MAP_PATH, R_OK));
-    ASSERT_EQ(0, access(DOZABLE_UID_MAP_PATH, R_OK));
-    ASSERT_EQ(0, access(STANDBY_UID_MAP_PATH, R_OK));
-    ASSERT_EQ(0, access(POWERSAVE_UID_MAP_PATH, R_OK));
+    ASSERT_EQ(0, access(CONFIGURATION_MAP_PATH, R_OK));
+    ASSERT_EQ(0, access(UID_OWNER_MAP_PATH, R_OK));
+}
+
+TEST_F(BpfBasicTest, TestSocketFilterSetUp) {
+    SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED;
+
+    ASSERT_EQ(0, access(CGROUP_SOCKET_PROG_PATH, R_OK));
+    ASSERT_EQ(0, access(UID_PERMISSION_MAP_PATH, R_OK));
 }
 
 TEST_F(BpfBasicTest, TestTagSocket) {
@@ -87,7 +102,7 @@
 
     BpfMap<uint64_t, UidTag> cookieTagMap(mapRetrieve(COOKIE_TAG_MAP_PATH, 0));
     ASSERT_LE(0, cookieTagMap.getMap());
-    int sock = socket(AF_INET6, SOCK_STREAM, 0);
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_LE(0, sock);
     uint64_t cookie = getSocketCookie(sock);
     ASSERT_NE(NONEXISTENT_COOKIE, cookie);
@@ -107,7 +122,7 @@
 
     BpfMap<uint64_t, UidTag> cookieTagMap(mapRetrieve(COOKIE_TAG_MAP_PATH, 0));
     ASSERT_LE(0, cookieTagMap.getMap());
-    int sock = socket(AF_INET6, SOCK_STREAM, 0);
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_LE(0, sock);
     uint64_t cookie = getSocketCookie(sock);
     ASSERT_NE(NONEXISTENT_COOKIE, cookie);
@@ -119,14 +134,14 @@
     ASSERT_EQ(0, close(sock));
     // Check map periodically until sk destroy handler have done its job.
     for (int i = 0; i < 10; i++) {
+        usleep(5000);  // 5ms
         tagResult = cookieTagMap.readValue(cookie);
         if (!isOk(tagResult)) {
             ASSERT_EQ(ENOENT, tagResult.status().code());
             return;
         }
-        usleep(50);
     }
-    FAIL() << "socket tag still exist after 500ms";
+    FAIL() << "socket tag still exist after 50ms";
 }
 
 TEST_F(BpfBasicTest, TestChangeCounterSet) {
@@ -148,29 +163,29 @@
 TEST_F(BpfBasicTest, TestDeleteTagData) {
     SKIP_IF_BPF_NOT_SUPPORTED;
 
-    BpfMap<StatsKey, StatsValue> uidStatsMap(mapRetrieve(UID_STATS_MAP_PATH, 0));
-    ASSERT_LE(0, uidStatsMap.getMap());
-    BpfMap<StatsKey, StatsValue> tagStatsMap(mapRetrieve(TAG_STATS_MAP_PATH, 0));
-    ASSERT_LE(0, tagStatsMap.getMap());
+    BpfMap<StatsKey, StatsValue> statsMapA(mapRetrieve(STATS_MAP_A_PATH, 0));
+    ASSERT_LE(0, statsMapA.getMap());
+    BpfMap<StatsKey, StatsValue> statsMapB(mapRetrieve(STATS_MAP_B_PATH, 0));
+    ASSERT_LE(0, statsMapB.getMap());
     BpfMap<uint32_t, StatsValue> appUidStatsMap(mapRetrieve(APP_UID_STATS_MAP_PATH, 0));
     ASSERT_LE(0, appUidStatsMap.getMap());
 
     StatsKey key = {.uid = TEST_UID, .tag = TEST_TAG, .counterSet = TEST_COUNTERSET,
                     .ifaceIndex = 1};
     StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
-    EXPECT_TRUE(isOk(tagStatsMap.writeValue(key, statsMapValue, BPF_ANY)));
+    EXPECT_TRUE(isOk(statsMapB.writeValue(key, statsMapValue, BPF_ANY)));
     key.tag = 0;
-    EXPECT_TRUE(isOk(uidStatsMap.writeValue(key, statsMapValue, BPF_ANY)));
+    EXPECT_TRUE(isOk(statsMapA.writeValue(key, statsMapValue, BPF_ANY)));
     EXPECT_TRUE(isOk(appUidStatsMap.writeValue(TEST_UID, statsMapValue, BPF_ANY)));
     ASSERT_EQ(0, qtaguid_deleteTagData(0, TEST_UID));
-    StatusOr<StatsValue> statsResult = uidStatsMap.readValue(key);
+    StatusOr<StatsValue> statsResult = statsMapA.readValue(key);
     ASSERT_FALSE(isOk(statsResult));
     ASSERT_EQ(ENOENT, statsResult.status().code());
     statsResult = appUidStatsMap.readValue(TEST_UID);
     ASSERT_FALSE(isOk(statsResult));
     ASSERT_EQ(ENOENT, statsResult.status().code());
     key.tag = TEST_TAG;
-    statsResult = tagStatsMap.readValue(key);
+    statsResult = statsMapB.readValue(key);
     ASSERT_FALSE(isOk(statsResult));
     ASSERT_EQ(ENOENT, statsResult.status().code());
 }
diff --git a/tests/dns_responder/Android.mk b/tests/dns_responder/Android.mk
deleted file mode 100644
index 44c8249..0000000
--- a/tests/dns_responder/Android.mk
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# 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.
-#
-LOCAL_PATH := $(call my-dir)
-
-# TODO describe library here
-include $(CLEAR_VARS)
-LOCAL_MODULE := libnetd_test_dnsresponder
-LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter -Wthread-safety
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CFLAGS += -Wno-varargs
-
-EXTRA_LDLIBS := -lpthread
-LOCAL_SHARED_LIBRARIES += \
-    libbase \
-    libbinder \
-    libcrypto \
-    liblog \
-    libnetd_client \
-    libssl \
-    libnetdutils
-LOCAL_STATIC_LIBRARIES += libutils
-
-LOCAL_AIDL_INCLUDES += \
-    frameworks/native/aidl/binder \
-    system/netd/server/binder
-
-LOCAL_C_INCLUDES += system/netd/include \
-                    system/netd/server \
-                    system/netd/server/binder \
-                    system/netd/tests/dns_responder \
-                    bionic/libc/dns/include
-
-LOCAL_SRC_FILES := dns_responder.cpp \
-                   dns_responder_client.cpp \
-                   dns_tls_frontend.cpp \
-                   ../../server/binder/android/net/INetd.aidl \
-                   ../../server/binder/android/net/UidRange.cpp
-
-LOCAL_MODULE_TAGS := eng tests
-
-include $(BUILD_STATIC_LIBRARY)
diff --git a/tests/dns_responder/dns_responder.h b/tests/dns_responder/dns_responder.h
deleted file mode 100644
index 9e8ab3d..0000000
--- a/tests/dns_responder/dns_responder.h
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 requied 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 DNS_RESPONDER_H
-#define DNS_RESPONDER_H
-
-#include <arpa/nameser.h>
-
-#include <atomic>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <unordered_map>
-#include <vector>
-
-#include <android-base/thread_annotations.h>
-
-namespace test {
-
-struct DNSHeader;
-struct DNSQuestion;
-struct DNSRecord;
-
-/*
- * Simple DNS responder, which replies to queries with the registered response
- * for that type. Class is assumed to be IN. If no response is registered, the
- * default error response code is returned.
- */
-class DNSResponder {
-public:
-    DNSResponder(std::string listen_address, std::string listen_service,
-                 int poll_timeout_ms, uint16_t error_rcode,
-                 double response_probability);
-    ~DNSResponder();
-    void addMapping(const char* name, ns_type type, const char* addr);
-    void removeMapping(const char* name, ns_type type);
-    void setResponseProbability(double response_probability);
-    void setFailOnEdns(bool fail) { fail_on_edns_ = fail; }
-    bool running() const;
-    bool startServer();
-    bool stopServer();
-    const std::string& listen_address() const {
-        return listen_address_;
-    }
-    const std::string& listen_service() const {
-        return listen_service_;
-    }
-    std::vector<std::pair<std::string, ns_type>> queries() const;
-    void clearQueries();
-
-private:
-    // Key used for accessing mappings.
-    struct QueryKey {
-        std::string name;
-        unsigned type;
-        QueryKey(std::string n, unsigned t) : name(n), type(t) {}
-        bool operator == (const QueryKey& o) const {
-            return name == o.name && type == o.type;
-        }
-        bool operator < (const QueryKey& o) const {
-            if (name < o.name) return true;
-            if (name > o.name) return false;
-            return type < o.type;
-        }
-    };
-
-    struct QueryKeyHash {
-        size_t operator() (const QueryKey& key) const {
-            return std::hash<std::string>()(key.name) +
-                   static_cast<size_t>(key.type);
-        }
-    };
-
-    // DNS request handler.
-    void requestHandler();
-
-    // Parses and generates a response message for incoming DNS requests.
-    // Returns false on parsing errors.
-    bool handleDNSRequest(const char* buffer, ssize_t buffer_len,
-                          char* response, size_t* response_len) const;
-
-    bool addAnswerRecords(const DNSQuestion& question,
-                          std::vector<DNSRecord>* answers) const;
-
-    bool generateErrorResponse(DNSHeader* header, ns_rcode rcode,
-                               char* response, size_t* response_len) const;
-    bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
-                           size_t* response_len) const;
-
-
-    // Address and service to listen on, currently limited to UDP.
-    const std::string listen_address_;
-    const std::string listen_service_;
-    // epoll_wait() timeout in ms.
-    const int poll_timeout_ms_;
-    // Error code to return for requests for an unknown name.
-    const uint16_t error_rcode_;
-    // Probability that a valid response is being sent instead of being sent
-    // instead of returning error_rcode_.
-    std::atomic<double> response_probability_;
-
-    // If true, behave like an old DNS server that doesn't support EDNS.
-    // Default false.
-    std::atomic<bool> fail_on_edns_;
-
-    // Mappings from (name, type) to registered response and the
-    // mutex protecting them.
-    std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_
-        GUARDED_BY(mappings_mutex_);
-    mutable std::mutex mappings_mutex_;
-    // Query names received so far and the corresponding mutex.
-    mutable std::vector<std::pair<std::string, ns_type>> queries_
-        GUARDED_BY(queries_mutex_);
-    mutable std::mutex queries_mutex_;
-    // Socket on which the server is listening.
-    int socket_;
-    // File descriptor for epoll.
-    int epoll_fd_;
-    // Signal for request handler termination.
-    std::atomic<bool> terminate_;
-    // Thread for handling incoming threads.
-    std::thread handler_thread_ GUARDED_BY(update_mutex_);
-    std::mutex update_mutex_;
-};
-
-}  // namespace test
-
-#endif  // DNS_RESPONDER_H
diff --git a/tests/dns_responder/dns_responder_client.cpp b/tests/dns_responder/dns_responder_client.cpp
deleted file mode 100644
index 90581fa..0000000
--- a/tests/dns_responder/dns_responder_client.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "dns_responder_client"
-#include "dns_responder_client.h"
-
-#include <android-base/stringprintf.h>
-#include <utils/Log.h>
-
-// TODO: make this dynamic and stop depending on implementation details.
-#define TEST_OEM_NETWORK "oem29"
-#define TEST_NETID 30
-
-// TODO: move this somewhere shared.
-static const char* ANDROID_DNS_MODE = "ANDROID_DNS_MODE";
-
-using android::base::StringPrintf;
-
-void DnsResponderClient::SetupMappings(unsigned num_hosts, const std::vector<std::string>& domains,
-        std::vector<Mapping>* mappings) {
-    mappings->resize(num_hosts * domains.size());
-    auto mappings_it = mappings->begin();
-    for (unsigned i = 0 ; i < num_hosts ; ++i) {
-        for (const auto& domain : domains) {
-            mappings_it->host = StringPrintf("host%u", i);
-            mappings_it->entry = StringPrintf("%s.%s.", mappings_it->host.c_str(),
-                    domain.c_str());
-            mappings_it->ip4 = StringPrintf("192.0.2.%u", i%253 + 1);
-            mappings_it->ip6 = StringPrintf("2001:db8::%x", i%65534 + 1);
-            ++mappings_it;
-        }
-    }
-}
-
-bool DnsResponderClient::SetResolversForNetwork(const std::vector<std::string>& servers,
-        const std::vector<std::string>& domains, const std::vector<int>& params) {
-    const auto rv = mNetdSrv->setResolverConfiguration(TEST_NETID, servers, domains, params,
-            "", {}, {});
-    return rv.isOk();
-}
-
-bool DnsResponderClient::SetResolversWithTls(const std::vector<std::string>& servers,
-        const std::vector<std::string>& domains, const std::vector<int>& params,
-        const std::vector<std::string>& tlsServers,
-        const std::string& name, const std::vector<std::string>& fingerprints) {
-    const auto rv = mNetdSrv->setResolverConfiguration(TEST_NETID, servers, domains, params,
-            name, tlsServers, fingerprints);
-    if (!rv.isOk()) ALOGI("SetResolversWithTls() -> %s", rv.toString8().c_str());
-    return rv.isOk();
-}
-
-void DnsResponderClient::SetupDNSServers(unsigned num_servers, const std::vector<Mapping>& mappings,
-        std::vector<std::unique_ptr<test::DNSResponder>>* dns,
-        std::vector<std::string>* servers) {
-    const char* listen_srv = "53";
-    dns->resize(num_servers);
-    servers->resize(num_servers);
-    for (unsigned i = 0 ; i < num_servers ; ++i) {
-        auto& server = (*servers)[i];
-        auto& d = (*dns)[i];
-        server = StringPrintf("127.0.0.%u", i + 100);
-        d = std::make_unique<test::DNSResponder>(server, listen_srv, 250,
-                ns_rcode::ns_r_servfail, 1.0);
-        for (const auto& mapping : mappings) {
-            d->addMapping(mapping.entry.c_str(), ns_type::ns_t_a, mapping.ip4.c_str());
-            d->addMapping(mapping.entry.c_str(), ns_type::ns_t_aaaa, mapping.ip6.c_str());
-        }
-        d->startServer();
-    }
-}
-
-void DnsResponderClient::ShutdownDNSServers(std::vector<std::unique_ptr<test::DNSResponder>>* dns) {
-    for (const auto& d : *dns) {
-        d->stopServer();
-    }
-    dns->clear();
-}
-
-int DnsResponderClient::SetupOemNetwork() {
-    mNetdSrv->networkDestroy(TEST_NETID);
-    auto ret = mNetdSrv->networkCreatePhysical(TEST_NETID, "");
-    if (!ret.isOk()) {
-        fprintf(stderr, "Creating physical network %d failed, %s\n", TEST_NETID,
-                ret.toString8().string());
-        return -1;
-    }
-    int oemNetId = TEST_NETID;
-    setNetworkForProcess(oemNetId);
-    if ((unsigned) oemNetId != getNetworkForProcess()) {
-        return -1;
-    }
-    return oemNetId;
-}
-
-void DnsResponderClient::TearDownOemNetwork(int oemNetId) {
-    if (oemNetId != -1) {
-        mNetdSrv->networkDestroy(oemNetId);
-    }
-}
-
-void DnsResponderClient::SetUp() {
-    // binder setup
-    auto binder = android::defaultServiceManager()->getService(android::String16("netd"));
-    mNetdSrv = android::interface_cast<android::net::INetd>(binder);
-
-    // Ensure resolutions go via proxy.
-    setenv(ANDROID_DNS_MODE, "", 1);
-    mOemNetId = SetupOemNetwork();
-}
-
-void DnsResponderClient::TearDown() {
-    TearDownOemNetwork(mOemNetId);
-}
diff --git a/tests/dns_responder/dns_responder_client.h b/tests/dns_responder/dns_responder_client.h
deleted file mode 100644
index 1981569..0000000
--- a/tests/dns_responder/dns_responder_client.h
+++ /dev/null
@@ -1,73 +0,0 @@
-#ifndef DNS_RESPONDER_CLIENT_H
-#define DNS_RESPONDER_CLIENT_H
-
-#include <cutils/sockets.h>
-
-#include <private/android_filesystem_config.h>
-#include <utils/StrongPointer.h>
-
-#include "android/net/INetd.h"
-#include "binder/IServiceManager.h"
-#include "NetdClient.h"
-#include "dns_responder.h"
-#include "resolv_params.h"
-
-class DnsResponderClient {
-public:
-    struct Mapping {
-        std::string host;
-        std::string entry;
-        std::string ip4;
-        std::string ip6;
-    };
-
-    virtual ~DnsResponderClient() = default;
-
-    static void SetupMappings(unsigned num_hosts, const std::vector<std::string>& domains,
-            std::vector<Mapping>* mappings);
-
-    bool SetResolversForNetwork(const std::vector<std::string>& servers,
-            const std::vector<std::string>& domains, const std::vector<int>& params);
-
-    bool SetResolversForNetwork(const std::vector<std::string>& servers,
-            const std::vector<std::string>& searchDomains,
-            const std::string& params);
-
-    bool SetResolversWithTls(const std::vector<std::string>& servers,
-            const std::vector<std::string>& searchDomains,
-            const std::vector<int>& params,
-            const std::string& name,
-            const std::vector<std::string>& fingerprints) {
-        // Pass servers as both network-assigned and TLS servers.  Tests can
-        // determine on which server and by which protocol queries arrived.
-        return SetResolversWithTls(servers, searchDomains, params,
-                                   servers, name, fingerprints);
-    }
-
-    bool SetResolversWithTls(const std::vector<std::string>& servers,
-            const std::vector<std::string>& searchDomains,
-            const std::vector<int>& params,
-            const std::vector<std::string>& tlsServers,
-            const std::string& name,
-            const std::vector<std::string>& fingerprints);
-
-    static void SetupDNSServers(unsigned num_servers, const std::vector<Mapping>& mappings,
-            std::vector<std::unique_ptr<test::DNSResponder>>* dns,
-            std::vector<std::string>* servers);
-
-    static void ShutdownDNSServers(std::vector<std::unique_ptr<test::DNSResponder>>* dns);
-
-    int SetupOemNetwork();
-
-    void TearDownOemNetwork(int oemNetId);
-
-    virtual void SetUp();
-
-    virtual void TearDown();
-
-public:
-    android::sp<android::net::INetd> mNetdSrv = nullptr;
-    int mOemNetId = -1;
-};
-
-#endif  // DNS_RESPONDER_CLIENT_H
diff --git a/tests/netd_integration_test.cpp b/tests/netd_integration_test.cpp
deleted file mode 100644
index 459521f..0000000
--- a/tests/netd_integration_test.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-// This file currently exists only so we can do:
-// runtest -x system/netd/tests/netd_integration_test.cpp
diff --git a/tests/netd_test.cpp b/tests/netd_test.cpp
index 8d1f6a8..7e14b01 100644
--- a/tests/netd_test.cpp
+++ b/tests/netd_test.cpp
@@ -15,1312 +15,21 @@
  *
  */
 
-#include <arpa/inet.h>
 #include <errno.h>
-#include <netdb.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <cutils/sockets.h>
-#include <android-base/stringprintf.h>
-#include <private/android_filesystem_config.h>
-
-#include <openssl/base64.h>
-
-#include <algorithm>
-#include <chrono>
-#include <iterator>
-#include <numeric>
-#include <thread>
-
-#define LOG_TAG "netd_test"
-// TODO: make this dynamic and stop depending on implementation details.
-#define TEST_NETID 30
-
-#include "resolv_netid.h"
-#include "NetdClient.h"
+#include <sys/capability.h>
 
 #include <gtest/gtest.h>
 
-#include <utils/Log.h>
-
-#include "dns_responder.h"
-#include "dns_responder_client.h"
-#include "dns_tls_frontend.h"
-#include "resolv_params.h"
-#include "ResolverStats.h"
-
-#include "android/net/INetd.h"
-#include "android/net/metrics/INetdEventListener.h"
-#include "binder/IServiceManager.h"
-#include "netdutils/SocketOption.h"
-
-using android::base::StringPrintf;
-using android::base::StringAppendF;
-using android::net::ResolverStats;
-using android::net::metrics::INetdEventListener;
-using android::netdutils::enableSockopt;
-
-// Emulates the behavior of UnorderedElementsAreArray, which currently cannot be used.
-// TODO: Use UnorderedElementsAreArray, which depends on being able to compile libgmock_host,
-// if that is not possible, improve this hacky algorithm, which is O(n**2)
-template <class A, class B>
-bool UnorderedCompareArray(const A& a, const B& b) {
-    if (a.size() != b.size()) return false;
-    for (const auto& a_elem : a) {
-        size_t a_count = 0;
-        for (const auto& a_elem2 : a) {
-            if (a_elem == a_elem2) {
-                ++a_count;
-            }
-        }
-        size_t b_count = 0;
-        for (const auto& b_elem : b) {
-            if (a_elem == b_elem) ++b_count;
-        }
-        if (a_count != b_count) return false;
-    }
-    return true;
+TEST(NetUtilsWrapperTest, TestFileCapabilities) {
+    errno = 0;
+    ASSERT_EQ(NULL, cap_get_file("/system/bin/netutils-wrapper-1.0"));
+    ASSERT_EQ(ENODATA, errno);
 }
 
-class AddrInfo {
-  public:
-    AddrInfo() : ai_(nullptr), error_(0) {}
-
-    AddrInfo(const char* node, const char* service, const addrinfo& hints) : ai_(nullptr) {
-        init(node, service, hints);
-    }
-
-    AddrInfo(const char* node, const char* service) : ai_(nullptr) {
-        init(node, service);
-    }
-
-    ~AddrInfo() { clear(); }
-
-    int init(const char* node, const char* service, const addrinfo& hints) {
-        clear();
-        error_ = getaddrinfo(node, service, &hints, &ai_);
-        return error_;
-    }
-
-    int init(const char* node, const char* service) {
-        clear();
-        error_ = getaddrinfo(node, service, nullptr, &ai_);
-        return error_;
-    }
-
-    void clear() {
-        if (ai_ != nullptr) {
-            freeaddrinfo(ai_);
-            ai_ = nullptr;
-            error_ = 0;
-        }
-    }
-
-    const addrinfo& operator*() const { return *ai_; }
-    const addrinfo* get() const { return ai_; }
-    const addrinfo* operator&() const { return ai_; }
-    int error() const { return error_; }
-
-  private:
-    addrinfo* ai_;
-    int error_;
-};
-
-class ResolverTest : public ::testing::Test, public DnsResponderClient {
-private:
-    int mOriginalMetricsLevel;
-
-protected:
-    virtual void SetUp() {
-        // Ensure resolutions go via proxy.
-        DnsResponderClient::SetUp();
-
-        // If DNS reporting is off: turn it on so we run through everything.
-        auto rv = mNetdSrv->getMetricsReportingLevel(&mOriginalMetricsLevel);
-        ASSERT_TRUE(rv.isOk());
-        if (mOriginalMetricsLevel != INetdEventListener::REPORTING_LEVEL_FULL) {
-            rv = mNetdSrv->setMetricsReportingLevel(INetdEventListener::REPORTING_LEVEL_FULL);
-            ASSERT_TRUE(rv.isOk());
-        }
-    }
-
-    virtual void TearDown() {
-        if (mOriginalMetricsLevel != INetdEventListener::REPORTING_LEVEL_FULL) {
-            auto rv = mNetdSrv->setMetricsReportingLevel(mOriginalMetricsLevel);
-            ASSERT_TRUE(rv.isOk());
-        }
-
-        DnsResponderClient::TearDown();
-    }
-
-    bool GetResolverInfo(std::vector<std::string>* servers, std::vector<std::string>* domains,
-            __res_params* params, std::vector<ResolverStats>* stats) {
-        using android::net::INetd;
-        std::vector<int32_t> params32;
-        std::vector<int32_t> stats32;
-        auto rv = mNetdSrv->getResolverInfo(TEST_NETID, servers, domains, &params32, &stats32);
-        if (!rv.isOk() || params32.size() != INetd::RESOLVER_PARAMS_COUNT) {
-            return false;
-        }
-        *params = __res_params {
-            .sample_validity = static_cast<uint16_t>(
-                    params32[INetd::RESOLVER_PARAMS_SAMPLE_VALIDITY]),
-            .success_threshold = static_cast<uint8_t>(
-                    params32[INetd::RESOLVER_PARAMS_SUCCESS_THRESHOLD]),
-            .min_samples = static_cast<uint8_t>(
-                    params32[INetd::RESOLVER_PARAMS_MIN_SAMPLES]),
-            .max_samples = static_cast<uint8_t>(
-                    params32[INetd::RESOLVER_PARAMS_MAX_SAMPLES])
-        };
-        return ResolverStats::decodeAll(stats32, stats);
-    }
-
-    std::string ToString(const hostent* he) const {
-        if (he == nullptr) return "<null>";
-        char buffer[INET6_ADDRSTRLEN];
-        if (!inet_ntop(he->h_addrtype, he->h_addr_list[0], buffer, sizeof(buffer))) {
-            return "<invalid>";
-        }
-        return buffer;
-    }
-
-    std::string ToString(const addrinfo* ai) const {
-        if (!ai)
-            return "<null>";
-        for (const auto* aip = ai ; aip != nullptr ; aip = aip->ai_next) {
-            char host[NI_MAXHOST];
-            int rv = getnameinfo(aip->ai_addr, aip->ai_addrlen, host, sizeof(host), nullptr, 0,
-                    NI_NUMERICHOST);
-            if (rv != 0)
-                return gai_strerror(rv);
-            return host;
-        }
-        return "<invalid>";
-    }
-
-    size_t GetNumQueries(const test::DNSResponder& dns, const char* name) const {
-        auto queries = dns.queries();
-        size_t found = 0;
-        for (const auto& p : queries) {
-            if (p.first == name) {
-                ++found;
-            }
-        }
-        return found;
-    }
-
-    size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type,
-            const char* name) const {
-        auto queries = dns.queries();
-        size_t found = 0;
-        for (const auto& p : queries) {
-            if (p.second == type && p.first == name) {
-                ++found;
-            }
-        }
-        return found;
-    }
-
-    void RunGetAddrInfoStressTest_Binder(unsigned num_hosts, unsigned num_threads,
-            unsigned num_queries) {
-        std::vector<std::string> domains = { "example.com" };
-        std::vector<std::unique_ptr<test::DNSResponder>> dns;
-        std::vector<std::string> servers;
-        std::vector<DnsResponderClient::Mapping> mappings;
-        ASSERT_NO_FATAL_FAILURE(SetupMappings(num_hosts, domains, &mappings));
-        ASSERT_NO_FATAL_FAILURE(SetupDNSServers(MAXNS, mappings, &dns, &servers));
-
-        ASSERT_TRUE(SetResolversForNetwork(servers, domains, mDefaultParams_Binder));
-
-        auto t0 = std::chrono::steady_clock::now();
-        std::vector<std::thread> threads(num_threads);
-        for (std::thread& thread : threads) {
-           thread = std::thread([this, &mappings, num_queries]() {
-                for (unsigned i = 0 ; i < num_queries ; ++i) {
-                    uint32_t ofs = arc4random_uniform(mappings.size());
-                    auto& mapping = mappings[ofs];
-                    addrinfo* result = nullptr;
-                    int rv = getaddrinfo(mapping.host.c_str(), nullptr, nullptr, &result);
-                    EXPECT_EQ(0, rv) << "error [" << rv << "] " << gai_strerror(rv);
-                    if (rv == 0) {
-                        std::string result_str = ToString(result);
-                        EXPECT_TRUE(result_str == mapping.ip4 || result_str == mapping.ip6)
-                            << "result='" << result_str << "', ip4='" << mapping.ip4
-                            << "', ip6='" << mapping.ip6;
-                    }
-                    if (result) {
-                        freeaddrinfo(result);
-                        result = nullptr;
-                    }
-                }
-            });
-        }
-
-        for (std::thread& thread : threads) {
-            thread.join();
-        }
-        auto t1 = std::chrono::steady_clock::now();
-        ALOGI("%u hosts, %u threads, %u queries, %Es", num_hosts, num_threads, num_queries,
-                std::chrono::duration<double>(t1 - t0).count());
-        ASSERT_NO_FATAL_FAILURE(ShutdownDNSServers(&dns));
-    }
-
-    const std::vector<std::string> mDefaultSearchDomains = { "example.com" };
-    // <sample validity in s> <success threshold in percent> <min samples> <max samples>
-    const std::string mDefaultParams = "300 25 8 8";
-    const std::vector<int> mDefaultParams_Binder = { 300, 25, 8, 8 };
-};
-
-TEST_F(ResolverTest, GetHostByName) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_srv = "53";
-    const char* host_name = "hello.example.com.";
-    const char *nonexistent_host_name = "nonexistent.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-
-    const hostent* result;
-
-    dns.clearQueries();
-    result = gethostbyname("nonexistent");
-    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, nonexistent_host_name));
-    ASSERT_TRUE(result == nullptr);
-    ASSERT_EQ(HOST_NOT_FOUND, h_errno);
-
-    dns.clearQueries();
-    result = gethostbyname("hello");
-    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
-    ASSERT_FALSE(result == nullptr);
-    ASSERT_EQ(4, result->h_length);
-    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
-    EXPECT_EQ("1.2.3.3", ToString(result));
-    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
-
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, TestBinderSerialization) {
-    using android::net::INetd;
-    std::vector<int> params_offsets = {
-        INetd::RESOLVER_PARAMS_SAMPLE_VALIDITY,
-        INetd::RESOLVER_PARAMS_SUCCESS_THRESHOLD,
-        INetd::RESOLVER_PARAMS_MIN_SAMPLES,
-        INetd::RESOLVER_PARAMS_MAX_SAMPLES
-    };
-    int size = static_cast<int>(params_offsets.size());
-    EXPECT_EQ(size, INetd::RESOLVER_PARAMS_COUNT);
-    std::sort(params_offsets.begin(), params_offsets.end());
-    for (int i = 0 ; i < size ; ++i) {
-        EXPECT_EQ(params_offsets[i], i);
-    }
-}
-
-TEST_F(ResolverTest, GetHostByName_Binder) {
-    using android::net::INetd;
-
-    std::vector<std::string> domains = { "example.com" };
-    std::vector<std::unique_ptr<test::DNSResponder>> dns;
-    std::vector<std::string> servers;
-    std::vector<Mapping> mappings;
-    ASSERT_NO_FATAL_FAILURE(SetupMappings(1, domains, &mappings));
-    ASSERT_NO_FATAL_FAILURE(SetupDNSServers(4, mappings, &dns, &servers));
-    ASSERT_EQ(1U, mappings.size());
-    const Mapping& mapping = mappings[0];
-
-    ASSERT_TRUE(SetResolversForNetwork(servers, domains, mDefaultParams_Binder));
-
-    const hostent* result = gethostbyname(mapping.host.c_str());
-    size_t total_queries = std::accumulate(dns.begin(), dns.end(), 0,
-            [this, &mapping](size_t total, auto& d) {
-                return total + GetNumQueriesForType(*d, ns_type::ns_t_a, mapping.entry.c_str());
-            });
-
-    EXPECT_LE(1U, total_queries);
-    ASSERT_FALSE(result == nullptr);
-    ASSERT_EQ(4, result->h_length);
-    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
-    EXPECT_EQ(mapping.ip4, ToString(result));
-    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
-
-    std::vector<std::string> res_servers;
-    std::vector<std::string> res_domains;
-    __res_params res_params;
-    std::vector<ResolverStats> res_stats;
-    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_params, &res_stats));
-    EXPECT_EQ(servers.size(), res_servers.size());
-    EXPECT_EQ(domains.size(), res_domains.size());
-    ASSERT_EQ(INetd::RESOLVER_PARAMS_COUNT, mDefaultParams_Binder.size());
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_SAMPLE_VALIDITY],
-            res_params.sample_validity);
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_SUCCESS_THRESHOLD],
-            res_params.success_threshold);
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_MIN_SAMPLES], res_params.min_samples);
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_MAX_SAMPLES], res_params.max_samples);
-    EXPECT_EQ(servers.size(), res_stats.size());
-
-    EXPECT_TRUE(UnorderedCompareArray(res_servers, servers));
-    EXPECT_TRUE(UnorderedCompareArray(res_domains, domains));
-
-    ASSERT_NO_FATAL_FAILURE(ShutdownDNSServers(&dns));
-}
-
-TEST_F(ResolverTest, GetAddrInfo) {
-    addrinfo* result = nullptr;
-
-    const char* listen_addr = "127.0.0.4";
-    const char* listen_addr2 = "127.0.0.5";
-    const char* listen_srv = "53";
-    const char* host_name = "howdy.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250,
-                           ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
-    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
-    ASSERT_TRUE(dns.startServer());
-
-    test::DNSResponder dns2(listen_addr2, listen_srv, 250,
-                            ns_rcode::ns_r_servfail, 1.0);
-    dns2.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
-    dns2.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
-    ASSERT_TRUE(dns2.startServer());
-
-
-    std::vector<std::string> servers = { listen_addr };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-    dns.clearQueries();
-    dns2.clearQueries();
-
-    EXPECT_EQ(0, getaddrinfo("howdy", nullptr, nullptr, &result));
-    size_t found = GetNumQueries(dns, host_name);
-    EXPECT_LE(1U, found);
-    // Could be A or AAAA
-    std::string result_str = ToString(result);
-    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
-        << ", result_str='" << result_str << "'";
-    // TODO: Use ScopedAddrinfo or similar once it is available in a common header file.
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-
-    // Verify that the name is cached.
-    size_t old_found = found;
-    EXPECT_EQ(0, getaddrinfo("howdy", nullptr, nullptr, &result));
-    found = GetNumQueries(dns, host_name);
-    EXPECT_LE(1U, found);
-    EXPECT_EQ(old_found, found);
-    result_str = ToString(result);
-    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
-        << result_str;
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-
-    // Change the DNS resolver, ensure that queries are still cached.
-    servers = { listen_addr2 };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-    dns.clearQueries();
-    dns2.clearQueries();
-
-    EXPECT_EQ(0, getaddrinfo("howdy", nullptr, nullptr, &result));
-    found = GetNumQueries(dns, host_name);
-    size_t found2 = GetNumQueries(dns2, host_name);
-    EXPECT_EQ(0U, found);
-    EXPECT_LE(0U, found2);
-
-    // Could be A or AAAA
-    result_str = ToString(result);
-    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
-        << ", result_str='" << result_str << "'";
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-
-    dns.stopServer();
-    dns2.stopServer();
-}
-
-TEST_F(ResolverTest, GetAddrInfoV4) {
-    addrinfo* result = nullptr;
-
-    const char* listen_addr = "127.0.0.5";
-    const char* listen_srv = "53";
-    const char* host_name = "hola.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250,
-                           ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.5");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-
-    addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_INET;
-    EXPECT_EQ(0, getaddrinfo("hola", nullptr, &hints, &result));
-    EXPECT_EQ(1U, GetNumQueries(dns, host_name));
-    EXPECT_EQ("1.2.3.5", ToString(result));
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-}
-
-TEST_F(ResolverTest, GetHostByNameBrokenEdns) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_srv = "53";
-    const char* host_name = "edns.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3");
-    dns.setFailOnEdns(true);  // This is the only change from the basic test.
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-
-    const hostent* result;
-
-    dns.clearQueries();
-    result = gethostbyname("edns");
-    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
-    ASSERT_FALSE(result == nullptr);
-    ASSERT_EQ(4, result->h_length);
-    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
-    EXPECT_EQ("1.2.3.3", ToString(result));
-    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
-}
-
-TEST_F(ResolverTest, GetAddrInfoBrokenEdns) {
-    addrinfo* result = nullptr;
-
-    const char* listen_addr = "127.0.0.5";
-    const char* listen_srv = "53";
-    const char* host_name = "edns2.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250,
-                           ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.5");
-    dns.setFailOnEdns(true);  // This is the only change from the basic test.
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-
-    addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_INET;
-    EXPECT_EQ(0, getaddrinfo("edns2", nullptr, &hints, &result));
-    EXPECT_EQ(1U, GetNumQueries(dns, host_name));
-    EXPECT_EQ("1.2.3.5", ToString(result));
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-}
-
-TEST_F(ResolverTest, MultidomainResolution) {
-    std::vector<std::string> searchDomains = { "example1.com", "example2.com", "example3.com" };
-    const char* listen_addr = "127.0.0.6";
-    const char* listen_srv = "53";
-    const char* host_name = "nihao.example2.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250,
-                           ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-    ASSERT_TRUE(SetResolversForNetwork(servers, searchDomains, mDefaultParams_Binder));
-
-    dns.clearQueries();
-    const hostent* result = gethostbyname("nihao");
-    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
-    ASSERT_FALSE(result == nullptr);
-    ASSERT_EQ(4, result->h_length);
-    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
-    EXPECT_EQ("1.2.3.3", ToString(result));
-    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, GetAddrInfoV6_failing) {
-    addrinfo* result = nullptr;
-
-    const char* listen_addr0 = "127.0.0.7";
-    const char* listen_addr1 = "127.0.0.8";
-    const char* listen_srv = "53";
-    const char* host_name = "ohayou.example.com.";
-    test::DNSResponder dns0(listen_addr0, listen_srv, 250,
-                            ns_rcode::ns_r_servfail, 0.0);
-    test::DNSResponder dns1(listen_addr1, listen_srv, 250,
-                            ns_rcode::ns_r_servfail, 1.0);
-    dns0.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::5");
-    dns1.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::6");
-    ASSERT_TRUE(dns0.startServer());
-    ASSERT_TRUE(dns1.startServer());
-    std::vector<std::string> servers = { listen_addr0, listen_addr1 };
-    // <sample validity in s> <success threshold in percent> <min samples> <max samples>
-    int sample_count = 8;
-    const std::vector<int> params = { 300, 25, sample_count, sample_count };
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, params));
-
-    // Repeatedly perform resolutions for non-existing domains until MAXNSSAMPLES resolutions have
-    // reached the dns0, which is set to fail. No more requests should then arrive at that server
-    // for the next sample_lifetime seconds.
-    // TODO: This approach is implementation-dependent, change once metrics reporting is available.
-    addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_INET6;
-    for (int i = 0 ; i < sample_count ; ++i) {
-        std::string domain = StringPrintf("nonexistent%d", i);
-        getaddrinfo(domain.c_str(), nullptr, &hints, &result);
-        if (result) {
-            freeaddrinfo(result);
-            result = nullptr;
-        }
-    }
-    // Due to 100% errors for all possible samples, the server should be ignored from now on and
-    // only the second one used for all following queries, until NSSAMPLE_VALIDITY is reached.
-    dns0.clearQueries();
-    dns1.clearQueries();
-    EXPECT_EQ(0, getaddrinfo("ohayou", nullptr, &hints, &result));
-    EXPECT_EQ(0U, GetNumQueries(dns0, host_name));
-    EXPECT_EQ(1U, GetNumQueries(dns1, host_name));
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-}
-
-TEST_F(ResolverTest, GetAddrInfoV6_concurrent) {
-    const char* listen_addr0 = "127.0.0.9";
-    const char* listen_addr1 = "127.0.0.10";
-    const char* listen_addr2 = "127.0.0.11";
-    const char* listen_srv = "53";
-    const char* host_name = "konbanha.example.com.";
-    test::DNSResponder dns0(listen_addr0, listen_srv, 250,
-                            ns_rcode::ns_r_servfail, 1.0);
-    test::DNSResponder dns1(listen_addr1, listen_srv, 250,
-                            ns_rcode::ns_r_servfail, 1.0);
-    test::DNSResponder dns2(listen_addr2, listen_srv, 250,
-                            ns_rcode::ns_r_servfail, 1.0);
-    dns0.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::5");
-    dns1.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::6");
-    dns2.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::7");
-    ASSERT_TRUE(dns0.startServer());
-    ASSERT_TRUE(dns1.startServer());
-    ASSERT_TRUE(dns2.startServer());
-    const std::vector<std::string> servers = { listen_addr0, listen_addr1, listen_addr2 };
-    std::vector<std::thread> threads(10);
-    for (std::thread& thread : threads) {
-       thread = std::thread([this, &servers]() {
-            unsigned delay = arc4random_uniform(1*1000*1000); // <= 1s
-            usleep(delay);
-            std::vector<std::string> serverSubset;
-            for (const auto& server : servers) {
-                if (arc4random_uniform(2)) {
-                    serverSubset.push_back(server);
-                }
-            }
-            if (serverSubset.empty()) serverSubset = servers;
-            ASSERT_TRUE(SetResolversForNetwork(serverSubset, mDefaultSearchDomains,
-                    mDefaultParams_Binder));
-            addrinfo hints;
-            memset(&hints, 0, sizeof(hints));
-            hints.ai_family = AF_INET6;
-            addrinfo* result = nullptr;
-            int rv = getaddrinfo("konbanha", nullptr, &hints, &result);
-            EXPECT_EQ(0, rv) << "error [" << rv << "] " << gai_strerror(rv);
-            if (result) {
-                freeaddrinfo(result);
-                result = nullptr;
-            }
-        });
-    }
-    for (std::thread& thread : threads) {
-        thread.join();
-    }
-}
-
-TEST_F(ResolverTest, GetAddrInfoStressTest_Binder_100) {
-    const unsigned num_hosts = 100;
-    const unsigned num_threads = 100;
-    const unsigned num_queries = 100;
-    ASSERT_NO_FATAL_FAILURE(RunGetAddrInfoStressTest_Binder(num_hosts, num_threads, num_queries));
-}
-
-TEST_F(ResolverTest, GetAddrInfoStressTest_Binder_100000) {
-    const unsigned num_hosts = 100000;
-    const unsigned num_threads = 100;
-    const unsigned num_queries = 100;
-    ASSERT_NO_FATAL_FAILURE(RunGetAddrInfoStressTest_Binder(num_hosts, num_threads, num_queries));
-}
-
-TEST_F(ResolverTest, EmptySetup) {
-    using android::net::INetd;
-    std::vector<std::string> servers;
-    std::vector<std::string> domains;
-    ASSERT_TRUE(SetResolversForNetwork(servers, domains, mDefaultParams_Binder));
-    std::vector<std::string> res_servers;
-    std::vector<std::string> res_domains;
-    __res_params res_params;
-    std::vector<ResolverStats> res_stats;
-    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_params, &res_stats));
-    EXPECT_EQ(0U, res_servers.size());
-    EXPECT_EQ(0U, res_domains.size());
-    ASSERT_EQ(INetd::RESOLVER_PARAMS_COUNT, mDefaultParams_Binder.size());
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_SAMPLE_VALIDITY],
-            res_params.sample_validity);
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_SUCCESS_THRESHOLD],
-            res_params.success_threshold);
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_MIN_SAMPLES], res_params.min_samples);
-    EXPECT_EQ(mDefaultParams_Binder[INetd::RESOLVER_PARAMS_MAX_SAMPLES], res_params.max_samples);
-}
-
-TEST_F(ResolverTest, SearchPathChange) {
-    addrinfo* result = nullptr;
-
-    const char* listen_addr = "127.0.0.13";
-    const char* listen_srv = "53";
-    const char* host_name1 = "test13.domain1.org.";
-    const char* host_name2 = "test13.domain2.org.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250,
-                           ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name1, ns_type::ns_t_aaaa, "2001:db8::13");
-    dns.addMapping(host_name2, ns_type::ns_t_aaaa, "2001:db8::1:13");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-    std::vector<std::string> domains = { "domain1.org" };
-    ASSERT_TRUE(SetResolversForNetwork(servers, domains, mDefaultParams_Binder));
-
-    addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_INET6;
-    EXPECT_EQ(0, getaddrinfo("test13", nullptr, &hints, &result));
-    EXPECT_EQ(1U, dns.queries().size());
-    EXPECT_EQ(1U, GetNumQueries(dns, host_name1));
-    EXPECT_EQ("2001:db8::13", ToString(result));
-    if (result) freeaddrinfo(result);
-
-    // Test that changing the domain search path on its own works.
-    domains = { "domain2.org" };
-    ASSERT_TRUE(SetResolversForNetwork(servers, domains, mDefaultParams_Binder));
-    dns.clearQueries();
-
-    EXPECT_EQ(0, getaddrinfo("test13", nullptr, &hints, &result));
-    EXPECT_EQ(1U, dns.queries().size());
-    EXPECT_EQ(1U, GetNumQueries(dns, host_name2));
-    EXPECT_EQ("2001:db8::1:13", ToString(result));
-    if (result) freeaddrinfo(result);
-}
-
-TEST_F(ResolverTest, MaxServerPrune_Binder) {
-    using android::net::INetd;
-
-    std::vector<std::string> domains = { "example.com" };
-    std::vector<std::unique_ptr<test::DNSResponder>> dns;
-    std::vector<std::string> servers;
-    std::vector<Mapping> mappings;
-    ASSERT_NO_FATAL_FAILURE(SetupMappings(1, domains, &mappings));
-    ASSERT_NO_FATAL_FAILURE(SetupDNSServers(MAXNS + 1, mappings, &dns, &servers));
-
-    ASSERT_TRUE(SetResolversForNetwork(servers, domains,  mDefaultParams_Binder));
-
-    std::vector<std::string> res_servers;
-    std::vector<std::string> res_domains;
-    __res_params res_params;
-    std::vector<ResolverStats> res_stats;
-    ASSERT_TRUE(GetResolverInfo(&res_servers, &res_domains, &res_params, &res_stats));
-    EXPECT_EQ(static_cast<size_t>(MAXNS), res_servers.size());
-
-    ASSERT_NO_FATAL_FAILURE(ShutdownDNSServers(&dns));
-}
-
-static std::string base64Encode(const std::vector<uint8_t>& input) {
-    size_t out_len;
-    EXPECT_EQ(1, EVP_EncodedLength(&out_len, input.size()));
-    // out_len includes the trailing NULL.
-    uint8_t output_bytes[out_len];
-    EXPECT_EQ(out_len - 1, EVP_EncodeBlock(output_bytes, input.data(), input.size()));
-    return std::string(reinterpret_cast<char*>(output_bytes));
-}
-
-// Test what happens if the specified TLS server is nonexistent.
-TEST_F(ResolverTest, GetHostByName_TlsMissing) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_srv = "53";
-    const char* host_name = "tlsmissing.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    // There's nothing listening on this address, so validation will either fail or
-    /// hang.  Either way, queries will continue to flow to the DNSResponder.
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "", {}));
-
-    const hostent* result;
-
-    result = gethostbyname("tlsmissing");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.3", ToString(result));
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    dns.stopServer();
-}
-
-// Test what happens if the specified TLS server replies with garbage.
-TEST_F(ResolverTest, GetHostByName_TlsBroken) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_srv = "53";
-    const char* host_name1 = "tlsbroken1.example.com.";
-    const char* host_name2 = "tlsbroken2.example.com.";
-    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name1, ns_type::ns_t_a, "1.2.3.1");
-    dns.addMapping(host_name2, ns_type::ns_t_a, "1.2.3.2");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    // Bind the specified private DNS socket but don't respond to any client sockets yet.
-    int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-    ASSERT_TRUE(s >= 0);
-    struct sockaddr_in tlsServer = {
-        .sin_family = AF_INET,
-        .sin_port = htons(853),
-    };
-    ASSERT_TRUE(inet_pton(AF_INET, listen_addr, &tlsServer.sin_addr));
-    enableSockopt(s, SOL_SOCKET, SO_REUSEPORT);
-    enableSockopt(s, SOL_SOCKET, SO_REUSEADDR);
-    ASSERT_FALSE(bind(s, reinterpret_cast<struct sockaddr*>(&tlsServer), sizeof(tlsServer)));
-    ASSERT_FALSE(listen(s, 1));
-
-    // Trigger TLS validation.
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "", {}));
-
-    struct sockaddr_storage cliaddr;
-    socklen_t sin_size = sizeof(cliaddr);
-    int new_fd = accept(s, reinterpret_cast<struct sockaddr *>(&cliaddr), &sin_size);
-    ASSERT_TRUE(new_fd > 0);
-
-    // We've received the new file descriptor but not written to it or closed, so the
-    // validation is still pending.  Queries should still flow correctly because the
-    // server is not used until validation succeeds.
-    const hostent* result;
-    result = gethostbyname("tlsbroken1");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.1", ToString(result));
-
-    // Now we cause the validation to fail.
-    std::string garbage = "definitely not a valid TLS ServerHello";
-    write(new_fd, garbage.data(), garbage.size());
-    close(new_fd);
-
-    // Validation failure shouldn't interfere with lookups, because lookups won't be sent
-    // to the TLS server unless validation succeeds.
-    result = gethostbyname("tlsbroken2");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.2", ToString(result));
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    dns.stopServer();
-    close(s);
-}
-
-TEST_F(ResolverTest, GetHostByName_Tls) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name1 = "tls1.example.com.";
-    const char* host_name2 = "tls2.example.com.";
-    const char* host_name3 = "tls3.example.com.";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name1, ns_type::ns_t_a, "1.2.3.1");
-    dns.addMapping(host_name2, ns_type::ns_t_a, "1.2.3.2");
-    dns.addMapping(host_name3, ns_type::ns_t_a, "1.2.3.3");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-    ASSERT_TRUE(tls.startServer());
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "", {}));
-
-    const hostent* result;
-
-    // Wait for validation to complete.
-    EXPECT_TRUE(tls.waitForQueries(1, 5000));
-
-    result = gethostbyname("tls1");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.1", ToString(result));
-
-    // Wait for query to get counted.
-    EXPECT_TRUE(tls.waitForQueries(2, 5000));
-
-    // Stop the TLS server.  Since we're in opportunistic mode, queries will
-    // fall back to the locally-assigned (clear text) nameservers.
-    tls.stopServer();
-
-    dns.clearQueries();
-    result = gethostbyname("tls2");
-    EXPECT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.2", ToString(result));
-    const auto queries = dns.queries();
-    EXPECT_EQ(1U, queries.size());
-    EXPECT_EQ("tls2.example.com.", queries[0].first);
-    EXPECT_EQ(ns_t_a, queries[0].second);
-
-    // Reset the resolvers without enabling TLS.  Queries should still be routed
-    // to the UDP endpoint.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
-
-    result = gethostbyname("tls3");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.3", ToString(result));
-
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, GetHostByName_TlsFingerprint) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    ASSERT_TRUE(dns.startServer());
-    for (int chain_length = 1; chain_length <= 3; ++chain_length) {
-        const char* host_name = StringPrintf("tlsfingerprint%d.example.com.", chain_length).c_str();
-        dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.1");
-        std::vector<std::string> servers = { listen_addr };
-
-        test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-        tls.set_chain_length(chain_length);
-        ASSERT_TRUE(tls.startServer());
-        ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-                { base64Encode(tls.fingerprint()) }));
-
-        const hostent* result;
-
-        // Wait for validation to complete.
-        EXPECT_TRUE(tls.waitForQueries(1, 5000));
-
-        result = gethostbyname(StringPrintf("tlsfingerprint%d", chain_length).c_str());
-        EXPECT_FALSE(result == nullptr);
-        if (result) {
-            EXPECT_EQ("1.2.3.1", ToString(result));
-
-            // Wait for query to get counted.
-            EXPECT_TRUE(tls.waitForQueries(2, 5000));
-        }
-
-        // Clear TLS bit to ensure revalidation.
-        ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-        tls.stopServer();
-    }
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, GetHostByName_BadTlsFingerprint) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name = "badtlsfingerprint.example.com.";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.1");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-    ASSERT_TRUE(tls.startServer());
-    std::vector<uint8_t> bad_fingerprint = tls.fingerprint();
-    bad_fingerprint[5] += 1;  // Corrupt the fingerprint.
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-            { base64Encode(bad_fingerprint) }));
-
-    // The initial validation should fail at the fingerprint check before
-    // issuing a query.
-    EXPECT_FALSE(tls.waitForQueries(1, 500));
-
-    // A fingerprint was provided and failed to match, so the query should fail.
-    EXPECT_EQ(nullptr, gethostbyname("badtlsfingerprint"));
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    tls.stopServer();
-    dns.stopServer();
-}
-
-// Test that we can pass two different fingerprints, and connection succeeds as long as
-// at least one of them matches the server.
-TEST_F(ResolverTest, GetHostByName_TwoTlsFingerprints) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name = "twotlsfingerprints.example.com.";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.1");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-    ASSERT_TRUE(tls.startServer());
-    std::vector<uint8_t> bad_fingerprint = tls.fingerprint();
-    bad_fingerprint[5] += 1;  // Corrupt the fingerprint.
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-            { base64Encode(bad_fingerprint), base64Encode(tls.fingerprint()) }));
-
-    const hostent* result;
-
-    // Wait for validation to complete.
-    EXPECT_TRUE(tls.waitForQueries(1, 5000));
-
-    result = gethostbyname("twotlsfingerprints");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.1", ToString(result));
-
-    // Wait for query to get counted.
-    EXPECT_TRUE(tls.waitForQueries(2, 5000));
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    tls.stopServer();
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, GetHostByName_TlsFingerprintGoesBad) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name1 = "tlsfingerprintgoesbad1.example.com.";
-    const char* host_name2 = "tlsfingerprintgoesbad2.example.com.";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name1, ns_type::ns_t_a, "1.2.3.1");
-    dns.addMapping(host_name2, ns_type::ns_t_a, "1.2.3.2");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-    ASSERT_TRUE(tls.startServer());
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-            { base64Encode(tls.fingerprint()) }));
-
-    const hostent* result;
-
-    // Wait for validation to complete.
-    EXPECT_TRUE(tls.waitForQueries(1, 5000));
-
-    result = gethostbyname("tlsfingerprintgoesbad1");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.1", ToString(result));
-
-    // Wait for query to get counted.
-    EXPECT_TRUE(tls.waitForQueries(2, 5000));
-
-    // Restart the TLS server.  This will generate a new certificate whose fingerprint
-    // no longer matches the stored fingerprint.
-    tls.stopServer();
-    tls.startServer();
-
-    result = gethostbyname("tlsfingerprintgoesbad2");
-    ASSERT_TRUE(result == nullptr);
-    EXPECT_EQ(HOST_NOT_FOUND, h_errno);
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    tls.stopServer();
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, GetHostByName_TlsFailover) {
-    const char* listen_addr1 = "127.0.0.3";
-    const char* listen_addr2 = "127.0.0.4";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name1 = "tlsfailover1.example.com.";
-    const char* host_name2 = "tlsfailover2.example.com.";
-    test::DNSResponder dns1(listen_addr1, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    test::DNSResponder dns2(listen_addr2, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns1.addMapping(host_name1, ns_type::ns_t_a, "1.2.3.1");
-    dns1.addMapping(host_name2, ns_type::ns_t_a, "1.2.3.2");
-    dns2.addMapping(host_name1, ns_type::ns_t_a, "1.2.3.3");
-    dns2.addMapping(host_name2, ns_type::ns_t_a, "1.2.3.4");
-    ASSERT_TRUE(dns1.startServer());
-    ASSERT_TRUE(dns2.startServer());
-    std::vector<std::string> servers = { listen_addr1, listen_addr2 };
-
-    test::DnsTlsFrontend tls1(listen_addr1, listen_tls, listen_addr1, listen_udp);
-    test::DnsTlsFrontend tls2(listen_addr2, listen_tls, listen_addr2, listen_udp);
-    ASSERT_TRUE(tls1.startServer());
-    ASSERT_TRUE(tls2.startServer());
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-            { base64Encode(tls1.fingerprint()), base64Encode(tls2.fingerprint()) }));
-
-    const hostent* result;
-
-    // Wait for validation to complete.
-    EXPECT_TRUE(tls1.waitForQueries(1, 5000));
-    EXPECT_TRUE(tls2.waitForQueries(1, 5000));
-
-    result = gethostbyname("tlsfailover1");
-    ASSERT_FALSE(result == nullptr);
-    EXPECT_EQ("1.2.3.1", ToString(result));
-
-    // Wait for query to get counted.
-    EXPECT_TRUE(tls1.waitForQueries(2, 5000));
-    // No new queries should have reached tls2.
-    EXPECT_EQ(1, tls2.queries());
-
-    // Stop tls1.  Subsequent queries should attempt to reach tls1, fail, and retry to tls2.
-    tls1.stopServer();
-
-    result = gethostbyname("tlsfailover2");
-    EXPECT_EQ("1.2.3.4", ToString(result));
-
-    // Wait for query to get counted.
-    EXPECT_TRUE(tls2.waitForQueries(2, 5000));
-
-    // No additional queries should have reached the insecure servers.
-    EXPECT_EQ(2U, dns1.queries().size());
-    EXPECT_EQ(2U, dns2.queries().size());
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    tls2.stopServer();
-    dns1.stopServer();
-    dns2.stopServer();
-}
-
-TEST_F(ResolverTest, GetHostByName_BadTlsName) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name = "badtlsname.example.com.";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.1");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-    ASSERT_TRUE(tls.startServer());
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder,
-            "www.example.com", {}));
-
-    // The TLS server's certificate doesn't chain to a known CA, and a nonempty name was specified,
-    // so the client should fail the TLS handshake before ever issuing a query.
-    EXPECT_FALSE(tls.waitForQueries(1, 500));
-
-    // The query should fail hard, because a name was specified.
-    EXPECT_EQ(nullptr, gethostbyname("badtlsname"));
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    tls.stopServer();
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, GetAddrInfo_Tls) {
-    const char* listen_addr = "127.0.0.3";
-    const char* listen_udp = "53";
-    const char* listen_tls = "853";
-    const char* host_name = "addrinfotls.example.com.";
-    test::DNSResponder dns(listen_addr, listen_udp, 250, ns_rcode::ns_r_servfail, 1.0);
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
-    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
-    ASSERT_TRUE(dns.startServer());
-    std::vector<std::string> servers = { listen_addr };
-
-    test::DnsTlsFrontend tls(listen_addr, listen_tls, listen_addr, listen_udp);
-    ASSERT_TRUE(tls.startServer());
-    ASSERT_TRUE(SetResolversWithTls(servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-            { base64Encode(tls.fingerprint()) }));
-
-    // Wait for validation to complete.
-    EXPECT_TRUE(tls.waitForQueries(1, 5000));
-
-    dns.clearQueries();
-    addrinfo* result = nullptr;
-    EXPECT_EQ(0, getaddrinfo("addrinfotls", nullptr, nullptr, &result));
-    size_t found = GetNumQueries(dns, host_name);
-    EXPECT_LE(1U, found);
-    // Could be A or AAAA
-    std::string result_str = ToString(result);
-    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
-        << ", result_str='" << result_str << "'";
-    // TODO: Use ScopedAddrinfo or similar once it is available in a common header file.
-    if (result) {
-        freeaddrinfo(result);
-        result = nullptr;
-    }
-    // Wait for both A and AAAA queries to get counted.
-    EXPECT_TRUE(tls.waitForQueries(3, 5000));
-
-    // Clear TLS bit.
-    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-    tls.stopServer();
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, TlsBypass) {
-    const char OFF[] = "off";
-    const char OPPORTUNISTIC[] = "opportunistic";
-    const char STRICT[] = "strict";
-
-    const char GETHOSTBYNAME[] = "gethostbyname";
-    const char GETADDRINFO[] = "getaddrinfo";
-    const char GETADDRINFOFORNET[] = "getaddrinfofornet";
-
-    const unsigned BYPASS_NETID = NETID_USE_LOCAL_NAMESERVERS | TEST_NETID;
-
-    const std::vector<uint8_t> NOOP_FINGERPRINT(test::SHA256_SIZE, 0U);
-
-    const char ADDR4[] = "192.0.2.1";
-    const char ADDR6[] = "2001:db8::1";
-
-    const char cleartext_addr[] = "127.0.0.53";
-    const char cleartext_port[] = "53";
-    const char tls_port[] = "853";
-    const std::vector<std::string> servers = { cleartext_addr };
-
-    test::DNSResponder dns(cleartext_addr, cleartext_port, 250, ns_rcode::ns_r_servfail, 1.0);
-    ASSERT_TRUE(dns.startServer());
-
-    test::DnsTlsFrontend tls(cleartext_addr, tls_port, cleartext_addr, cleartext_port);
-
-    struct TestConfig {
-        const std::string mode;
-        const bool withWorkingTLS;
-        const std::string method;
-
-        std::string asHostName() const {
-            return StringPrintf("%s.%s.%s.",
-                                mode.c_str(),
-                                withWorkingTLS ? "tlsOn" : "tlsOff",
-                                method.c_str());
-        }
-    } testConfigs[]{
-        {OFF,           false, GETHOSTBYNAME},
-        {OPPORTUNISTIC, false, GETHOSTBYNAME},
-        {STRICT,        false, GETHOSTBYNAME},
-        {OFF,           true,  GETHOSTBYNAME},
-        {OPPORTUNISTIC, true,  GETHOSTBYNAME},
-        {STRICT,        true,  GETHOSTBYNAME},
-        {OFF,           false, GETADDRINFO},
-        {OPPORTUNISTIC, false, GETADDRINFO},
-        {STRICT,        false, GETADDRINFO},
-        {OFF,           true,  GETADDRINFO},
-        {OPPORTUNISTIC, true,  GETADDRINFO},
-        {STRICT,        true,  GETADDRINFO},
-        {OFF,           false, GETADDRINFOFORNET},
-        {OPPORTUNISTIC, false, GETADDRINFOFORNET},
-        {STRICT,        false, GETADDRINFOFORNET},
-        {OFF,           true,  GETADDRINFOFORNET},
-        {OPPORTUNISTIC, true,  GETADDRINFOFORNET},
-        {STRICT,        true,  GETADDRINFOFORNET},
-    };
-
-    for (const auto& config : testConfigs) {
-        const std::string testHostName = config.asHostName();
-        SCOPED_TRACE(testHostName);
-
-        // Don't tempt test bugs due to caching.
-        const char* host_name = testHostName.c_str();
-        dns.addMapping(host_name, ns_type::ns_t_a, ADDR4);
-        dns.addMapping(host_name, ns_type::ns_t_aaaa, ADDR6);
-
-        if (config.withWorkingTLS) ASSERT_TRUE(tls.startServer());
-
-        if (config.mode == OFF) {
-            ASSERT_TRUE(SetResolversForNetwork(
-                    servers, mDefaultSearchDomains,  mDefaultParams_Binder));
-        } else if (config.mode == OPPORTUNISTIC) {
-            ASSERT_TRUE(SetResolversWithTls(
-                    servers, mDefaultSearchDomains, mDefaultParams_Binder, "", {}));
-            // Wait for validation to complete.
-            if (config.withWorkingTLS) EXPECT_TRUE(tls.waitForQueries(1, 5000));
-        } else if (config.mode == STRICT) {
-            // We use the existence of fingerprints to trigger strict mode,
-            // rather than hostname validation.
-            const auto& fingerprint =
-                    (config.withWorkingTLS) ? tls.fingerprint() : NOOP_FINGERPRINT;
-            ASSERT_TRUE(SetResolversWithTls(
-                    servers, mDefaultSearchDomains, mDefaultParams_Binder, "",
-                    { base64Encode(fingerprint) }));
-            // Wait for validation to complete.
-            if (config.withWorkingTLS) EXPECT_TRUE(tls.waitForQueries(1, 5000));
-        } else {
-            FAIL() << "Unsupported Private DNS mode: " << config.mode;
-        }
-
-        const int tlsQueriesBefore = tls.queries();
-
-        const hostent* h_result = nullptr;
-        addrinfo* ai_result = nullptr;
-
-        if (config.method == GETHOSTBYNAME) {
-            ASSERT_EQ(0, setNetworkForResolv(BYPASS_NETID));
-            h_result = gethostbyname(host_name);
-
-            EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
-            ASSERT_FALSE(h_result == nullptr);
-            ASSERT_EQ(4, h_result->h_length);
-            ASSERT_FALSE(h_result->h_addr_list[0] == nullptr);
-            EXPECT_EQ(ADDR4, ToString(h_result));
-            EXPECT_TRUE(h_result->h_addr_list[1] == nullptr);
-        } else if (config.method == GETADDRINFO) {
-            ASSERT_EQ(0, setNetworkForResolv(BYPASS_NETID));
-            EXPECT_EQ(0, getaddrinfo(host_name, nullptr, nullptr, &ai_result));
-
-            EXPECT_LE(1U, GetNumQueries(dns, host_name));
-            // Could be A or AAAA
-            const std::string result_str = ToString(ai_result);
-            EXPECT_TRUE(result_str == ADDR4 || result_str == ADDR6)
-                << ", result_str='" << result_str << "'";
-        } else if (config.method == GETADDRINFOFORNET) {
-            EXPECT_EQ(0, android_getaddrinfofornet(
-                    host_name, nullptr, nullptr, BYPASS_NETID, MARK_UNSET, &ai_result));
-
-            EXPECT_LE(1U, GetNumQueries(dns, host_name));
-            // Could be A or AAAA
-            const std::string result_str = ToString(ai_result);
-            EXPECT_TRUE(result_str == ADDR4 || result_str == ADDR6)
-                << ", result_str='" << result_str << "'";
-        } else {
-            FAIL() << "Unsupported query method: " << config.method;
-        }
-
-        const int tlsQueriesAfter = tls.queries();
-        EXPECT_EQ(0, tlsQueriesAfter - tlsQueriesBefore);
-
-        // TODO: Use ScopedAddrinfo or similar once it is available in a common header file.
-        if (ai_result != nullptr) freeaddrinfo(ai_result);
-
-        // Clear per-process resolv netid.
-        ASSERT_EQ(0, setNetworkForResolv(NETID_UNSET));
-        tls.stopServer();
-        dns.clearQueries();
-    }
-
-    dns.stopServer();
-}
-
-TEST_F(ResolverTest, StrictMode_NoTlsServers) {
-    const std::vector<uint8_t> NOOP_FINGERPRINT(test::SHA256_SIZE, 0U);
-    const char cleartext_addr[] = "127.0.0.53";
-    const char cleartext_port[] = "53";
-    const std::vector<std::string> servers = { cleartext_addr };
-
-    test::DNSResponder dns(cleartext_addr, cleartext_port, 250, ns_rcode::ns_r_servfail, 1.0);
-    const char* host_name = "strictmode.notlsips.example.com.";
-    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
-    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
-    ASSERT_TRUE(dns.startServer());
-
-    ASSERT_TRUE(SetResolversWithTls(
-            servers, mDefaultSearchDomains, mDefaultParams_Binder,
-            {}, "", { base64Encode(NOOP_FINGERPRINT) }));
-
-    addrinfo* ai_result = nullptr;
-    EXPECT_NE(0, getaddrinfo(host_name, nullptr, nullptr, &ai_result));
-    EXPECT_EQ(0U, GetNumQueries(dns, host_name));
+TEST(NetdSELinuxTest, CheckProperMTULabels) {
+    // Since we expect the egrep regexp to filter everything out,
+    // we thus expect no matches and thus a return code of 1
+    // NOLINTNEXTLINE(cert-env33-c)
+    ASSERT_EQ(W_EXITCODE(1, 0), system("ls -Z /sys/class/net/*/mtu | egrep -q -v "
+                                       "'^u:object_r:sysfs_net:s0 /sys/class/net/'"));
 }
diff --git a/tests/netlink_listener_test.cpp b/tests/netlink_listener_test.cpp
new file mode 100644
index 0000000..b39182c
--- /dev/null
+++ b/tests/netlink_listener_test.cpp
@@ -0,0 +1,157 @@
+//
+// 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 <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <linux/inet_diag.h>
+#include <linux/netlink.h>
+#include <linux/sock_diag.h>
+#include <linux/unistd.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <gtest/gtest.h>
+
+#include <cutils/qtaguid.h>
+
+#include <netdutils/Misc.h>
+#include <netdutils/Syscalls.h>
+#include "NetlinkListener.h"
+#include "TrafficController.h"
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+#include "netdutils/Netlink.h"
+
+// A test uid that is large enough so normal apps are not likely to take,
+constexpr uid_t TEST_UID = UID_MAX - 2;
+// A test tag arbitrarily selected.
+constexpr uint32_t TEST_TAG = 0xFF0F0F0F;
+
+constexpr uint32_t SOCK_CLOSE_WAIT_US = 20 * 1000;
+constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
+
+using android::netdutils::Status;
+using android::netdutils::statusFromErrno;
+
+// This test set up a SkDestroyListener that is runing parallel with the production
+// SkDestroyListener. The test will create thousands of sockets and tag them on the
+// production cookieUidTagMap and close them in a short time. When the number of
+// sockets get closed exceeds the buffer size, it will start to return ENOBUFF
+// error. The error will be ignored by the production SkDestroyListener and the
+// test will clean up the tags in tearDown if there is any remains.
+
+// TODO: Instead of test the ENOBUFF error, we can test the production
+// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF
+// triggerred.
+class NetlinkListenerTest : public testing::Test {
+  protected:
+    NetlinkListenerTest() {}
+    BpfMap<uint64_t, UidTag> mCookieTagMap;
+
+    void SetUp() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+
+        mCookieTagMap.reset(android::bpf::mapRetrieve(COOKIE_TAG_MAP_PATH, 0));
+        ASSERT_TRUE(mCookieTagMap.isValid());
+    }
+
+    void TearDown() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+
+        const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTag& value,
+                                                BpfMap<uint64_t, UidTag>& map) {
+            if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
+                Status res = map.deleteValue(key);
+                if (isOk(res) || (res.code() == ENOENT)) {
+                    return android::netdutils::status::ok;
+                }
+                ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+                      strerror(res.code()));
+            }
+            // Move forward to next cookie in the map.
+            return android::netdutils::status::ok;
+        };
+        EXPECT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries));
+    }
+
+    Status checkNoGarbageTagsExist() {
+        const auto checkGarbageTags = [](const uint64_t&, const UidTag& value,
+                                         const BpfMap<uint64_t, UidTag>&) {
+            if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) {
+                return statusFromErrno(EUCLEAN, "Closed socket is not untagged");
+            }
+            return android::netdutils::status::ok;
+        };
+        return mCookieTagMap.iterateWithValue(checkGarbageTags);
+    }
+
+    void checkMassiveSocketDestroy(const int totalNumber, bool expectError) {
+        std::unique_ptr<android::net::NetlinkListenerInterface> skDestroyListener;
+        auto result = android::net::TrafficController::makeSkDestroyListener();
+        if (!isOk(result)) {
+            ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
+        } else {
+            skDestroyListener = std::move(result.value());
+        }
+        int rxErrorCount = 0;
+        // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+        const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; };
+        skDestroyListener->registerSkErrorHandler(rxErrorHandler);
+        int fds[totalNumber];
+        for (int i = 0; i < totalNumber; i++) {
+            fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+            EXPECT_LE(0, fds[i]);
+            qtaguid_tagSocket(fds[i], TEST_TAG, TEST_UID);
+        }
+
+        // TODO: Use a separate thread that have it's own fd table so we can
+        // close socket faster by terminating that threads.
+        for (int i = 0; i < totalNumber; i++) {
+            EXPECT_EQ(0, close(fds[i]));
+        }
+        // wait a bit for netlink listner to handle all the messages.
+        usleep(SOCK_CLOSE_WAIT_US);
+        if (expectError) {
+            // If ENOBUFS triggered, check it only called into the handler once, ie.
+            // that the netlink handler is not spinning.
+            int currentErrorCount = rxErrorCount;
+            EXPECT_LT(0, rxErrorCount);
+            usleep(ENOBUFS_POLL_WAIT_US);
+            EXPECT_EQ(currentErrorCount, rxErrorCount);
+        } else {
+            EXPECT_TRUE(isOk(checkNoGarbageTagsExist()));
+            EXPECT_EQ(0, rxErrorCount);
+        }
+    }
+};
+
+TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    checkMassiveSocketDestroy(10, false);
+    checkMassiveSocketDestroy(100, false);
+}
+
+TEST_F(NetlinkListenerTest, TestSkDestroyError) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    checkMassiveSocketDestroy(20000, true);
+}
diff --git a/tests/runtests.sh b/tests/runtests.sh
index c1a6b70..6f828fc 100755
--- a/tests/runtests.sh
+++ b/tests/runtests.sh
@@ -1,16 +1,24 @@
 #!/usr/bin/env bash
 
-readonly PROJECT_TOP="system/netd"
+set -e
+set -u
 
-# TODO:
-#   - add Android.bp test targets
-#   - switch away from runtest.py
-readonly ALL_TESTS="
-    server/netd_unit_test.cpp
-    tests/netd_integration_test.cpp
+readonly DEFAULT_TESTS="
+    netdutils_test
+    netd_unit_test
+    netd_integration_test
+    resolv_integration_test
+    resolv_unit_test
 "
 
+readonly EXTENDED_TESTS="
+    netd_benchmark
+"
+
+readonly TEST_DEVICE_PATH="/data/local/tmp"
+
 REPO_TOP=""
+SOONG_BIN=""
 DEBUG=""
 
 function logToStdErr() {
@@ -20,6 +28,7 @@
 function testAndSetRepoTop() {
     if [[ -n "$1" && -d "$1/.repo" ]]; then
         REPO_TOP="$1"
+        SOONG_BIN="$REPO_TOP/build/soong/soong_ui.bash"
         return 0
     fi
     return 1
@@ -38,54 +47,113 @@
     done
 }
 
-function runOneTest() {
-    local testName="$1"
-    local cmd="$REPO_TOP/development/testrunner/runtest.py -x $PROJECT_TOP/$testName"
-    echo "###"
-    echo "# $testName"
+function runCmd() {
+    local cmd="$@"
     echo "#"
     echo "# $cmd"
-    echo "###"
-    echo ""
     $DEBUG $cmd
-    local rval=$?
-    echo ""
+    return $?
+}
 
-    # NOTE: currently runtest.py returns 0 even for failed tests.
-    return $rval
+function runOneTest() {
+    local test="$1"
+    echo
+    echo "###"
+    echo "# $test"
+    echo "###"
+
+    local prefix="$ANDROID_TARGET_OUT_TESTCASES/$test/$TARGET_ARCH/$test"
+    for bits in '' 32 64; do
+            local testbin="${prefix}${bits}"
+            if [ -f "$testbin" ]; then
+                runCmd adb push "$testbin" "$TEST_DEVICE_PATH/$test" &&
+                    runCmd adb shell "$TEST_DEVICE_PATH/$test" &&
+                    runCmd adb shell "rm $TEST_DEVICE_PATH/$test"
+                return $?
+            fi
+    done
+
+    logToStdErr "Couldn't find test binary '$prefix'"
+    return 1
 }
 
 function main() {
+    if [ ! -v ANDROID_BUILD_TOP ]; then
+        logToStdErr "You need to source and lunch before you can use this script"
+        return 1
+    fi
     gotoRepoTop
     if ! testAndSetRepoTop "$REPO_TOP"; then
         logToStdErr "Could not find useful top of repo directory"
         return 1
     fi
-    logToStdErr "using REPO_TOP=$REPO_TOP"
+    logToStdErr "Using REPO_TOP=$REPO_TOP"
 
-    if [[ -n "$1" ]]; then
+    TARGET_ARCH=$("$SOONG_BIN" --dumpvar-mode TARGET_ARCH)
+    if [ -z "$TARGET_ARCH" ]; then
+        logToStdErr "Could not determine TARGET_ARCH"
+        return 1
+    fi
+
+    local test_regex=""
+    while [[ $# -gt 0 ]]; do
         case "$1" in
             "-n")
                 DEBUG=echo
                 shift
                 ;;
+            *)
+                # Allow us to do things like "runtests.sh integration", etc.
+                test_regex="$1"
+                shift
+                ;;
         esac
-    fi
+    done
 
-    # Allow us to do things like "runtests.sh integration", etc.
-    readonly TEST_REGEX="$1"
+    # Try becoming root, in case the tests will be run on a device
+    # with a userdebug build. Failure to become root is not fatal
+    # for all (in fact, not even most) tests so don't exit on error.
+    runCmd adb root || logToStdErr "WARNING: unable to 'adb root'"
 
-    failures=0
-    for testName in $ALL_TESTS; do
-        if [[ -z "$TEST_REGEX" || "$testName" =~ "$TEST_REGEX" ]]; then
-            runOneTest "$testName"
-            let failures+=$?
+    local failures=0
+    local skipped=0
+
+    # Find out which tests to run.
+    local tests=""
+    for testName in $DEFAULT_TESTS; do
+        if [[ -z "$test_regex" || "$testName" =~ "$test_regex" ]]; then
+            tests="$tests $testName"
         else
             logToStdErr "Skipping $testName"
+            skipped=$((skipped + 1))
         fi
     done
 
+    # If something has been specified, also check the extended tests for
+    # a possible match (i.e. in order to run benchmarks, etc).
+    if [[ -n "$test_regex" ]]; then
+        for testName in $EXTENDED_TESTS; do
+            if [[ "$testName" =~ "$test_regex" ]]; then
+                tests="$tests $testName"
+            fi
+        done
+    fi
+
+    if [[ -z "$tests" ]]; then
+        logToStdErr "No tests matched"
+        return 1
+    fi
+
+    # Build all the specified tests.
+    runCmd "$SOONG_BIN" --make-mode "$tests" || return $?
+
+    # Run all the specified tests.
+    for testName in $tests; do
+        runOneTest "$testName" || failures=$((failures + 1))
+    done
+
     echo "Number of tests failing: $failures"
+    [[ $skipped -gt 0 ]] && echo "Number of tests skipped: $skipped"
     return $failures
 }
 
diff --git a/tests/tun_interface.cpp b/tests/tun_interface.cpp
index 3d4ab23..65f1528 100644
--- a/tests/tun_interface.cpp
+++ b/tests/tun_interface.cpp
@@ -16,21 +16,26 @@
  * tun_interface.cpp - creates tun interfaces for testing purposes
  */
 
+#include <string>
+
 #include <fcntl.h>
-#include <netdb.h>
-#include <stdlib.h>
-#include <unistd.h>
 #include <linux/if.h>
 #include <linux/if_tun.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
 #include <net/if.h>
+#include <netdb.h>
 #include <netinet/in.h>
+#include <stdlib.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <netutils/ifc.h>
 
 #include "tun_interface.h"
@@ -38,11 +43,12 @@
 #define TUN_DEV "/dev/tun"
 
 using android::base::StringPrintf;
+using android::base::unique_fd;
 
 namespace android {
 namespace net {
 
-int TunInterface::init() {
+int TunInterface::init(const std::string& ifName) {
     // Generate a random ULA address pair.
     arc4random_buf(&mSrcAddr, sizeof(mSrcAddr));
     mSrcAddr.s6_addr[0] = 0xfd;
@@ -54,17 +60,26 @@
     sockaddr_in6 src6 = { .sin6_family = AF_INET6, .sin6_addr = mSrcAddr, };
     sockaddr_in6 dst6 = { .sin6_family = AF_INET6, .sin6_addr = mDstAddr, };
     int flags = NI_NUMERICHOST;
-    if (getnameinfo((sockaddr *) &src6, sizeof(src6), srcStr, sizeof(srcStr), NULL, 0, flags) ||
-        getnameinfo((sockaddr *) &dst6, sizeof(dst6), dstStr, sizeof(dstStr), NULL, 0, flags)) {
+    if (getnameinfo((sockaddr *) &src6, sizeof(src6), srcStr, sizeof(srcStr), nullptr, 0, flags) ||
+        getnameinfo((sockaddr *) &dst6, sizeof(dst6), dstStr, sizeof(dstStr), nullptr, 0, flags)) {
         return -EINVAL;
     }
 
-    // Create a tun interface with a name based on our PID and some randomness.
-    // iptables will only accept interfaces whose name is up to IFNAMSIZ - 1 bytes long.
-    mIfName = StringPrintf("netd%u_%u", getpid(), arc4random());
-    if (mIfName.size() >= IFNAMSIZ) {
-        mIfName.resize(IFNAMSIZ - 1);
+    // Create a tun interface with a name based on a random number.
+    // In order to fit the size of interface alert name , resize ifname to 9
+    // Alert name format in netd: ("%sAlert", ifname)
+    // Limitation in kernel: char name[15] in struct xt_quota_mtinfo2
+
+    // Note that this form of alert doesn't actually appear to be used for interface alerts.
+    // It can only be created by BandwidthController::setInterfaceAlert, but that appears to have no
+    // actual callers in the framework, because mActiveAlerts is always empty.
+    // TODO: remove setInterfaceAlert and use a longer interface name.
+    mIfName = ifName;
+    if (mIfName.empty()) {
+        mIfName = StringPrintf("netd%x", arc4random());
     }
+    mIfName.resize(9);
+
     struct ifreq ifr = {
         .ifr_ifru = { .ifru_flags = IFF_TUN },
     };
@@ -80,24 +95,93 @@
         return ret;
     }
 
-    if (ifc_add_address(ifr.ifr_name, srcStr, 64) ||
-        ifc_add_address(ifr.ifr_name, dstStr, 64)) {
+    mIfIndex = if_nametoindex(ifr.ifr_name);
+
+    if (addAddress(srcStr, 64) || addAddress(dstStr, 64)) {
         ret = -errno;
         close(mFd);
         return ret;
     }
 
-    mIfIndex = if_nametoindex(ifr.ifr_name);
-
+    if (int ret = ifc_enable(ifr.ifr_name)) {
+        return ret;
+    }
     return 0;
 }
 
 void TunInterface::destroy() {
     if (mFd != -1) {
+        ifc_disable(mIfName.c_str());
         close(mFd);
         mFd = -1;
     }
 }
 
+int TunInterface::addAddress(const std::string& addr, int prefixlen) {
+    // Wait for an RTM_NEWADDR indicating that the address has been created.
+    // This is because IPv6 addresses, even addresses that are optimistic or created with
+    // IFA_F_NODAD, are not immediately usable when the netlink ACK returns.
+    // This is not generally necessary in device code because the framework hears about IP addresses
+    // asynchronously via netlink, but it is necessary to ensure tests aren't flaky.
+    unique_fd s(socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+    if (s == -1) return -errno;
+
+    sockaddr_nl groups = {.nl_family = AF_NETLINK,
+                          .nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR};
+    if (bind(s, reinterpret_cast<sockaddr*>(&groups), sizeof(groups)) == -1) return -errno;
+
+    sockaddr_nl kernel = {.nl_family = AF_NETLINK};
+    if (connect(s, reinterpret_cast<sockaddr*>(&kernel), sizeof(kernel)) == -1) return -errno;
+
+    // Wait up to 200ms for address to arrive.
+    timeval timeout = {.tv_usec = 200 * 1000};
+    if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) return -errno;
+
+    if (ifc_add_address(mIfName.c_str(), addr.c_str(), prefixlen)) return -errno;
+
+    int family;
+    size_t addrlen;
+    union {
+        in_addr ip4;
+        in6_addr ip6;
+    } ip;
+    if (addr.find(':') != std::string::npos) {
+        family = AF_INET6;
+        inet_pton(AF_INET6, addr.c_str(), &ip.ip6);
+        addrlen = sizeof(ip.ip6);
+    } else {
+        family = AF_INET;
+        inet_pton(AF_INET, addr.c_str(), &ip.ip4);
+        addrlen = sizeof(ip.ip4);
+    }
+
+    while (1) {
+        char buf[4096];
+        ssize_t len = recv(s, buf, sizeof(buf), 0);
+
+        if (len == -1) break;
+        if (len < static_cast<ssize_t>(NLMSG_SPACE(sizeof(ifaddrmsg)))) continue;
+
+        nlmsghdr* nlmsg = reinterpret_cast<nlmsghdr*>(buf);
+        if (nlmsg->nlmsg_type != RTM_NEWADDR) continue;
+
+        ifaddrmsg* ifaddr = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(nlmsg));
+        if (ifaddr->ifa_family != family) continue;
+        if (ifaddr->ifa_prefixlen != prefixlen) continue;
+        if (ifaddr->ifa_index != static_cast<uint32_t>(mIfIndex)) continue;
+
+        int ifalen = IFA_PAYLOAD(nlmsg);
+        for (rtattr* rta = IFA_RTA(ifaddr); RTA_OK(rta, ifalen); rta = RTA_NEXT(rta, ifalen)) {
+            if (rta->rta_type != IFA_LOCAL && rta->rta_type != IFA_ADDRESS) continue;
+            if (RTA_PAYLOAD(rta) != addrlen) continue;
+            if (!memcmp(RTA_DATA(rta), &ip, addrlen)) {
+                return 0;
+            }
+        }
+    }
+
+    return -errno;
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/tests/tun_interface.h b/tests/tun_interface.h
index ad5070b..0af43b1 100644
--- a/tests/tun_interface.h
+++ b/tests/tun_interface.h
@@ -29,7 +29,7 @@
 
     // Creates a tun interface. Returns 0 on success or -errno on failure. Must succeed before it is
     // legal to call any of the other methods in this class.
-    int init();
+    int init(const std::string& ifName = "");
     void destroy();
 
     const std::string& name() const { return mIfName; }
@@ -37,7 +37,10 @@
     const in6_addr& srcAddr() const { return mSrcAddr; }
     const in6_addr& dstAddr() const { return mDstAddr; }
 
-private:
+    int addAddress(const std::string& addr, int prefixlen);
+    int getFdForTesting() const { return mFd; }
+
+  private:
     int mFd = -1;
     std::string mIfName;
     int mIfIndex;