Add test for DNS resolver code in netd.

Change-Id: I92466868ae32ee67fb5d17c7758a7841f614e827
diff --git a/tests/netd_test.cpp b/tests/netd_test.cpp
new file mode 100644
index 0000000..3816346
--- /dev/null
+++ b/tests/netd_test.cpp
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cutils/sockets.h>
+#include <private/android_filesystem_config.h>
+#include "NetdClient.h"
+
+#include <gtest/gtest.h>
+#define LOG_TAG "resolverTest"
+#include <utils/Log.h>
+#include <testUtil.h>
+
+#include "dns_responder.h"
+
+// TODO: make this dynamic and stop depending on implementation details.
+#define TEST_OEM_NETWORK "oem29"
+#define TEST_NETID 30
+
+class ResponseCode {
+public:
+    // Keep in sync with
+    // frameworks/base/services/java/com/android/server/NetworkManagementService.java
+    static const int CommandOkay               = 200;
+    static const int DnsProxyQueryResult       = 222;
+
+    static const int DnsProxyOperationFailed   = 401;
+
+    static const int CommandSyntaxError        = 500;
+    static const int CommandParameterError     = 501;
+};
+
+
+// Returns ResponseCode.
+int netdCommand(const char* sockname, const char* command) {
+    int sock = socket_local_client(sockname,
+                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                   SOCK_STREAM);
+    if (sock < 0) {
+        perror("Error connecting");
+        return -1;
+    }
+
+    // FrameworkListener expects the whole command in one read.
+    char buffer[256];
+    int nwritten = snprintf(buffer, sizeof(buffer), "0 %s", command);
+    if (write(sock, buffer, nwritten + 1) < 0) {
+        perror("Error sending netd command");
+        close(sock);
+        return -1;
+    }
+
+    int nread = read(sock, buffer, sizeof(buffer));
+    if (nread < 0) {
+        perror("Error reading response");
+        close(sock);
+        return -1;
+    }
+    close(sock);
+    return atoi(buffer);
+}
+
+
+bool expectNetdResult(int code, const char* sockname, const char* format, ...) {
+    char command[256];
+    va_list args;
+    va_start(args, format);
+    vsnprintf(command, sizeof(command), format, args);
+    va_end(args);
+    int result = netdCommand(sockname, command);
+    EXPECT_EQ(code, result) << command;
+    return (200 <= code && code < 300);
+}
+
+
+class ResolverTest : public ::testing::Test {
+protected:
+    virtual void SetUp() {
+        // Ensure resolutions go via proxy.
+        setenv("ANDROID_DNS_MODE", "", 1);
+        uid = getuid();
+        pid = getpid();
+        SetupOemNetwork();
+    }
+
+    virtual void TearDown() {
+        TearDownOemNetwork();
+        netdCommand("netd", "network destroy " TEST_OEM_NETWORK);
+    }
+
+    void SetupOemNetwork() {
+        netdCommand("netd", "network destroy " TEST_OEM_NETWORK);
+        if (expectNetdResult(ResponseCode::CommandOkay, "netd",
+                             "network create %s", TEST_OEM_NETWORK)) {
+            oemNetId = TEST_NETID;
+        }
+        setNetworkForProcess(oemNetId);
+        ASSERT_EQ((unsigned) oemNetId, getNetworkForProcess());
+    }
+
+    void TearDownOemNetwork() {
+        if (oemNetId != -1) {
+            expectNetdResult(ResponseCode::CommandOkay, "netd",
+                             "network destroy %s", TEST_OEM_NETWORK);
+        }
+    }
+
+    bool SetResolverForNetwork(const char* address) const {
+        return
+            expectNetdResult(ResponseCode::CommandOkay, "netd",
+                             "resolver setnetdns %d \"example.com\" %s", oemNetId,
+                             address) &&
+            FlushCache();
+    }
+
+    bool FlushCache() const {
+        return expectNetdResult(ResponseCode::CommandOkay, "netd",
+                                "resolver flushnet %d", oemNetId);
+    }
+
+    const char* ToString(const addrinfo* result) const {
+        if (!result)
+            return "<null>";
+        sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(result->ai_addr);
+        return inet_ntoa(addr->sin_addr);
+    }
+
+    const char* ToString(const hostent* result) const {
+        in_addr addr;
+        memcpy(reinterpret_cast<char*>(&addr), result->h_addr_list[0],
+               sizeof(addr));
+        return inet_ntoa(addr);
+    }
+
+    int pid;
+    int uid;
+    int oemNetId = -1;
+};
+
+
+TEST_F(ResolverTest, GetHostByName) {
+    const char* listen_addr = "127.0.0.3";
+    const char* listen_srv = "53";
+    test::DNSResponder resp(listen_addr, listen_srv, 250,
+                            ns_rcode::ns_r_servfail);
+    resp.addMapping("hello.example.com.", ns_type::ns_t_a, "1.2.3.3");
+    ASSERT_TRUE(resp.startServer());
+    ASSERT_TRUE(SetResolverForNetwork(listen_addr));
+
+    resp.clearQueries();
+    const hostent* result = gethostbyname("hello");
+    auto queries = resp.queries();
+    size_t found = 0;
+    for (const auto& p : queries) {
+        if (p.second == ns_type::ns_t_a && p.first == "hello.example.com.") {
+            ++found;
+        }
+    }
+    EXPECT_EQ(1, found);
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_STREQ("1.2.3.3", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+    resp.stopServer();
+}
+
+TEST_F(ResolverTest, GetAddrInfo) {
+    addrinfo* result = nullptr;
+
+    const char* listen_addr = "127.0.0.4";
+    const char* listen_srv = "53";
+    test::DNSResponder resp(listen_addr, listen_srv, 250,
+                            ns_rcode::ns_r_servfail);
+    resp.addMapping("howdie.example.com.", ns_type::ns_t_a, "1.2.3.4");
+    resp.addMapping("howdie.example.com.", ns_type::ns_t_aaaa, "::1.2.3.4");
+    ASSERT_TRUE(resp.startServer());
+    ASSERT_TRUE(SetResolverForNetwork(listen_addr));
+
+    resp.clearQueries();
+    EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result));
+    auto queries = resp.queries();
+    size_t found = 0;
+    for (const auto& p : queries) {
+        if (p.first == "howdie.example.com.") {
+            ++found;
+        }
+    }
+    EXPECT_LE(1, 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");
+    if (result) freeaddrinfo(result);
+    result = nullptr;
+
+    // Verify that it's cached.
+    EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result));
+    result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4");
+    if (result) freeaddrinfo(result);
+    result = nullptr;
+
+    // Verify that cache can be flushed.
+    resp.clearQueries();
+    ASSERT_TRUE(FlushCache());
+    resp.addMapping("howdie.example.com.", ns_type::ns_t_a, "1.2.3.44");
+    resp.addMapping("howdie.example.com.", ns_type::ns_t_aaaa, "::1.2.3.44");
+
+    EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result));
+    queries = resp.queries();
+    found = 0;
+    for (const auto& p : queries) {
+        if (p.first == "howdie.example.com.") {
+            ++found;
+        }
+    }
+    EXPECT_LE(1, found);
+    // Could be A or AAAA
+    result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.44" || result_str == "::1.2.3.44");
+    if (result) freeaddrinfo(result);
+}
+
+TEST_F(ResolverTest, GetAddrInfoV4) {
+    addrinfo* result = nullptr;
+
+    const char* listen_addr = "127.0.0.5";
+    const char* listen_srv = "53";
+    test::DNSResponder resp(listen_addr, listen_srv, 250,
+                            ns_rcode::ns_r_servfail);
+    resp.addMapping("hola.example.com.", ns_type::ns_t_a, "1.2.3.5");
+    ASSERT_TRUE(resp.startServer());
+    ASSERT_TRUE(SetResolverForNetwork(listen_addr));
+
+    addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET;
+    EXPECT_EQ(0, getaddrinfo("hola", nullptr, &hints, &result));
+    auto queries = resp.queries();
+    size_t found = 0;
+    for (const auto& p : queries) {
+        if (p.first == "hola.example.com.") {
+            ++found;
+        }
+    }
+    EXPECT_LE(1, found);
+    EXPECT_STREQ("1.2.3.5", ToString(result));
+    if (result) freeaddrinfo(result);
+}