Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless requied by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | * |
| 16 | */ |
| 17 | |
| 18 | #include <arpa/inet.h> |
| 19 | #include <errno.h> |
| 20 | #include <netdb.h> |
| 21 | #include <stdarg.h> |
| 22 | #include <stdio.h> |
| 23 | #include <stdlib.h> |
| 24 | |
| 25 | #include <cutils/sockets.h> |
| 26 | #include <private/android_filesystem_config.h> |
| 27 | #include "NetdClient.h" |
| 28 | |
| 29 | #include <gtest/gtest.h> |
| 30 | #define LOG_TAG "resolverTest" |
| 31 | #include <utils/Log.h> |
| 32 | #include <testUtil.h> |
| 33 | |
| 34 | #include "dns_responder.h" |
| 35 | |
| 36 | // TODO: make this dynamic and stop depending on implementation details. |
| 37 | #define TEST_OEM_NETWORK "oem29" |
| 38 | #define TEST_NETID 30 |
| 39 | |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 40 | enum class ResponseCode : int { |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 41 | // Keep in sync with |
| 42 | // frameworks/base/services/java/com/android/server/NetworkManagementService.java |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 43 | CommandOkay = 200, |
| 44 | DnsProxyQueryResult = 222, |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 45 | |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 46 | DnsProxyOperationFailed = 401, |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 47 | |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 48 | CommandSyntaxError = 500, |
| 49 | CommandParameterError = 501 |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 50 | }; |
| 51 | |
| 52 | |
| 53 | // Returns ResponseCode. |
| 54 | int netdCommand(const char* sockname, const char* command) { |
| 55 | int sock = socket_local_client(sockname, |
| 56 | ANDROID_SOCKET_NAMESPACE_RESERVED, |
| 57 | SOCK_STREAM); |
| 58 | if (sock < 0) { |
| 59 | perror("Error connecting"); |
| 60 | return -1; |
| 61 | } |
| 62 | |
| 63 | // FrameworkListener expects the whole command in one read. |
| 64 | char buffer[256]; |
| 65 | int nwritten = snprintf(buffer, sizeof(buffer), "0 %s", command); |
| 66 | if (write(sock, buffer, nwritten + 1) < 0) { |
| 67 | perror("Error sending netd command"); |
| 68 | close(sock); |
| 69 | return -1; |
| 70 | } |
| 71 | |
| 72 | int nread = read(sock, buffer, sizeof(buffer)); |
| 73 | if (nread < 0) { |
| 74 | perror("Error reading response"); |
| 75 | close(sock); |
| 76 | return -1; |
| 77 | } |
| 78 | close(sock); |
| 79 | return atoi(buffer); |
| 80 | } |
| 81 | |
| 82 | |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 83 | bool expectNetdResult(ResponseCode code, const char* sockname, const char* format, ...) { |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 84 | char command[256]; |
| 85 | va_list args; |
| 86 | va_start(args, format); |
| 87 | vsnprintf(command, sizeof(command), format, args); |
| 88 | va_end(args); |
| 89 | int result = netdCommand(sockname, command); |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 90 | int rc = static_cast<int>(code); |
| 91 | EXPECT_EQ(rc, result) << command; |
| 92 | return (200 <= rc && rc < 300); |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | |
| 96 | class ResolverTest : public ::testing::Test { |
| 97 | protected: |
| 98 | virtual void SetUp() { |
| 99 | // Ensure resolutions go via proxy. |
| 100 | setenv("ANDROID_DNS_MODE", "", 1); |
| 101 | uid = getuid(); |
| 102 | pid = getpid(); |
| 103 | SetupOemNetwork(); |
| 104 | } |
| 105 | |
| 106 | virtual void TearDown() { |
| 107 | TearDownOemNetwork(); |
| 108 | netdCommand("netd", "network destroy " TEST_OEM_NETWORK); |
| 109 | } |
| 110 | |
| 111 | void SetupOemNetwork() { |
| 112 | netdCommand("netd", "network destroy " TEST_OEM_NETWORK); |
| 113 | if (expectNetdResult(ResponseCode::CommandOkay, "netd", |
| 114 | "network create %s", TEST_OEM_NETWORK)) { |
| 115 | oemNetId = TEST_NETID; |
| 116 | } |
| 117 | setNetworkForProcess(oemNetId); |
| 118 | ASSERT_EQ((unsigned) oemNetId, getNetworkForProcess()); |
| 119 | } |
| 120 | |
| 121 | void TearDownOemNetwork() { |
| 122 | if (oemNetId != -1) { |
| 123 | expectNetdResult(ResponseCode::CommandOkay, "netd", |
| 124 | "network destroy %s", TEST_OEM_NETWORK); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | bool SetResolverForNetwork(const char* address) const { |
| 129 | return |
| 130 | expectNetdResult(ResponseCode::CommandOkay, "netd", |
| 131 | "resolver setnetdns %d \"example.com\" %s", oemNetId, |
| 132 | address) && |
| 133 | FlushCache(); |
| 134 | } |
| 135 | |
| 136 | bool FlushCache() const { |
| 137 | return expectNetdResult(ResponseCode::CommandOkay, "netd", |
| 138 | "resolver flushnet %d", oemNetId); |
| 139 | } |
| 140 | |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 141 | std::string ToString(const hostent* result) const { |
| 142 | if (result == nullptr) return std::string(); |
| 143 | return std::string(result->h_name); |
| 144 | } |
| 145 | |
| 146 | std::string ToString(const addrinfo* result) const { |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 147 | if (!result) |
| 148 | return "<null>"; |
| 149 | sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(result->ai_addr); |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 150 | return std::string(inet_ntoa(addr->sin_addr)); |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | int pid; |
| 154 | int uid; |
| 155 | int oemNetId = -1; |
| 156 | }; |
| 157 | |
| 158 | |
| 159 | TEST_F(ResolverTest, GetHostByName) { |
| 160 | const char* listen_addr = "127.0.0.3"; |
| 161 | const char* listen_srv = "53"; |
| 162 | test::DNSResponder resp(listen_addr, listen_srv, 250, |
| 163 | ns_rcode::ns_r_servfail); |
| 164 | resp.addMapping("hello.example.com.", ns_type::ns_t_a, "1.2.3.3"); |
| 165 | ASSERT_TRUE(resp.startServer()); |
| 166 | ASSERT_TRUE(SetResolverForNetwork(listen_addr)); |
| 167 | |
| 168 | resp.clearQueries(); |
| 169 | const hostent* result = gethostbyname("hello"); |
| 170 | auto queries = resp.queries(); |
| 171 | size_t found = 0; |
| 172 | for (const auto& p : queries) { |
| 173 | if (p.second == ns_type::ns_t_a && p.first == "hello.example.com.") { |
| 174 | ++found; |
| 175 | } |
| 176 | } |
| 177 | EXPECT_EQ(1, found); |
| 178 | ASSERT_FALSE(result == nullptr); |
| 179 | ASSERT_EQ(4, result->h_length); |
| 180 | ASSERT_FALSE(result->h_addr_list[0] == nullptr); |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 181 | EXPECT_EQ("hello.example.com", ToString(result)); |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 182 | EXPECT_TRUE(result->h_addr_list[1] == nullptr); |
| 183 | resp.stopServer(); |
| 184 | } |
| 185 | |
| 186 | TEST_F(ResolverTest, GetAddrInfo) { |
| 187 | addrinfo* result = nullptr; |
| 188 | |
| 189 | const char* listen_addr = "127.0.0.4"; |
| 190 | const char* listen_srv = "53"; |
| 191 | test::DNSResponder resp(listen_addr, listen_srv, 250, |
| 192 | ns_rcode::ns_r_servfail); |
| 193 | resp.addMapping("howdie.example.com.", ns_type::ns_t_a, "1.2.3.4"); |
| 194 | resp.addMapping("howdie.example.com.", ns_type::ns_t_aaaa, "::1.2.3.4"); |
| 195 | ASSERT_TRUE(resp.startServer()); |
| 196 | ASSERT_TRUE(SetResolverForNetwork(listen_addr)); |
| 197 | |
| 198 | resp.clearQueries(); |
| 199 | EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result)); |
| 200 | auto queries = resp.queries(); |
| 201 | size_t found = 0; |
| 202 | for (const auto& p : queries) { |
| 203 | if (p.first == "howdie.example.com.") { |
| 204 | ++found; |
| 205 | } |
| 206 | } |
| 207 | EXPECT_LE(1, found); |
| 208 | // Could be A or AAAA |
| 209 | std::string result_str = ToString(result); |
| 210 | EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4"); |
| 211 | if (result) freeaddrinfo(result); |
| 212 | result = nullptr; |
| 213 | |
| 214 | // Verify that it's cached. |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 215 | size_t old_found = found; |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 216 | EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result)); |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 217 | queries = resp.queries(); |
| 218 | found = 0; |
| 219 | for (const auto& p : queries) { |
| 220 | if (p.first == "howdie.example.com.") { |
| 221 | ++found; |
| 222 | } |
| 223 | } |
| 224 | EXPECT_EQ(old_found, found); |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 225 | result_str = ToString(result); |
| 226 | EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4"); |
| 227 | if (result) freeaddrinfo(result); |
| 228 | result = nullptr; |
| 229 | |
| 230 | // Verify that cache can be flushed. |
| 231 | resp.clearQueries(); |
| 232 | ASSERT_TRUE(FlushCache()); |
| 233 | resp.addMapping("howdie.example.com.", ns_type::ns_t_a, "1.2.3.44"); |
| 234 | resp.addMapping("howdie.example.com.", ns_type::ns_t_aaaa, "::1.2.3.44"); |
| 235 | |
| 236 | EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result)); |
| 237 | queries = resp.queries(); |
| 238 | found = 0; |
| 239 | for (const auto& p : queries) { |
| 240 | if (p.first == "howdie.example.com.") { |
| 241 | ++found; |
| 242 | } |
| 243 | } |
| 244 | EXPECT_LE(1, found); |
| 245 | // Could be A or AAAA |
| 246 | result_str = ToString(result); |
| 247 | EXPECT_TRUE(result_str == "1.2.3.44" || result_str == "::1.2.3.44"); |
| 248 | if (result) freeaddrinfo(result); |
| 249 | } |
| 250 | |
| 251 | TEST_F(ResolverTest, GetAddrInfoV4) { |
| 252 | addrinfo* result = nullptr; |
| 253 | |
| 254 | const char* listen_addr = "127.0.0.5"; |
| 255 | const char* listen_srv = "53"; |
| 256 | test::DNSResponder resp(listen_addr, listen_srv, 250, |
| 257 | ns_rcode::ns_r_servfail); |
| 258 | resp.addMapping("hola.example.com.", ns_type::ns_t_a, "1.2.3.5"); |
| 259 | ASSERT_TRUE(resp.startServer()); |
| 260 | ASSERT_TRUE(SetResolverForNetwork(listen_addr)); |
| 261 | |
| 262 | addrinfo hints; |
| 263 | memset(&hints, 0, sizeof(hints)); |
| 264 | hints.ai_family = AF_INET; |
| 265 | EXPECT_EQ(0, getaddrinfo("hola", nullptr, &hints, &result)); |
| 266 | auto queries = resp.queries(); |
| 267 | size_t found = 0; |
| 268 | for (const auto& p : queries) { |
| 269 | if (p.first == "hola.example.com.") { |
| 270 | ++found; |
| 271 | } |
| 272 | } |
| 273 | EXPECT_LE(1, found); |
Pierre Imai | ccf7b99 | 2016-02-25 16:34:29 +0900 | [diff] [blame^] | 274 | EXPECT_EQ("1.2.3.5", ToString(result)); |
Pierre Imai | 904ce3a | 2016-02-18 13:13:12 +0900 | [diff] [blame] | 275 | if (result) freeaddrinfo(result); |
| 276 | } |