Add asynchronous DNS query API and basic tests.

Adds support for asynchronous "raw" DNS API for clients.
API allows apps to use multinetworking capability correctly
and also allows other query types than A/AAAA.

Test: built, flashed, booted/ runtest.sh
Change-Id: I98931cd561821cd396f8421e9419a289b371468a
diff --git a/client/Android.bp b/client/Android.bp
index 3640837..db3ac69 100644
--- a/client/Android.bp
+++ b/client/Android.bp
@@ -18,7 +18,10 @@
         "FwmarkClient.cpp",
         "NetdClient.cpp",
     ],
-    header_libs: ["libnetd_client_headers"],
+    header_libs: [
+        "libnetd_client_headers",
+        "libbase_headers",
+    ],
     export_header_lib_headers: ["libnetd_client_headers"],
     include_dirs: [
         "system/netd/resolv",
diff --git a/client/NetdClient.cpp b/client/NetdClient.cpp
index 4497877..de9b0f5 100644
--- a/client/NetdClient.cpp
+++ b/client/NetdClient.cpp
@@ -19,12 +19,15 @@
 #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 "Fwmark.h"
 #include "FwmarkClient.h"
@@ -32,8 +35,15 @@
 #include "Stopwatch.h"
 #include "netid_client.h"
 
+#include "android-base/unique_fd.h"
+
+using android::base::unique_fd;
+
 namespace {
 
+// Keep this in sync with CMD_BUF_SIZE in FrameworkListener.cpp.
+constexpr size_t MAX_CMD_SIZE = 1024;
+
 std::atomic_uint netIdForProcess(NETID_UNSET);
 std::atomic_uint netIdForResolv(NETID_UNSET);
 
@@ -206,6 +216,59 @@
     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;
+    int n = TEMP_FAILURE_RETRY(read(fd, &tmp, sizeof(tmp)));
+    if (n < 0) {
+        return false;
+    }
+    *result = ntohl(tmp);
+    return true;
+}
+
 }  // namespace
 
 #define CHECK_SOCKET_IS_MARKABLE(sock)          \
@@ -321,3 +384,80 @@
     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) {
+    u_char buf[MAX_CMD_SIZE] = {};
+    int len = res_mkquery(QUERY, dname, ns_class, ns_type, NULL, 0, NULL, buf, sizeof(buf));
+
+    return resNetworkSend(netId, buf, len);
+}
+
+extern "C" int resNetworkSend(unsigned netId, const uint8_t* msg, int msglen) {
+    // 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;
+    auto encodedQuery = std::string(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 " + encodedQuery + " " + std::to_string(netId) + '\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, int 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 < 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);
+}
\ No newline at end of file
diff --git a/include/NetdClient.h b/include/NetdClient.h
index 2c6edd0..c2c7148 100644
--- a/include/NetdClient.h
+++ b/include/NetdClient.h
@@ -48,6 +48,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 classType, int type);
+
+int resNetworkResult(int query_fd, int* rcode, uint8_t* answer, int anslen);
+
+int resNetworkSend(unsigned netId, const uint8_t* msg, int msglen);
+
+int resNetworkRecv(int send_fd, int* rcode, uint8_t* answer, int anslen);
+
+void resNetworkCancel(int nsend_fd);
 __END_DECLS
 
 #endif  // NETD_INCLUDE_NETD_CLIENT_H
diff --git a/resolv/resolver_test.cpp b/resolv/resolver_test.cpp
index ba224cc..bab7e22 100644
--- a/resolv/resolver_test.cpp
+++ b/resolv/resolver_test.cpp
@@ -18,10 +18,16 @@
 #define LOG_TAG "netd_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>
@@ -54,7 +60,8 @@
 #include "netdutils/SocketOption.h"
 
 // TODO: make this dynamic and stop depending on implementation details.
-#define TEST_NETID 30
+constexpr int TEST_NETID = 30;
+constexpr int MAXPACKET = (8 * 1024);
 
 // Semi-public Bionic hook used by the NDK (frameworks/base/native/android/net.c)
 // Tested here for convenience.
@@ -1557,3 +1564,271 @@
     EXPECT_NE(0, getaddrinfo(host_name, nullptr, nullptr, &ai_result));
     EXPECT_EQ(0U, GetNumQueries(dns, host_name));
 }
+
+namespace {
+
+int getAsyncResponse(int fd, int* rcode, u_char* 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);
+        return n;
+    }
+    return -1;
+}
+
+std::string toString(u_char* 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 u_char* 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;
+}
+
+}  // namespace
+
+TEST_F(ResolverTest, Async_NormalQueryV4V6) {
+    const char listen_addr[] = "127.0.0.4";
+    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);
+    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};
+    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
+    dns.clearQueries();
+
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1);   // Type A       1
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 28);  // Type AAAA    28
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    u_char 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", 1, 1);   // Type A       1
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 28);  // Type AAAA    28
+
+    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) {
+    const char listen_addr[] = "127.0.0.4";
+    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);
+    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};
+    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
+    dns.clearQueries();
+
+    static struct {
+        int fd;
+        const char* dname;
+        const int queryType;
+        const int expectRcode;
+    } kTestData[] = {
+            {-1, "", T_AAAA, 0},
+            {-1, "as65ass46", T_AAAA, 0},
+            {-1, "454564564564", T_AAAA, 0},
+            {-1, "h645235", T_A, 0},
+            {-1, "www.google.com", T_A, 0},
+    };
+
+    for (auto& td : kTestData) {
+        SCOPED_TRACE(td.dname);
+        td.fd = resNetworkQuery(TEST_NETID, td.dname, 1, td.queryType);
+        EXPECT_TRUE(td.fd != -1);
+    }
+
+    // dns_responder return empty resp(packet only contains query part) with no error currently
+    for (const auto& td : kTestData) {
+        u_char 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) {
+    const char listen_addr[] = "127.0.0.4";
+    const char listen_srv[] = "53";
+    const char host_name[] = "howdy.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, static_cast<ns_rcode>(-1));
+    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};
+    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
+    dns.clearQueries();
+
+    // A 1  AAAA 28
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 28);
+    EXPECT_TRUE(fd1 != -1);
+
+    // make sure setResponseProbability effective
+    dns.stopServer();
+    dns.setResponseProbability(0.0);
+    ASSERT_TRUE(dns.startServer());
+
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1);
+    EXPECT_TRUE(fd2 != -1);
+
+    int fd3 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1);
+    EXPECT_TRUE(fd3 != -1);
+
+    u_char buf[MAXPACKET] = {};
+    int rcode;
+
+    // expect no response, ETIMEDOUT = 110
+    int res = getAsyncResponse(fd2, &rcode, buf, MAXPACKET);
+    EXPECT_EQ(res, -110);
+
+    // expect no response, ETIMEDOUT = 110
+    memset(buf, 0, MAXPACKET);
+    res = getAsyncResponse(fd3, &rcode, buf, MAXPACKET);
+    EXPECT_EQ(res, -110);
+
+    // make sure setResponseProbability effective
+    dns.stopServer();
+    dns.setResponseProbability(1.0);
+    ASSERT_TRUE(dns.startServer());
+
+    int fd4 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1);
+    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) {
+    const char listen_addr[] = "127.0.0.4";
+    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);
+    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};
+    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
+    dns.clearQueries();
+
+    int fd = dns_open_proxy();
+    EXPECT_TRUE(fd > 0);
+
+    const std::string badMsg = "16-52512#";
+    static struct {
+        const std::string cmd;
+        const int expectErr;
+    } kTestData[] = {
+            // Less arguement
+            {"resnsend " + badMsg + '\0', -EINVAL},
+            // Bad netId
+            {"resnsend " + badMsg + " badnetId" + '\0', -EINVAL},
+            // Bad raw data
+            {"resnsend " + badMsg + " " + std::to_string(TEST_NETID) + '\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 " + query + " " + std::to_string(TEST_NETID) + '\0';
+    ssize_t rc = TEMP_FAILURE_RETRY(write(fd, cmd.c_str(), cmd.size()));
+    EXPECT_EQ(rc, static_cast<ssize_t>(cmd.size()));
+
+    u_char smallBuf[1] = {};
+    int rcode;
+    rc = getAsyncResponse(fd, &rcode, smallBuf, 1);
+    EXPECT_EQ(rc, -EMSGSIZE);
+
+    // 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()));
+    u_char buf[MAXPACKET] = {};
+    rc = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_EQ("1.2.3.4", toString(buf, rc, AF_INET));
+}
\ No newline at end of file