Snap for 7080740 from d8420baacf39ac23a048bc83bc2c95f8a3d061d7 to mainline-networkstack-release
Change-Id: I0e5f971bfed2c4239cfd64b9772adaecc58a4d32
diff --git a/Android.bp b/Android.bp
index db610a6..71228b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,6 +13,10 @@
cc_library_headers {
name: "dnsproxyd_protocol_headers",
export_include_dirs: ["include/dnsproxyd_protocol"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
}
aidl_interface {
@@ -23,6 +27,8 @@
"binder/android/net/ResolverHostsParcel.aidl",
"binder/android/net/ResolverOptionsParcel.aidl",
"binder/android/net/ResolverParamsParcel.aidl",
+ // New AIDL classes should go into android.net.resolv.aidl so they can be clearly identified
+ "binder/android/net/resolv/aidl/**/*.aidl",
],
imports: [
"netd_event_listener_interface",
@@ -50,6 +56,7 @@
"4",
"5",
"6",
+ "7",
],
}
@@ -101,7 +108,6 @@
"res_cache.cpp",
"res_comp.cpp",
"res_debug.cpp",
- "res_init.cpp",
"res_mkquery.cpp",
"res_query.cpp",
"res_send.cpp",
@@ -259,6 +265,8 @@
"DnsQueryLogTest.cpp",
"DnsStatsTest.cpp",
"ExperimentsTest.cpp",
+ "OperationLimiterTest.cpp",
+ "PrivateDnsConfigurationTest.cpp",
],
shared_libs: [
"libcrypto",
@@ -268,7 +276,7 @@
static_libs: [
"dnsresolver_aidl_interface-unstable-ndk_platform",
"netd_aidl_interface-ndk_platform",
- "netd_event_listener_interface-ndk_platform",
+ "netd_event_listener_interface-unstable-ndk_platform",
"libcutils",
"libgmock",
"libnetd_resolv",
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index df5d873..d7fbcfc 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -39,7 +39,6 @@
#include <cutils/misc.h> // FIRST_APPLICATION_UID
#include <cutils/multiuser.h>
#include <netdutils/InternetAddresses.h>
-#include <netdutils/OperationLimiter.h>
#include <netdutils/ResponseCode.h>
#include <netdutils/Slice.h>
#include <netdutils/Stopwatch.h>
@@ -50,6 +49,7 @@
#include "DnsResolver.h"
#include "NetdPermissions.h"
+#include "OperationLimiter.h"
#include "PrivateDnsConfiguration.h"
#include "ResolverEventReporter.h"
#include "dnsproxyd_protocol/DnsProxydProtocol.h" // NETID_USE_LOCAL_NAMESERVERS
@@ -84,25 +84,6 @@
}
}
-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;
@@ -129,7 +110,7 @@
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.");
+ "Calls from AID_SYSTEM must not result in a permission check to avoid deadlock.");
if (uid >= 0 && uid < FIRST_APPLICATION_UID) {
return true;
}
@@ -168,7 +149,7 @@
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) {
+ if (ns_initparse((const uint8_t*)answer, anslen, &handle) < 0) {
return 0;
}
int ancount = ns_msg_count(handle, ns_s_an);
@@ -181,12 +162,12 @@
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));
+ 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));
+ addIpAddrWithinLimit(ip_addrs, (sockaddr*)&sin6, sizeof(sin6));
total_ip_addr_count++;
}
}
@@ -215,17 +196,17 @@
return 0;
}
if (hp->h_addrtype == AF_INET) {
- in_addr** list = (in_addr**) hp->h_addr_list;
+ 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));
+ 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;
+ 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));
+ addIpAddrWithinLimit(ip_addrs, (sockaddr*)&sin6, sizeof(sin6));
total_ip_addr_count++;
}
}
@@ -401,7 +382,7 @@
bool isSpecialUseIPv4Address(const struct sockaddr* sa) {
if (sa->sa_family != AF_INET) return false;
- return isSpecialUseIPv4Address(((struct sockaddr_in*) sa)->sin_addr);
+ return isSpecialUseIPv4Address(((struct sockaddr_in*)sa)->sin_addr);
}
bool onlyNonSpecialUseIPv4Addresses(struct hostent* hp) {
@@ -410,7 +391,7 @@
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;
+ if (isSpecialUseIPv4Address(*(struct in_addr*)hp->h_addr_list[i])) return false;
return true;
}
@@ -480,10 +461,10 @@
if (!isValidNat64Prefix(prefix)) return false;
struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
- struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+ 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];
+ 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
@@ -515,10 +496,10 @@
if (!isValidNat64Prefix(prefix)) return false;
struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
- struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+ 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;
+ 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
@@ -563,21 +544,31 @@
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) {}
+void DnsProxyListener::Handler::spawn() {
+ const int rval = netdutils::threadLaunch(this);
+ if (rval == 0) {
+ return;
+ }
-DnsProxyListener::GetAddrInfoHandler::~GetAddrInfoHandler() {
- free(mHost);
- free(mService);
- free(mHints);
+ char* msg = nullptr;
+ asprintf(&msg, "%s (%d)", strerror(-rval), -rval);
+ mClient->sendMsg(ResponseCode::OperationFailed, msg, false);
+ free(msg);
+ delete this;
}
-static bool evaluate_domain_name(const android_net_context &netcontext,
- const char *host) {
- if (!gResNetdCallbacks.evaluate_domain_name)
- return true;
+DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(SocketClient* c, std::string host,
+ std::string service,
+ std::unique_ptr<addrinfo> hints,
+ const android_net_context& netcontext)
+ : Handler(c),
+ mHost(move(host)),
+ mService(move(service)),
+ mHints(move(hints)),
+ mNetContext(netcontext) {}
+
+static bool evaluate_domain_name(const android_net_context& netcontext, const char* host) {
+ if (!gResNetdCallbacks.evaluate_domain_name) return true;
return gResNetdCallbacks.evaluate_domain_name(netcontext, host);
}
@@ -597,15 +588,15 @@
bool success = true;
int i;
if (hp->h_name != nullptr) {
- success &= sendLenAndData(c, strlen(hp->h_name)+1, hp->h_name);
+ 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]);
+ 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
+ success &= sendLenAndData(c, 0, ""); // null to indicate we're done
uint32_t buf = htonl(hp->h_addrtype);
success &= c->sendData(&buf, sizeof(buf)) == 0;
@@ -613,10 +604,10 @@
buf = htonl(hp->h_length);
success &= c->sendData(&buf, sizeof(buf)) == 0;
- for (i=0; hp->h_addr_list[i] != nullptr; i++) {
+ 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
+ success &= sendLenAndData(c, 0, ""); // null to indicate we're done
return success;
}
@@ -634,11 +625,8 @@
// 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);
+ 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;
}
@@ -658,8 +646,6 @@
void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res,
NetworkDnsEventReported* event) {
- 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));
@@ -677,10 +663,12 @@
// 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)) {
+ const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str();
+ const char* service = mService.starts_with('^') ? nullptr : mService.c_str();
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 = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, res, event);
+ *rv = resolv_getaddrinfo(host, service, mHints.get(), &mNetContext, res, event);
queryLimiter.finish(uid);
if (*rv) {
*rv = EAI_NODATA; // return original error code
@@ -718,9 +706,10 @@
NetworkDnsEventReported event;
initDnsEvent(&event, mNetContext);
if (queryLimiter.start(uid)) {
- if (evaluate_domain_name(mNetContext, mHost)) {
- rv = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, &result,
- &event);
+ const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str();
+ const char* service = mService.starts_with('^') ? nullptr : mService.c_str();
+ if (evaluate_domain_name(mNetContext, host)) {
+ rv = resolv_getaddrinfo(host, service, mHints.get(), &mNetContext, &result, &event);
} else {
rv = EAI_SYSTEM;
}
@@ -763,7 +752,6 @@
reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, event, mHost,
ip_addrs, total_ip_addr_count);
freeaddrinfo(result);
- mClient->decRef();
}
std::string DnsProxyListener::GetAddrInfoHandler::threadName() {
@@ -789,34 +777,20 @@
DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd() : FrameworkCommand("getaddrinfo") {}
-int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
- int argc, char **argv) {
+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);
+ 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;
+ const std::string name = argv[1];
+ const std::string service = argv[2];
int ai_flags = strtol(argv[3], nullptr, 10);
int ai_family = strtol(argv[4], nullptr, 10);
int ai_socktype = strtol(argv[5], nullptr, 10);
@@ -832,18 +806,16 @@
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));
+ std::unique_ptr<addrinfo> hints;
+ if (ai_flags != -1 || ai_family != -1 || ai_socktype != -1 || ai_protocol != -1) {
+ hints.reset((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);
+ (new GetAddrInfoHandler(cli, name, service, move(hints), netcontext))->spawn();
return 0;
}
@@ -888,19 +860,13 @@
netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
}
- DnsProxyListener::ResNSendHandler* handler =
- new DnsProxyListener::ResNSendHandler(cli, argv[3], flags, netcontext);
- tryThreadOrError(cli, handler);
+ (new ResNSendHandler(cli, argv[3], flags, netcontext))->spawn();
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();
-}
+ : Handler(c), mMsg(std::move(msg)), mFlags(flags), mNetContext(netcontext) {}
void DnsProxyListener::ResNSendHandler::run() {
LOG(DEBUG) << "ResNSendHandler::run: " << mFlags << " / {" << mNetContext.app_netid << " "
@@ -991,7 +957,7 @@
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);
+ extractResNsendAnswers((uint8_t*)ansBuf.data(), nsendAns, rr_type, &ip_addrs);
reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
resNSendToAiError(nsendAns, rcode), event, rr_name, ip_addrs,
total_ip_addr_count);
@@ -1058,8 +1024,7 @@
*******************************************************/
DnsProxyListener::GetHostByNameCmd::GetHostByNameCmd() : FrameworkCommand("gethostbyname") {}
-int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient *cli,
- int argc, char **argv) {
+int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient* cli, int argc, char** argv) {
logArguments(argc, argv);
if (argc != 4) {
@@ -1074,15 +1039,9 @@
uid_t uid = cli->getUid();
unsigned netId = strtoul(argv[1], nullptr, 10);
const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
- char* name = argv[2];
+ std::string 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);
@@ -1090,19 +1049,14 @@
netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
}
- DnsProxyListener::GetHostByNameHandler* handler =
- new DnsProxyListener::GetHostByNameHandler(cli, name, af, netcontext);
- tryThreadOrError(cli, handler);
+ (new GetHostByNameHandler(cli, name, af, netcontext))->spawn();
return 0;
}
-DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, char* name, int af,
+DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, std::string name,
+ int af,
const android_net_context& netcontext)
- : mClient(c), mName(name), mAf(af), mNetContext(netcontext) {}
-
-DnsProxyListener::GetHostByNameHandler::~GetHostByNameHandler() {
- free(mName);
-}
+ : Handler(c), mName(move(name)), mAf(af), mNetContext(netcontext) {}
void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, hostent* hbuf, char* buf,
size_t buflen, struct hostent** hpp,
@@ -1123,7 +1077,8 @@
// 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 = resolv_gethostbyname(mName, AF_INET, hbuf, buf, buflen, &mNetContext, hpp, event);
+ const char* name = mName.starts_with('^') ? nullptr : mName.c_str();
+ *rv = resolv_gethostbyname(name, AF_INET, hbuf, buf, buflen, &mNetContext, hpp, event);
queryLimiter.finish(uid);
if (*rv) {
*rv = EAI_NODATA; // return original error code
@@ -1152,8 +1107,9 @@
NetworkDnsEventReported event;
initDnsEvent(&event, mNetContext);
if (queryLimiter.start(uid)) {
- if (evaluate_domain_name(mNetContext, mName)) {
- rv = resolv_gethostbyname(mName, mAf, &hbuf, tmpbuf, sizeof tmpbuf, &mNetContext, &hp,
+ const char* name = mName.starts_with('^') ? nullptr : mName.c_str();
+ if (evaluate_domain_name(mNetContext, name)) {
+ rv = resolv_gethostbyname(name, mAf, &hbuf, tmpbuf, sizeof tmpbuf, &mNetContext, &hp,
&event);
} else {
rv = EAI_SYSTEM;
@@ -1190,7 +1146,6 @@
const int total_ip_addr_count = extractGetHostByNameAnswers(hp, &ip_addrs);
reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, event,
mName, ip_addrs, total_ip_addr_count);
- mClient->decRef();
}
std::string DnsProxyListener::GetHostByNameHandler::threadName() {
@@ -1202,8 +1157,7 @@
*******************************************************/
DnsProxyListener::GetHostByAddrCmd::GetHostByAddrCmd() : FrameworkCommand("gethostbyaddr") {}
-int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient *cli,
- int argc, char **argv) {
+int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient* cli, int argc, char** argv) {
logArguments(argc, argv);
if (argc != 5) {
@@ -1222,15 +1176,14 @@
unsigned netId = strtoul(argv[4], nullptr, 10);
const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
- void* addr = malloc(sizeof(in6_addr));
+ in6_addr addr;
errno = 0;
- int result = inet_pton(addrFamily, addrStr, addr);
+ 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;
}
@@ -1242,30 +1195,24 @@
netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
}
- DnsProxyListener::GetHostByAddrHandler* handler = new DnsProxyListener::GetHostByAddrHandler(
- cli, addr, addrLen, addrFamily, netcontext);
- tryThreadOrError(cli, handler);
+ (new GetHostByAddrHandler(cli, addr, addrLen, addrFamily, netcontext))->spawn();
return 0;
}
-DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c, void* address,
+DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c, in6_addr address,
int addressLen, int addressFamily,
const android_net_context& netcontext)
- : mClient(c),
+ : Handler(c),
mAddress(address),
mAddressLen(addressLen),
mAddressFamily(addressFamily),
mNetContext(netcontext) {}
-DnsProxyListener::GetHostByAddrHandler::~GetHostByAddrHandler() {
- free(mAddress);
-}
-
void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(hostent* hbuf, char* buf,
size_t buflen,
struct hostent** hpp,
NetworkDnsEventReported* event) {
- if (*hpp != nullptr || mAddressFamily != AF_INET6 || !mAddress) {
+ if (*hpp != nullptr || mAddressFamily != AF_INET6) {
return;
}
@@ -1279,8 +1226,8 @@
}
struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
- struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
- struct in6_addr v6addr = *(in6_addr*) mAddress;
+ struct sockaddr_in6* v6prefix = (struct sockaddr_in6*)&ss;
+ struct in6_addr v6addr = 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]) ||
@@ -1320,7 +1267,7 @@
NetworkDnsEventReported event;
initDnsEvent(&event, mNetContext);
if (queryLimiter.start(uid)) {
- rv = resolv_gethostbyaddr(mAddress, mAddressLen, mAddressFamily, &hbuf, tmpbuf,
+ rv = resolv_gethostbyaddr(&mAddress, mAddressLen, mAddressFamily, &hbuf, tmpbuf,
sizeof tmpbuf, &mNetContext, &hp, &event);
queryLimiter.finish(uid);
} else {
@@ -1351,7 +1298,6 @@
reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, event,
(hp && hp->h_name) ? hp->h_name : "null", {}, 0);
- mClient->decRef();
}
std::string DnsProxyListener::GetHostByAddrHandler::threadName() {
diff --git a/DnsProxyListener.h b/DnsProxyListener.h
index 34340a0..87f58c8 100644
--- a/DnsProxyListener.h
+++ b/DnsProxyListener.h
@@ -38,6 +38,23 @@
static constexpr const char* SOCKET_NAME = "dnsproxyd";
private:
+ class Handler {
+ public:
+ Handler(SocketClient* c) : mClient(c) { mClient->incRef(); }
+ virtual ~Handler() { mClient->decRef(); }
+ void operator=(const Handler&) = delete;
+
+ // Attept to spawn the worker thread, or return an error to the client.
+ // The Handler instance will self-delete in either case.
+ void spawn();
+
+ virtual void run() = 0;
+ virtual std::string threadName() = 0;
+
+ SocketClient* mClient; // ref-counted
+ };
+
+ /* ------ getaddrinfo ------*/
class GetAddrInfoCmd : public FrameworkCommand {
public:
GetAddrInfoCmd();
@@ -45,24 +62,22 @@
int runCommand(SocketClient* c, int argc, char** argv) override;
};
- /* ------ getaddrinfo ------*/
- class GetAddrInfoHandler {
+ class GetAddrInfoHandler : public Handler {
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();
+ GetAddrInfoHandler(SocketClient* c, std::string host, std::string service,
+ std::unique_ptr<addrinfo> hints, const android_net_context& netcontext);
+ ~GetAddrInfoHandler() override = default;
- void run();
- std::string threadName();
+ void run() override;
+ std::string threadName() override;
private:
void doDns64Synthesis(int32_t* rv, addrinfo** res, NetworkDnsEventReported* event);
- SocketClient* mClient; // ref counted
- char* mHost; // owned. TODO: convert to std::string.
- char* mService; // owned. TODO: convert to std::string.
- addrinfo* mHints; // owned
+ std::string mHost;
+ std::string mService;
+ std::unique_ptr<addrinfo> mHints;
android_net_context mNetContext;
};
@@ -74,21 +89,20 @@
int runCommand(SocketClient* c, int argc, char** argv) override;
};
- class GetHostByNameHandler {
+ class GetHostByNameHandler : public Handler {
public:
- GetHostByNameHandler(SocketClient* c, char* name, int af,
+ GetHostByNameHandler(SocketClient* c, std::string name, int af,
const android_net_context& netcontext);
- ~GetHostByNameHandler();
+ ~GetHostByNameHandler() override = default;
- void run();
- std::string threadName();
+ void run() override;
+ std::string threadName() override;
private:
void doDns64Synthesis(int32_t* rv, hostent* hbuf, char* buf, size_t buflen, hostent** hpp,
NetworkDnsEventReported* event);
- SocketClient* mClient; // ref counted
- char* mName; // owned. TODO: convert to std::string.
+ std::string mName;
int mAf;
android_net_context mNetContext;
};
@@ -101,21 +115,20 @@
int runCommand(SocketClient* c, int argc, char** argv) override;
};
- class GetHostByAddrHandler {
+ class GetHostByAddrHandler : public Handler {
public:
- GetHostByAddrHandler(SocketClient* c, void* address, int addressLen, int addressFamily,
+ GetHostByAddrHandler(SocketClient* c, in6_addr address, int addressLen, int addressFamily,
const android_net_context& netcontext);
- ~GetHostByAddrHandler();
+ ~GetHostByAddrHandler() override = default;
- void run();
- std::string threadName();
+ void run() override;
+ std::string threadName() override;
private:
void doDns64ReverseLookup(hostent* hbuf, char* buf, size_t buflen, hostent** hpp,
NetworkDnsEventReported* event);
- SocketClient* mClient; // ref counted
- void* mAddress; // address to lookup; owned
+ in6_addr mAddress;
int mAddressLen; // length of address to look up
int mAddressFamily; // address family
android_net_context mNetContext;
@@ -129,17 +142,16 @@
int runCommand(SocketClient* c, int argc, char** argv) override;
};
- class ResNSendHandler {
+ class ResNSendHandler : public Handler {
public:
ResNSendHandler(SocketClient* c, std::string msg, uint32_t flags,
const android_net_context& netcontext);
- ~ResNSendHandler();
+ ~ResNSendHandler() override = default;
- void run();
- std::string threadName();
+ void run() override;
+ std::string threadName() override;
private:
- SocketClient* mClient; // ref counted
std::string mMsg;
uint32_t mFlags;
android_net_context mNetContext;
diff --git a/DnsQueryLog.cpp b/DnsQueryLog.cpp
index 6f0e179..9cc3ca4 100644
--- a/DnsQueryLog.cpp
+++ b/DnsQueryLog.cpp
@@ -17,7 +17,7 @@
#include "DnsQueryLog.h"
-#include <android-base/stringprintf.h>
+#include "util.h"
namespace android::net {
@@ -45,25 +45,10 @@
return ret.empty() ? "" : ret.substr(0, ret.length() - 2);
}
-// Return the readable string format "hr:min:sec.ms".
-std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
- using std::chrono::duration_cast;
- using std::chrono::milliseconds;
- const auto time_sec = std::chrono::system_clock::to_time_t(ts);
- char buf[32];
- std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
- int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
- return android::base::StringPrintf("%s.%03d", buf, ms);
-}
-
} // namespace
void DnsQueryLog::push(Record&& record) {
- std::lock_guard guard(mLock);
- mQueue.push_back(std::move(record));
- if (mQueue.size() > mCapacity) {
- mQueue.pop_front();
- }
+ mQueue.push(std::move(record));
}
void DnsQueryLog::dump(netdutils::DumpWriter& dw) const {
@@ -71,8 +56,7 @@
netdutils::ScopedIndent indentStats(dw);
const auto now = std::chrono::system_clock::now();
- std::lock_guard guard(mLock);
- for (const auto& record : mQueue) {
+ for (const auto& record : mQueue.copy()) {
if (now - record.timestamp > mValidityTimeMs) continue;
const std::string maskedHostname = maskHostname(record.hostname);
diff --git a/DnsQueryLog.h b/DnsQueryLog.h
index c19f8db..3e6478e 100644
--- a/DnsQueryLog.h
+++ b/DnsQueryLog.h
@@ -17,16 +17,16 @@
#pragma once
-#include <deque>
#include <string>
#include <vector>
-#include <android-base/thread_annotations.h>
#include <netdutils/DumpWriter.h>
+#include "LockedQueue.h"
+
namespace android::net {
-// A circular buffer based class used for query logging. It's thread-safe for concurrent access.
+// This class stores query records in a locked ring buffer. It's thread-safe for concurrent access.
class DnsQueryLog {
public:
static constexpr std::string_view DUMP_KEYWORD = "querylog";
@@ -52,15 +52,13 @@
// Allow the tests to set the capacity and the validaty time in milliseconds.
DnsQueryLog(size_t size = kDefaultLogSize,
std::chrono::milliseconds time = kDefaultValidityMinutes)
- : mCapacity(size), mValidityTimeMs(time) {}
+ : mQueue(size), mValidityTimeMs(time) {}
- void push(Record&& record) EXCLUDES(mLock);
- void dump(netdutils::DumpWriter& dw) const EXCLUDES(mLock);
+ void push(Record&& record);
+ void dump(netdutils::DumpWriter& dw) const;
private:
- mutable std::mutex mLock;
- std::deque<Record> mQueue GUARDED_BY(mLock);
- const size_t mCapacity;
+ LockedRingBuffer<Record> mQueue;
const std::chrono::milliseconds mValidityTimeMs;
// The capacity of the circular buffer.
diff --git a/DnsResolver.cpp b/DnsResolver.cpp
index 5068aef..fcd4293 100644
--- a/DnsResolver.cpp
+++ b/DnsResolver.cpp
@@ -20,6 +20,8 @@
#include "DnsProxyListener.h"
#include "DnsResolverService.h"
+#include "DnsTlsDispatcher.h"
+#include "PrivateDnsConfiguration.h"
#include "netd_resolv/resolv.h"
#include "res_debug.h"
#include "util.h"
@@ -28,7 +30,8 @@
android::base::InitLogging(/*argv=*/nullptr);
android::base::SetDefaultTag("libnetd_resolv");
LOG(INFO) << __func__ << ": Initializing resolver";
- resolv_set_log_severity(android::base::WARNING);
+ // TODO(b/170539625): restore log level to WARNING after clarifying flaky tests.
+ resolv_set_log_severity(isUserDebugBuild() ? android::base::DEBUG : android::base::WARNING);
using android::net::gApiLevel;
gApiLevel = getApiLevel();
using android::net::gResNetdCallbacks;
@@ -72,6 +75,14 @@
return &instance;
}
+DnsResolver::DnsResolver() {
+ // TODO: make them member variables after fixing the circular dependency:
+ // DnsTlsDispatcher.h -> resolv_private.h -> DnsResolver.h -> DnsTlsDispatcher.h
+ auto& dnsTlsDispatcher = DnsTlsDispatcher::getInstance();
+ auto& privateDnsConfiguration = PrivateDnsConfiguration::getInstance();
+ privateDnsConfiguration.setObserver(&dnsTlsDispatcher);
+}
+
bool DnsResolver::start() {
if (!verifyCallbacks()) {
LOG(ERROR) << __func__ << ": Callback verification failed";
diff --git a/DnsResolver.h b/DnsResolver.h
index 863b096..9c2f3d8 100644
--- a/DnsResolver.h
+++ b/DnsResolver.h
@@ -40,7 +40,8 @@
ResolverController resolverCtrl;
private:
- DnsResolver() {}
+ DnsResolver();
+
DnsProxyListener mDnsProxyListener;
DnsQueryLog mQueryLog;
};
diff --git a/DnsResolverService.cpp b/DnsResolverService.cpp
index a2eb598..1d6723f 100644
--- a/DnsResolverService.cpp
+++ b/DnsResolverService.cpp
@@ -32,6 +32,7 @@
#include "DnsResolver.h"
#include "Experiments.h"
#include "NetdPermissions.h" // PERM_*
+#include "PrivateDnsConfiguration.h"
#include "ResolverEventReporter.h"
#include "resolv_cache.h"
@@ -72,9 +73,9 @@
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()); });
+ BnDnsResolver::logFunc = [](const auto& log) {
+ binderCallLogFn(log, [](const std::string& msg) { gResNetdCallbacks.log(msg.c_str()); });
+ };
}
binder_status_t DnsResolverService::start() {
@@ -117,6 +118,8 @@
gDnsResolv->resolverCtrl.dump(dw, netId);
dw.blankline();
}
+
+ PrivateDnsConfiguration::getInstance().dump(dw);
Experiments::getInstance()->dump(dw);
return STATUS_OK;
}
@@ -138,6 +141,14 @@
return statusFromErrcode(res);
}
+::ndk::ScopedAStatus DnsResolverService::registerUnsolicitedEventListener(
+ const std::shared_ptr<
+ aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&) {
+ ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+ return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
::ndk::ScopedAStatus DnsResolverService::checkAnyPermission(
const std::vector<const char*>& permissions) {
// TODO: Remove callback and move this to unnamed namespace after libbiner_ndk supports
diff --git a/DnsResolverService.h b/DnsResolverService.h
index 2228e21..fe39301 100644
--- a/DnsResolverService.h
+++ b/DnsResolverService.h
@@ -39,6 +39,10 @@
::ndk::ScopedAStatus registerEventListener(
const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
override;
+ ::ndk::ScopedAStatus registerUnsolicitedEventListener(
+ const std::shared_ptr<
+ aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&
+ listener) override;
// Resolver commands.
::ndk::ScopedAStatus setResolverConfiguration(
diff --git a/DnsStats.cpp b/DnsStats.cpp
index 2f76d8a..c6374c9 100644
--- a/DnsStats.cpp
+++ b/DnsStats.cpp
@@ -110,7 +110,16 @@
// Update the quality factors.
mSkippedCount = 0;
- updatePenalty(record);
+
+ // Because failures due to no permission can't prove that the quality of DNS server is bad,
+ // skip the penalty update. The average latency, however, has been updated. For short-latency
+ // servers, it will be fine. For long-latency servers, their average latency will be
+ // decreased but the latency-based algorithm will adjust their average latency back to the
+ // right range after few attempts when network is not restricted.
+ // The check is synced from isNetworkRestricted() in res_send.cpp.
+ if (record.linux_errno != EPERM) {
+ updatePenalty(record);
+ }
}
void StatsRecords::updateStatsData(const Record& record, const bool add) {
@@ -194,6 +203,7 @@
if (serverSockAddr == ipSockAddr) {
const StatsRecords::Record rec = {
.rcode = record.rcode(),
+ .linux_errno = record.linux_errno(),
.latencyUs = microseconds(record.latency_micros()),
};
statsRecords.push(rec);
diff --git a/DnsStats.h b/DnsStats.h
index c5459b4..39025d3 100644
--- a/DnsStats.h
+++ b/DnsStats.h
@@ -68,7 +68,8 @@
class StatsRecords {
public:
struct Record {
- int rcode;
+ int rcode = 0; // NS_R_NO_ERROR
+ int linux_errno = 0; // SYS_NO_ERROR
std::chrono::microseconds latencyUs;
};
diff --git a/DnsStatsTest.cpp b/DnsStatsTest.cpp
index 38a7a21..5efd186 100644
--- a/DnsStatsTest.cpp
+++ b/DnsStatsTest.cpp
@@ -59,8 +59,16 @@
TEST_F(StatsRecordsTest, PushRecord) {
const IPSockAddr server = IPSockAddr::toIPSockAddr("127.0.0.2", 53);
constexpr size_t size = 3;
- const StatsRecords::Record recordNoError = {NS_R_NO_ERROR, 10ms};
- const StatsRecords::Record recordTimeout = {NS_R_TIMEOUT, 250ms};
+ const StatsRecords::Record recordNoError = {
+ .rcode = NS_R_NO_ERROR,
+ .linux_errno = 0,
+ .latencyUs{10ms},
+ };
+ const StatsRecords::Record recordTimeout = {
+ .rcode = NS_R_TIMEOUT,
+ .linux_errno = 0,
+ .latencyUs{250ms},
+ };
StatsRecords sr(server, size);
EXPECT_EQ(sr.getStatsData(), makeStatsData(server, 0, 0ms, {}));
@@ -424,6 +432,17 @@
EXPECT_THAT(mDnsStats.getSortedServers(PROTO_UDP),
testing::ElementsAreArray({server2, server3, server1, server4}));
+ // Add some internal_error records with permission error to server2.
+ // The internal_error won't cause the priority of server2 drop. (but some of the other
+ // quality factors will still be counted, such as skipped_count and latency)
+ auto recordFromNetworkRestricted = makeDnsQueryEvent(PROTO_UDP, NS_R_INTERNAL_ERROR, 1ms);
+ recordFromNetworkRestricted.set_linux_errno(static_cast<LinuxErrno>(EPERM));
+ for (int i = 0; i < 3; i++) {
+ EXPECT_TRUE(mDnsStats.addStats(server2, recordFromNetworkRestricted));
+ }
+ EXPECT_THAT(mDnsStats.getSortedServers(PROTO_UDP),
+ testing::ElementsAreArray({server2, server3, server1, server4}));
+
// The list of the DNS servers changed.
EXPECT_TRUE(mDnsStats.setServers({server2, server4}, PROTO_UDP));
EXPECT_THAT(mDnsStats.getSortedServers(PROTO_UDP),
diff --git a/DnsTlsDispatcher.cpp b/DnsTlsDispatcher.cpp
index df8fce8..41eac9d 100644
--- a/DnsTlsDispatcher.cpp
+++ b/DnsTlsDispatcher.cpp
@@ -41,6 +41,11 @@
mFactory.reset(new DnsTlsSocketFactory());
}
+DnsTlsDispatcher& DnsTlsDispatcher::getInstance() {
+ static DnsTlsDispatcher instance;
+ return instance;
+}
+
std::list<DnsTlsServer> DnsTlsDispatcher::getOrderedServerList(
const std::list<DnsTlsServer> &tlsServers, unsigned mark) const {
// Our preferred DnsTlsServer order is:
diff --git a/DnsTlsDispatcher.h b/DnsTlsDispatcher.h
index 9eb6dfe..2cb7519 100644
--- a/DnsTlsDispatcher.h
+++ b/DnsTlsDispatcher.h
@@ -28,6 +28,7 @@
#include "DnsTlsServer.h"
#include "DnsTlsTransport.h"
#include "IDnsTlsSocketFactory.h"
+#include "PrivateDnsValidationObserver.h"
#include "resolv_private.h"
namespace android {
@@ -35,15 +36,14 @@
// 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 {
+class DnsTlsDispatcher : public PrivateDnsValidationObserver {
public:
- // Default constructor.
- DnsTlsDispatcher();
-
// Constructor with dependency injection for testing.
explicit DnsTlsDispatcher(std::unique_ptr<IDnsTlsSocketFactory> factory)
: mFactory(std::move(factory)) {}
+ static DnsTlsDispatcher& getInstance();
+
// 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.
@@ -61,7 +61,12 @@
const netdutils::Slice query, const netdutils::Slice ans,
int* _Nonnull resplen, bool* _Nonnull connectTriggered);
+ // Implement PrivateDnsValidationObserver.
+ void onValidationStateUpdate(const std::string&, Validation, uint32_t) override{};
+
private:
+ DnsTlsDispatcher();
+
// 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.
diff --git a/DnsTlsQueryMap.cpp b/DnsTlsQueryMap.cpp
index c4ae450..96191e3 100644
--- a/DnsTlsQueryMap.cpp
+++ b/DnsTlsQueryMap.cpp
@@ -20,9 +20,16 @@
#include <android-base/logging.h>
+#include "Experiments.h"
+
namespace android {
namespace net {
+DnsTlsQueryMap::DnsTlsQueryMap() {
+ mMaxTries = Experiments::getInstance()->getFlag("dot_maxtries", kMaxTries);
+ if (mMaxTries < 1) mMaxTries = 1;
+}
+
std::unique_ptr<DnsTlsQueryMap::QueryFuture> DnsTlsQueryMap::recordQuery(
const netdutils::Slice query) {
std::lock_guard guard(mLock);
@@ -67,7 +74,7 @@
std::lock_guard guard(mLock);
for (auto it = mQueries.begin(); it != mQueries.end();) {
auto& p = it->second;
- if (p.tries >= kMaxTries) {
+ if (p.tries >= mMaxTries) {
expire(&p);
it = mQueries.erase(it);
} else {
diff --git a/DnsTlsQueryMap.h b/DnsTlsQueryMap.h
index f744234..f3ff963 100644
--- a/DnsTlsQueryMap.h
+++ b/DnsTlsQueryMap.h
@@ -36,6 +36,8 @@
public:
enum class Response : uint8_t { success, network_error, limit_error, internal_error };
+ DnsTlsQueryMap();
+
struct Query {
// The new ID number assigned to this query.
uint16_t newId;
@@ -80,6 +82,7 @@
// The maximum number of times we will send a query before abandoning it.
static constexpr int kMaxTries = 3;
+ int mMaxTries;
private:
std::mutex mLock;
diff --git a/DnsTlsServer.cpp b/DnsTlsServer.cpp
index 4e94488..89ea841 100644
--- a/DnsTlsServer.cpp
+++ b/DnsTlsServer.cpp
@@ -18,6 +18,8 @@
#include <algorithm>
+#include <netdutils/InternetAddresses.h>
+
namespace {
// Returns a tuple of references to the elements of a.
@@ -109,7 +111,7 @@
// Returns a tuple of references to the elements of s.
auto make_tie(const DnsTlsServer& s) {
- return std::tie(s.ss, s.name, s.protocol, s.connectTimeout);
+ return std::tie(s.ss, s.name, s.protocol);
}
bool DnsTlsServer::operator <(const DnsTlsServer& other) const {
@@ -124,5 +126,9 @@
return !name.empty();
}
+std::string DnsTlsServer::toIpString() const {
+ return netdutils::IPSockAddr::toIPSockAddr(ss).ip().toString();
+}
+
} // namespace net
} // namespace android
diff --git a/DnsTlsServer.h b/DnsTlsServer.h
index 8484b98..67b0012 100644
--- a/DnsTlsServer.h
+++ b/DnsTlsServer.h
@@ -16,7 +16,6 @@
#pragma once
-#include <chrono>
#include <set>
#include <string>
#include <vector>
@@ -25,6 +24,8 @@
#include <params.h>
+#include "PrivateDnsCommon.h"
+
namespace android {
namespace net {
@@ -38,31 +39,48 @@
DnsTlsServer(const sockaddr_storage& ss) : ss(ss) {}
// The server location, including IP and port.
+ // TODO: make it const.
sockaddr_storage ss = {};
// The server's hostname. If this string is nonempty, the server must present a
// certificate that indicates this name and has a valid chain to a trusted root CA.
+ // TODO: make it const.
std::string name;
// The certificate of the CA that signed the server's certificate.
// It is used to store temporary test CA certificate for internal tests.
+ // TODO: make it const.
std::string certificate;
// Placeholder. More protocols might be defined in the future.
+ // TODO: make it const.
int protocol = IPPROTO_TCP;
- // The time to wait for the attempt on connecting to the server.
- // Set the default value 127 seconds to be consistent with TCP connect timeout.
- // (presume net.ipv4.tcp_syn_retries = 6)
- static constexpr std::chrono::milliseconds kDotConnectTimeoutMs =
- std::chrono::milliseconds(127 * 1000);
- std::chrono::milliseconds connectTimeout = kDotConnectTimeoutMs;
-
// Exact comparison of DnsTlsServer objects
bool operator<(const DnsTlsServer& other) const;
bool operator==(const DnsTlsServer& other) const;
bool wasExplicitlyConfigured() const;
+ std::string toIpString() const;
+
+ Validation validationState() const { return mValidation; }
+ void setValidationState(Validation val) { mValidation = val; }
+
+ // The socket mark used for validation.
+ // Note that the mark of a connection to which the DnsResolver sends app's DNS requests can
+ // be different.
+ // TODO: make it const.
+ uint32_t mark = 0;
+
+ // Return whether or not the server can be used for a network. It depends on
+ // the resolver configuration.
+ bool active() const { return mActive; }
+ void setActive(bool val) { mActive = val; }
+
+ private:
+ // State, unrelated to the comparison of DnsTlsServer objects.
+ Validation mValidation = Validation::unknown_server;
+ bool mActive = false;
};
// This comparison only checks the IP address. It ignores ports, names, and fingerprints.
diff --git a/DnsTlsSocket.cpp b/DnsTlsSocket.cpp
index 19bc139..ad12316 100644
--- a/DnsTlsSocket.cpp
+++ b/DnsTlsSocket.cpp
@@ -37,12 +37,14 @@
#include <netdutils/SocketOption.h>
#include <netdutils/ThreadUtil.h>
+#include "Experiments.h"
#include "netd_resolv/resolv.h"
#include "private/android_filesystem_config.h" // AID_DNS
#include "resolv_private.h"
namespace android {
+using android::net::Experiments;
using base::StringPrintf;
using netdutils::enableSockopt;
using netdutils::enableTcpKeepAlives;
@@ -81,7 +83,7 @@
mSslFd.reset(socket(mServer.ss.ss_family, type, mServer.protocol));
if (mSslFd.get() == -1) {
- LOG(ERROR) << "Failed to create socket";
+ PLOG(ERROR) << "Failed to create socket";
return Status(errno);
}
@@ -89,9 +91,10 @@
const socklen_t len = sizeof(mMark);
if (setsockopt(mSslFd.get(), SOL_SOCKET, SO_MARK, &mMark, len) == -1) {
- LOG(ERROR) << "Failed to set socket mark";
+ const int err = errno;
+ PLOG(ERROR) << "Failed to set socket mark";
mSslFd.reset();
- return Status(errno);
+ return Status(err);
}
const Status tfo = enableSockopt(mSslFd.get(), SOL_TCP, TCP_FASTOPEN_CONNECT);
@@ -105,9 +108,10 @@
if (connect(mSslFd.get(), reinterpret_cast<const struct sockaddr *>(&mServer.ss),
sizeof(mServer.ss)) != 0 &&
errno != EINPROGRESS) {
- LOG(DEBUG) << "Socket failed to connect";
+ const int err = errno;
+ PLOG(ERROR) << "Socket failed to connect";
mSslFd.reset();
- return Status(errno);
+ return Status(err);
}
return netdutils::status::ok;
@@ -169,17 +173,42 @@
// Enable session cache
mCache->prepareSslContext(mSslCtx.get());
- // Connect
- Status status = tcpConnect();
- if (!status.ok()) {
- return false;
- }
- mSsl = sslConnect(mSslFd.get());
- if (!mSsl) {
- return false;
- }
-
mEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
+ mShutdownEvent.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
+
+ const Experiments* const instance = Experiments::getInstance();
+ mConnectTimeoutMs = instance->getFlag("dot_connect_timeout_ms", kDotConnectTimeoutMs);
+ if (mConnectTimeoutMs < 1000) mConnectTimeoutMs = 1000;
+
+ mAsyncHandshake = instance->getFlag("dot_async_handshake", 0);
+ LOG(DEBUG) << "DnsTlsSocket is initialized with { mConnectTimeoutMs: " << mConnectTimeoutMs
+ << ", mAsyncHandshake: " << mAsyncHandshake << " }";
+
+ transitionState(State::UNINITIALIZED, State::INITIALIZED);
+
+ return true;
+}
+
+bool DnsTlsSocket::startHandshake() {
+ std::lock_guard guard(mLock);
+ if (mState != State::INITIALIZED) {
+ LOG(ERROR) << "Calling startHandshake in unexpected state " << static_cast<int>(mState);
+ return false;
+ }
+ transitionState(State::INITIALIZED, State::CONNECTING);
+
+ if (!mAsyncHandshake) {
+ if (Status status = tcpConnect(); !status.ok()) {
+ transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+ LOG(WARNING) << "TCP Handshake failed: " << status.code();
+ return false;
+ }
+ if (mSsl = sslConnect(mSslFd.get()); !mSsl) {
+ transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+ LOG(WARNING) << "TLS Handshake failed";
+ return false;
+ }
+ }
// Start the I/O loop.
mLoopThread.reset(new std::thread(&DnsTlsSocket::loop, this));
@@ -187,7 +216,7 @@
return true;
}
-bssl::UniquePtr<SSL> DnsTlsSocket::sslConnect(int fd) {
+bssl::UniquePtr<SSL> DnsTlsSocket::prepareForSslConnect(int fd) {
if (!mSslCtx) {
LOG(ERROR) << "Internal error: context is null in sslConnect";
return nullptr;
@@ -230,6 +259,15 @@
LOG(DEBUG) << "No session available";
}
+ return ssl;
+}
+
+bssl::UniquePtr<SSL> DnsTlsSocket::sslConnect(int fd) {
+ bssl::UniquePtr<SSL> ssl;
+ if (ssl = prepareForSslConnect(fd); !ssl) {
+ return nullptr;
+ }
+
for (;;) {
LOG(DEBUG) << " Calling SSL_connect with mark 0x" << std::hex << mMark;
int ret = SSL_connect(ssl.get());
@@ -242,7 +280,7 @@
// SSL_ERROR_WANT_READ is returned because the application data has been sent during
// the TCP connection handshake, the device is waiting for the SSL handshake reply
// from the server.
- if (int err = waitForReading(fd, mServer.connectTimeout.count()); err <= 0) {
+ if (int err = waitForReading(fd, mConnectTimeoutMs); err <= 0) {
PLOG(WARNING) << "SSL_connect read error " << err << ", mark 0x" << std::hex
<< mMark;
return nullptr;
@@ -251,7 +289,7 @@
case SSL_ERROR_WANT_WRITE:
// If no application data is sent during the TCP connection handshake, the
// device is waiting for the connection established to perform SSL handshake.
- if (int err = waitForWriting(fd, mServer.connectTimeout.count()); err <= 0) {
+ if (int err = waitForWriting(fd, mConnectTimeoutMs); err <= 0) {
PLOG(WARNING) << "SSL_connect write error " << err << ", mark 0x" << std::hex
<< mMark;
return nullptr;
@@ -269,6 +307,59 @@
return ssl;
}
+bssl::UniquePtr<SSL> DnsTlsSocket::sslConnectV2(int fd) {
+ bssl::UniquePtr<SSL> ssl;
+ if (ssl = prepareForSslConnect(fd); !ssl) {
+ return nullptr;
+ }
+
+ for (;;) {
+ LOG(DEBUG) << " Calling SSL_connect with mark 0x" << std::hex << mMark;
+ int ret = SSL_connect(ssl.get());
+ LOG(DEBUG) << " SSL_connect returned " << ret << " with mark 0x" << std::hex << mMark;
+ if (ret == 1) break; // SSL handshake complete;
+
+ enum { SSLFD = 0, EVENTFD = 1 };
+ pollfd fds[2] = {
+ {.fd = mSslFd.get(), .events = 0},
+ {.fd = mShutdownEvent.get(), .events = POLLIN},
+ };
+
+ const int ssl_err = SSL_get_error(ssl.get(), ret);
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ fds[SSLFD].events = POLLIN;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ fds[SSLFD].events = POLLOUT;
+ break;
+ default:
+ PLOG(WARNING) << "SSL_connect ssl error =" << ssl_err << ", mark 0x" << std::hex
+ << mMark;
+ return nullptr;
+ }
+
+ int n = TEMP_FAILURE_RETRY(poll(fds, std::size(fds), mConnectTimeoutMs));
+ if (n <= 0) {
+ PLOG(WARNING) << ((n == 0) ? "handshake timeout" : "Poll failed");
+ return nullptr;
+ }
+
+ if (fds[EVENTFD].revents & (POLLIN | POLLERR)) {
+ LOG(WARNING) << "Got shutdown request during handshake";
+ return nullptr;
+ }
+ if (fds[SSLFD].revents & POLLERR) {
+ LOG(WARNING) << "Got POLLERR on SSLFD during handshake";
+ return nullptr;
+ }
+ }
+
+ LOG(DEBUG) << mMark << " handshake complete";
+
+ return ssl;
+}
+
void DnsTlsSocket::sslDisconnect() {
if (mSsl) {
SSL_shutdown(mSsl.get());
@@ -310,6 +401,25 @@
const int timeout_msecs = DnsTlsSocket::kIdleTimeout.count() * 1000;
setThreadName(StringPrintf("TlsListen_%u", mMark & 0xffff).c_str());
+
+ if (mAsyncHandshake) {
+ if (Status status = tcpConnect(); !status.ok()) {
+ LOG(WARNING) << "TCP Handshake failed: " << status.code();
+ mObserver->onClosed();
+ transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+ return;
+ }
+ if (mSsl = sslConnectV2(mSslFd.get()); !mSsl) {
+ LOG(WARNING) << "TLS Handshake failed";
+ mObserver->onClosed();
+ transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+ return;
+ }
+ LOG(DEBUG) << "Handshaking succeeded";
+ }
+
+ transitionState(State::CONNECTING, State::CONNECTED);
+
while (true) {
// poll() ignores negative fds
struct pollfd fds[2] = { { .fd = -1 }, { .fd = -1 } };
@@ -336,12 +446,24 @@
break;
}
if (s < 0) {
- LOG(DEBUG) << "Poll failed: " << errno;
+ PLOG(DEBUG) << "Poll failed";
break;
}
if (fds[SSLFD].revents & (POLLIN | POLLERR | POLLHUP)) {
- if (!readResponse()) {
- LOG(DEBUG) << "SSL remote close or read error.";
+ bool readFailed = false;
+
+ // readResponse() only reads one DNS (and consumes exact bytes) from ssl.
+ // Keep doing so until ssl has no pending data.
+ // TODO: readResponse() can block until it reads a complete DNS response. Consider
+ // refactoring it to not get blocked in any case.
+ do {
+ if (!readResponse()) {
+ LOG(DEBUG) << "SSL remote close or read error.";
+ readFailed = true;
+ }
+ } while (SSL_pending(mSsl.get()) > 0 && !readFailed);
+
+ if (readFailed) {
break;
}
}
@@ -379,6 +501,7 @@
sslDisconnect();
LOG(DEBUG) << "Calling onClosed";
mObserver->onClosed();
+ transitionState(State::CONNECTED, State::WAIT_FOR_DELETE);
LOG(DEBUG) << "Ending loop";
}
@@ -426,6 +549,11 @@
// Write a negative number to the eventfd. This triggers an immediate shutdown.
incrementEventFd(INT64_MIN);
}
+ if (mShutdownEvent != -1) {
+ if (eventfd_write(mShutdownEvent.get(), INT64_MIN) == -1) {
+ PLOG(ERROR) << "Failed to write to mShutdownEvent";
+ }
+ }
}
bool DnsTlsSocket::incrementEventFd(const int64_t count) {
@@ -441,6 +569,15 @@
return true;
}
+void DnsTlsSocket::transitionState(State from, State to) {
+ if (mState != from) {
+ LOG(WARNING) << "BUG: transitioning from an unexpected state " << static_cast<int>(mState)
+ << ", expect: from " << static_cast<int>(from) << " to "
+ << static_cast<int>(to);
+ }
+ mState = to;
+}
+
// Read exactly len bytes into buffer or fail with an SSL error code
int DnsTlsSocket::sslRead(const Slice buffer, bool wait) {
size_t remaining = buffer.size();
diff --git a/DnsTlsSocket.h b/DnsTlsSocket.h
index 1439ea9..f6736f8 100644
--- a/DnsTlsSocket.h
+++ b/DnsTlsSocket.h
@@ -44,18 +44,55 @@
// or the destructor in a callback. Doing so will result in deadlocks.
// This class may call the observer at any time after initialize(), until the destructor
// returns (but not after).
+//
+// Calls to IDnsTlsSocketObserver in a DnsTlsSocket life cycle:
+//
+// UNINITIALIZED
+// |
+// v
+// INITIALIZED
+// |
+// v
+// +----CONNECTING------+
+// Handshake fails | | Handshake succeeds
+// (onClose() when | |
+// mAsyncHandshake is set) | v
+// | +---> CONNECTED --+
+// | | | |
+// | +-----------+ | Idle timeout
+// | Send/Recv queries | onClose()
+// | onResponse() |
+// | |
+// | |
+// +--> WAIT_FOR_DELETE <-----+
+//
+//
+// TODO: Add onHandshakeFinished() for handshake results.
class DnsTlsSocket : public IDnsTlsSocket {
public:
+ enum class State {
+ UNINITIALIZED,
+ INITIALIZED,
+ CONNECTING,
+ CONNECTED,
+ WAIT_FOR_DELETE,
+ };
+
DnsTlsSocket(const DnsTlsServer& server, unsigned mark,
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.
+ // Creates the SSL context for this session. Returns false on failure.
// This method should be called after construction and before use of a DnsTlsSocket.
// Only call this method once per DnsTlsSocket.
bool initialize() EXCLUDES(mLock);
+ // If async handshake is enabled, this function simply signals a handshake request, and the
+ // handshake will be performed in the loop thread; otherwise, if async handshake is disabled,
+ // this function performs the handshake and returns after the handshake finishes.
+ bool startHandshake() EXCLUDES(mLock);
+
// Send a query on the provided SSL socket. |query| contains
// the body of a query, not including the ID header. This function will typically return before
// the query is actually sent. If this function fails, DnsTlsSocketObserver will be
@@ -77,10 +114,16 @@
// On error, returns the errno.
netdutils::Status tcpConnect() REQUIRES(mLock);
+ bssl::UniquePtr<SSL> prepareForSslConnect(int fd) REQUIRES(mLock);
+
// Connect an SSL session on the provided socket. If connection fails, closing the
// socket remains the caller's responsibility.
bssl::UniquePtr<SSL> sslConnect(int fd) REQUIRES(mLock);
+ // Connect an SSL session on the provided socket. This is an interruptible version
+ // which allows to terminate connection handshake any time.
+ bssl::UniquePtr<SSL> sslConnectV2(int fd) REQUIRES(mLock);
+
// Disconnect the SSL session and close the socket.
void sslDisconnect() REQUIRES(mLock);
@@ -94,6 +137,9 @@
int sslRead(const netdutils::Slice buffer, bool wait) REQUIRES(mLock);
bool sendQuery(const std::vector<uint8_t>& buf) REQUIRES(mLock);
+
+ // Read one DNS response. It can potentially block until reading the exact bytes of
+ // the response.
bool readResponse() REQUIRES(mLock);
// It is only used for DNS-OVER-TLS internal test.
@@ -108,6 +154,9 @@
// This function sends a message to the loop thread by incrementing mEventFd.
bool incrementEventFd(int64_t count) EXCLUDES(mLock);
+ // Transition the state from expected state |from| to new state |to|.
+ void transitionState(State from, State to) REQUIRES(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;
@@ -119,8 +168,14 @@
// 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.
+ // Note that: data starts being read from the eventfd when the state is CONNECTED.
base::unique_fd mEventFd;
+ // An eventfd used to listen to shutdown requests when the state is CONNECTING.
+ // TODO: let |mEventFd| exclusively handle query requests, and let |mShutdownEvent| exclusively
+ // handle shutdown requests.
+ base::unique_fd mShutdownEvent;
+
// SSL Socket fields.
bssl::UniquePtr<SSL_CTX> mSslCtx GUARDED_BY(mLock);
base::unique_fd mSslFd GUARDED_BY(mLock);
@@ -131,6 +186,20 @@
const DnsTlsServer mServer;
IDnsTlsSocketObserver* _Nonnull const mObserver;
DnsTlsSessionCache* _Nonnull const mCache;
+ State mState GUARDED_BY(mLock) = State::UNINITIALIZED;
+
+ // If true, defer the handshake to the loop thread; otherwise, run the handshake on caller's
+ // thread (the call to startHandshake()).
+ bool mAsyncHandshake GUARDED_BY(mLock) = false;
+
+ // The time to wait for the attempt on connecting to the server.
+ // Set the default value 127 seconds to be consistent with TCP connect timeout.
+ // (presume net.ipv4.tcp_syn_retries = 6)
+ static constexpr int kDotConnectTimeoutMs = 127 * 1000;
+ int mConnectTimeoutMs;
+
+ // For testing.
+ friend class DnsTlsSocketTest;
};
} // end of namespace net
diff --git a/DnsTlsTransport.cpp b/DnsTlsTransport.cpp
index d0098c2..a2964cc 100644
--- a/DnsTlsTransport.cpp
+++ b/DnsTlsTransport.cpp
@@ -70,9 +70,14 @@
void DnsTlsTransport::doConnect() {
LOG(DEBUG) << "Constructing new socket";
mSocket = mFactory->createDnsTlsSocket(mServer, mMark, this, &mCache);
+
+ bool success = true;
+ if (mSocket.get() == nullptr || !mSocket->startHandshake()) {
+ success = false;
+ }
mConnectCounter++;
- if (mSocket) {
+ if (success) {
auto queries = mQueries.getAll();
LOG(DEBUG) << "Initialization succeeded. Reissuing " << queries.size() << " queries.";
for(auto& q : queries) {
@@ -153,8 +158,8 @@
// 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, uint32_t mark) {
- LOG(DEBUG) << "Beginning validation on " << netid;
+bool DnsTlsTransport::validate(const DnsTlsServer& server, uint32_t mark) {
+ LOG(DEBUG) << "Beginning validation with mark " << std::hex << mark;
// 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.
static const char kDnsSafeChars[] =
@@ -190,7 +195,7 @@
DnsTlsTransport transport(server, mark, &factory);
auto r = transport.query(netdutils::Slice(query, qlen)).get();
if (r.code != Response::success) {
- LOG(DEBUG) << "query failed";
+ LOG(WARNING) << "query failed";
return false;
}
@@ -207,7 +212,7 @@
}
const int ancount = (recvbuf[6] << 8) | recvbuf[7];
- LOG(DEBUG) << netid << " answer count: " << ancount;
+ LOG(DEBUG) << "answer count: " << ancount;
// TODO: Further validate the response contents (check for valid AAAA record, ...).
// Note that currently, integration tests rely on this function accepting a
diff --git a/DnsTlsTransport.h b/DnsTlsTransport.h
index c0fbaef..7b7f81f 100644
--- a/DnsTlsTransport.h
+++ b/DnsTlsTransport.h
@@ -52,10 +52,10 @@
// Given a |query|, this method sends it to the server and returns the result asynchronously.
std::future<Result> query(const netdutils::Slice query) EXCLUDES(mLock);
- // Check that a given TLS server is fully working on the specified netid.
+ // Check that a given TLS server is fully working with a specified mark.
// 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, uint32_t mark);
+ static bool validate(const DnsTlsServer& server, uint32_t mark);
int getConnectCounter() const EXCLUDES(mLock);
diff --git a/Experiments.h b/Experiments.h
index e267e50..bc03822 100644
--- a/Experiments.h
+++ b/Experiments.h
@@ -47,10 +47,12 @@
mutable std::mutex mMutex;
std::unordered_map<std::string_view, int> mFlagsMapInt GUARDED_BY(mMutex);
// TODO: Migrate other experiment flags to here.
- // (retry_count, retransmission_time_interval, dot_connect_timeout_ms)
+ // (retry_count, retransmission_time_interval)
static constexpr const char* const kExperimentFlagKeyList[] = {
- "keep_listening_udp", "parallel_lookup", "parallel_lookup_sleep_time",
- "sort_nameservers"};
+ "keep_listening_udp", "parallel_lookup_release", "parallel_lookup_sleep_time",
+ "sort_nameservers", "dot_async_handshake", "dot_connect_timeout_ms",
+ "dot_maxtries",
+ };
// This value is used in updateInternal as the default value if any flags can't be found.
static constexpr int kFlagIntDefault = INT_MIN;
// For testing.
diff --git a/IDnsTlsSocket.h b/IDnsTlsSocket.h
index 0f2800e..8d2be8a 100644
--- a/IDnsTlsSocket.h
+++ b/IDnsTlsSocket.h
@@ -40,6 +40,8 @@
// notified that the socket is closed.
// Note that a true return value indicates successful sending, not receipt of a response.
virtual bool query(uint16_t id, const netdutils::Slice query) = 0;
+
+ virtual bool startHandshake() = 0;
};
} // end of namespace net
diff --git a/LockedQueue.h b/LockedQueue.h
index 0481eda..8694f2d 100644
--- a/LockedQueue.h
+++ b/LockedQueue.h
@@ -46,6 +46,30 @@
std::deque<T> mQueue GUARDED_BY(mLock);
};
+template <typename T>
+class LockedRingBuffer {
+ public:
+ explicit LockedRingBuffer(size_t size) : mCapacity(size) {}
+
+ void push(T&& record) {
+ std::lock_guard guard(mLock);
+ mQueue.push_back(std::move(record));
+ if (mQueue.size() > mCapacity) {
+ mQueue.pop_front();
+ }
+ }
+
+ std::deque<T> copy() const {
+ std::lock_guard guard(mLock);
+ return mQueue;
+ }
+
+ private:
+ mutable std::mutex mLock;
+ const size_t mCapacity;
+ std::deque<T> mQueue GUARDED_BY(mLock);
+};
+
} // end of namespace net
} // end of namespace android
diff --git a/OperationLimiter.h b/OperationLimiter.h
new file mode 100644
index 0000000..1fa1bf2
--- /dev/null
+++ b/OperationLimiter.h
@@ -0,0 +1,120 @@
+/*
+ * 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_OPERATIONLIMITER_H
+#define NETUTILS_OPERATIONLIMITER_H
+
+#include <mutex>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+
+namespace android {
+namespace netdutils {
+
+// Tracks the number of operations in progress on behalf of a particular key or
+// ID, rejecting further attempts to start new operations after a configurable
+// limit has been reached.
+//
+// The intended usage pattern is:
+// OperationLimiter<UserId> connections_per_user;
+// ...
+// int connectToSomeResource(int user) {
+// if (!connections_per_user.start(user)) return TRY_AGAIN_LATER;
+// // ...do expensive work here...
+// connections_per_user.finish(user);
+// }
+//
+// This class is thread-safe.
+template <typename KeyType>
+class OperationLimiter {
+ public:
+ OperationLimiter(int limitPerKey, int globalLimit = INT_MAX)
+ : mLimitPerKey(limitPerKey), mGlobalLimit(globalLimit) {}
+
+ ~OperationLimiter() {
+ DCHECK(mCounters.empty()) << "Destroying OperationLimiter with active operations";
+ }
+
+ // Returns false if |key| has reached the maximum number of concurrent operations,
+ // or if the global limit has been reached. Otherwise, increments the counter and returns true.
+ //
+ // Note: each successful start(key) must be matched by exactly one call to
+ // finish(key).
+ bool start(KeyType key) EXCLUDES(mMutex) {
+ std::lock_guard lock(mMutex);
+
+ if (mGlobalCounter >= mGlobalLimit) {
+ // Oh, no!
+ return false;
+ }
+
+ auto& cnt = mCounters[key]; // operator[] creates new entries as needed.
+ if (cnt >= mLimitPerKey) {
+ // Oh, no!
+ return false;
+ }
+
+ ++cnt;
+ ++mGlobalCounter;
+ return true;
+ }
+
+ // Decrements the number of operations in progress accounted to |key|.
+ // See usage notes on start().
+ void finish(KeyType key) EXCLUDES(mMutex) {
+ std::lock_guard lock(mMutex);
+
+ --mGlobalCounter;
+ if (mGlobalCounter < 0) {
+ LOG(FATAL_WITHOUT_ABORT) << "Global operations counter going negative, this is a bug.";
+ return;
+ }
+
+ auto it = mCounters.find(key);
+ if (it == mCounters.end()) {
+ LOG(FATAL_WITHOUT_ABORT) << "Decremented non-existent counter for key=" << key;
+ return;
+ }
+ auto& cnt = it->second;
+ --cnt;
+ if (cnt <= 0) {
+ // Cleanup counters once they drop down to zero.
+ mCounters.erase(it);
+ }
+ }
+
+ private:
+ // Protects access to the map below.
+ std::mutex mMutex;
+
+ // Tracks the number of outstanding queries by key.
+ std::unordered_map<KeyType, int> mCounters GUARDED_BY(mMutex);
+
+ int mGlobalCounter GUARDED_BY(mMutex) = 0;
+
+ // Maximum number of outstanding queries from a single key.
+ const int mLimitPerKey;
+
+ // Maximum number of outstanding queries, globally.
+ const int mGlobalLimit;
+};
+
+} // namespace netdutils
+} // namespace android
+
+#endif // NETUTILS_OPERATIONLIMITER_H
diff --git a/OperationLimiterTest.cpp b/OperationLimiterTest.cpp
new file mode 100644
index 0000000..9d2dadb
--- /dev/null
+++ b/OperationLimiterTest.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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 "OperationLimiter.h"
+
+#include <gtest/gtest-spi.h>
+
+namespace android {
+namespace netdutils {
+
+TEST(OperationLimiter, limits) {
+ OperationLimiter<int> limiter(3);
+
+ EXPECT_TRUE(limiter.start(42));
+ EXPECT_TRUE(limiter.start(42));
+ EXPECT_TRUE(limiter.start(42));
+
+ // Limit reached... calling any number of times should have no effect.
+ EXPECT_FALSE(limiter.start(42));
+ EXPECT_FALSE(limiter.start(42));
+ EXPECT_FALSE(limiter.start(42));
+
+ // Finishing a single operations is enough for starting a new one...
+ limiter.finish(42);
+ EXPECT_TRUE(limiter.start(42));
+
+ // ...but not two!
+ EXPECT_FALSE(limiter.start(42));
+
+ // Different ids should still have quota...
+ EXPECT_TRUE(limiter.start(666));
+ limiter.finish(666);
+
+ // Finish all pending operations
+ limiter.finish(42);
+ limiter.finish(42);
+ limiter.finish(42);
+}
+
+TEST(OperationLimiter, finishWithoutStart) {
+ OperationLimiter<int> limiter(1);
+
+ // Will output a LOG(FATAL_WITHOUT_ABORT), but we have no way to probe this.
+ limiter.finish(42);
+
+ // This will ensure that the finish() above didn't set a negative value.
+ EXPECT_TRUE(limiter.start(42));
+ EXPECT_FALSE(limiter.start(42));
+}
+
+TEST(OperationLimiter, destroyWithActiveOperations) {
+ // The death message doesn't seem to be captured on Android.
+ EXPECT_DEBUG_DEATH(
+ {
+ OperationLimiter<int> limiter(3);
+ limiter.start(42);
+ },
+ "" /* "active operations */);
+}
+
+} // namespace netdutils
+} // namespace android
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 27eac94..a1ccc2e 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,8 @@
[Builtin Hooks]
clang_format = true
commit_msg_test_field = false
+rustfmt = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+rustfmt = --config-path=rustfmt.toml
diff --git a/PrivateDnsCommon.h b/PrivateDnsCommon.h
new file mode 100644
index 0000000..229e97c
--- /dev/null
+++ b/PrivateDnsCommon.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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
+
+namespace android::net {
+
+// Validation status of a private DNS server on a specific netId.
+enum class Validation : uint8_t {
+ in_process,
+ success,
+ success_but_expired,
+ fail,
+ unknown_server,
+ unknown_netid,
+};
+
+// The private DNS mode on a specific netId.
+enum class PrivateDnsMode : uint8_t {
+ OFF,
+ OPPORTUNISTIC,
+ STRICT,
+};
+
+constexpr const char* validationStatusToString(Validation value) {
+ switch (value) {
+ case Validation::in_process:
+ return "in_process";
+ case Validation::success:
+ return "success";
+ case Validation::success_but_expired:
+ return "success_but_expired";
+ case Validation::fail:
+ return "fail";
+ case Validation::unknown_server:
+ return "unknown_server";
+ case Validation::unknown_netid:
+ return "unknown_netid";
+ default:
+ return "unknown_status";
+ }
+}
+
+constexpr const char* getPrivateDnsModeString(PrivateDnsMode mode) {
+ switch (mode) {
+ case PrivateDnsMode::OFF:
+ return "OFF";
+ case PrivateDnsMode::OPPORTUNISTIC:
+ return "OPPORTUNISTIC";
+ case PrivateDnsMode::STRICT:
+ return "STRICT";
+ }
+}
+
+} // namespace android::net
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
index 7d9e82a..8f3b836 100644
--- a/PrivateDnsConfiguration.cpp
+++ b/PrivateDnsConfiguration.cpp
@@ -18,9 +18,9 @@
#include "PrivateDnsConfiguration.h"
+#include <android-base/format.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
-#include <netdb.h>
#include <netdutils/ThreadUtil.h>
#include <sys/socket.h>
@@ -28,7 +28,6 @@
#include "ResolverEventReporter.h"
#include "netd_resolv/resolv.h"
#include "netdutils/BackoffSequence.h"
-#include "resolv_cache.h"
#include "util.h"
using android::base::StringPrintf;
@@ -38,22 +37,6 @@
namespace android {
namespace net {
-namespace {
-
-milliseconds getExperimentTimeout(const std::string& flagName, const milliseconds defaultValue) {
- int val = getExperimentFlagInt(flagName, defaultValue.count());
- return milliseconds((val < 1000) ? 1000 : val);
-}
-
-} // 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);
-}
-
bool parseServer(const char* server, sockaddr_storage* parsed) {
addrinfo hints = {
.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
@@ -79,7 +62,7 @@
<< ", " << servers.size() << ", " << name << ")";
// Parse the list of servers that has been passed in
- std::set<DnsTlsServer> tlsServers;
+ PrivateDnsTracker tmp;
for (const auto& s : servers) {
sockaddr_storage parsed;
if (!parseServer(s.c_str(), &parsed)) {
@@ -88,59 +71,48 @@
DnsTlsServer server(parsed);
server.name = name;
server.certificate = caCert;
- server.connectTimeout =
- getExperimentTimeout("dot_connect_timeout_ms", DnsTlsServer::kDotConnectTimeoutMs);
- tlsServers.insert(server);
- LOG(DEBUG) << "Set DoT connect timeout " << server.connectTimeout.count() << "ms for " << s;
+ server.mark = mark;
+ tmp[ServerIdentity(server)] = server;
}
std::lock_guard guard(mPrivateDnsLock);
if (!name.empty()) {
mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
- } else if (!tlsServers.empty()) {
+ } else if (!tmp.empty()) {
mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
} else {
mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
mPrivateDnsTransports.erase(netId);
- resolv_stats_set_servers_for_dot(netId, {});
- mPrivateDnsValidateThreads.erase(netId);
- // TODO: As mPrivateDnsValidateThreads is reset, validation threads which haven't yet
- // finished are considered outdated. Consider signaling the outdated validation threads to
- // stop them from updating the state of PrivateDnsConfiguration (possibly disallow them to
- // report onPrivateDnsValidationEvent).
+ // TODO: signal validation threads to stop.
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) {
- LOG(ERROR) << "Memory error while recording private DNS for netId " << netId;
- return -ENOMEM;
- }
- }
- auto& tracker = netPair->second;
+ auto& tracker = mPrivateDnsTransports[netId];
- // 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 the servers if not contained in tracker.
+ for (const auto& [identity, server] : tmp) {
+ if (tracker.find(identity) == tracker.end()) {
+ tracker[identity] = server;
}
}
- // 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);
+ for (auto& [identity, server] : tracker) {
+ const bool active = tmp.find(identity) != tmp.end();
+ server.setActive(active);
+
+ // For simplicity, deem the validation result of inactive servers as unreliable.
+ if (!server.active() && server.validationState() == Validation::success) {
+ updateServerState(identity, Validation::success_but_expired, netId);
+ }
+
+ if (needsValidation(server)) {
+ updateServerState(identity, Validation::in_process, netId);
+ startValidation(server, netId);
}
}
- return resolv_stats_set_servers_for_dot(netId, servers);
+ return 0;
}
PrivateDnsStatus PrivateDnsConfiguration::getStatus(unsigned netId) {
@@ -153,8 +125,10 @@
const auto netPair = mPrivateDnsTransports.find(netId);
if (netPair != mPrivateDnsTransports.end()) {
- for (const auto& serverPair : netPair->second) {
- status.serversMap.emplace(serverPair.first, serverPair.second);
+ for (const auto& [_, server] : netPair->second) {
+ if (server.active()) {
+ status.serversMap.emplace(server, server.validationState());
+ }
}
}
@@ -166,22 +140,43 @@
std::lock_guard guard(mPrivateDnsLock);
mPrivateDnsModes.erase(netId);
mPrivateDnsTransports.erase(netId);
- mPrivateDnsValidateThreads.erase(netId);
}
-void PrivateDnsConfiguration::validatePrivateDnsProvider(const DnsTlsServer& server,
- PrivateDnsTracker& tracker, unsigned netId,
- uint32_t mark) REQUIRES(mPrivateDnsLock) {
- tracker[server] = Validation::in_process;
- LOG(DEBUG) << "Server " << addrToString(&server.ss) << " marked as in_process on netId "
- << netId << ". Tracker now has size " << tracker.size();
- // This judge must be after "tracker[server] = Validation::in_process;"
- if (!needValidateThread(server, netId)) {
- return;
+bool PrivateDnsConfiguration::requestValidation(unsigned netId, const DnsTlsServer& server,
+ uint32_t mark) {
+ std::lock_guard guard(mPrivateDnsLock);
+ auto netPair = mPrivateDnsTransports.find(netId);
+ if (netPair == mPrivateDnsTransports.end()) {
+ return false;
}
+ auto& tracker = netPair->second;
+ const ServerIdentity identity = ServerIdentity(server);
+ auto it = tracker.find(identity);
+ if (it == tracker.end()) {
+ return false;
+ }
+
+ const DnsTlsServer& target = it->second;
+
+ if (!target.active()) return false;
+
+ if (target.validationState() != Validation::success) return false;
+
+ // Don't run the validation if |mark| (from android_net_context.dns_mark) is different.
+ // This is to protect validation from running on unexpected marks.
+ // Validation should be associated with a mark gotten by system permission.
+ if (target.mark != mark) return false;
+
+ updateServerState(identity, Validation::in_process, netId);
+ startValidation(target, netId);
+ return true;
+}
+
+void PrivateDnsConfiguration::startValidation(const DnsTlsServer& server, unsigned netId)
+ REQUIRES(mPrivateDnsLock) {
// Note that capturing |server| and |netId| in this lambda create copies.
- std::thread validate_thread([this, server, netId, mark] {
+ std::thread validate_thread([this, server, netId] {
setThreadName(StringPrintf("TlsVerify_%u", netId).c_str());
// cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
@@ -205,10 +200,11 @@
while (true) {
// ::validate() is a blocking call that performs network operations.
// It can take milliseconds to minutes, up to the SYN retry limit.
- LOG(WARNING) << "Validating DnsTlsServer on netId " << netId;
- const bool success = DnsTlsTransport::validate(server, netId, mark);
- LOG(DEBUG) << "validateDnsTlsServer returned " << success << " for "
- << addrToString(&server.ss);
+ LOG(WARNING) << "Validating DnsTlsServer " << server.toIpString() << " with mark 0x"
+ << std::hex << server.mark;
+ const bool success = DnsTlsTransport::validate(server, server.mark);
+ LOG(WARNING) << "validateDnsTlsServer returned " << success << " for "
+ << server.toIpString();
const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
if (!needs_reeval) {
@@ -216,12 +212,12 @@
}
if (backoff.hasNextTimeout()) {
+ // TODO: make the thread able to receive signals to shutdown early.
std::this_thread::sleep_for(backoff.getNextTimeout());
} else {
break;
}
}
- this->cleanValidateThreadTracker(server, netId);
});
validate_thread.detach();
}
@@ -230,18 +226,21 @@
bool success) {
constexpr bool NEEDS_REEVALUATION = true;
constexpr bool DONT_REEVALUATE = false;
+ const ServerIdentity identity = ServerIdentity(server);
std::lock_guard guard(mPrivateDnsLock);
auto netPair = mPrivateDnsTransports.find(netId);
if (netPair == mPrivateDnsTransports.end()) {
LOG(WARNING) << "netId " << netId << " was erased during private DNS validation";
+ notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
return DONT_REEVALUATE;
}
const auto mode = mPrivateDnsModes.find(netId);
if (mode == mPrivateDnsModes.end()) {
LOG(WARNING) << "netId " << netId << " has no private DNS validation mode";
+ notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
return DONT_REEVALUATE;
}
const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
@@ -250,104 +249,115 @@
(success || !modeDoesReevaluation) ? DONT_REEVALUATE : NEEDS_REEVALUATION;
auto& tracker = netPair->second;
- auto serverPair = tracker.find(server);
+ auto serverPair = tracker.find(identity);
if (serverPair == tracker.end()) {
- // TODO: Consider not adding this server to the tracker since this server is not expected
- // to be one of the private DNS servers for this network now. This could prevent this
- // server from being included when dumping status.
- LOG(WARNING) << "Server " << addrToString(&server.ss)
+ LOG(WARNING) << "Server " << server.toIpString()
<< " was removed during private DNS validation";
success = false;
reevaluationStatus = DONT_REEVALUATE;
- } else if (!(serverPair->first == server)) {
+ } else if (!(serverPair->second == server)) {
// TODO: It doesn't seem correct to overwrite the tracker entry for
// |server| down below in this circumstance... Fix this.
- LOG(WARNING) << "Server " << addrToString(&server.ss)
+ LOG(WARNING) << "Server " << server.toIpString()
<< " was changed during private DNS validation";
success = false;
reevaluationStatus = DONT_REEVALUATE;
+ } else if (!serverPair->second.active()) {
+ LOG(WARNING) << "Server " << server.toIpString() << " was removed from the configuration";
+ 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);
+ it->onPrivateDnsValidationEvent(netId, server.toIpString(), server.name, success);
}
LOG(DEBUG) << "Sent validation " << (success ? "success" : "failure") << " event on netId "
- << netId << " for " << addrToString(&server.ss) << " with hostname {"
- << server.name << "}";
+ << netId << " for " << server.toIpString() << " with hostname {" << server.name
+ << "}";
} else {
LOG(ERROR)
<< "Validation event not sent since no INetdEventListener receiver is available.";
}
if (success) {
- tracker[server] = Validation::success;
+ updateServerState(identity, Validation::success, netId);
} 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;
+ const auto result = (reevaluationStatus == NEEDS_REEVALUATION) ? Validation::in_process
+ : Validation::fail;
+ updateServerState(identity, result, netId);
}
LOG(WARNING) << "Validation " << (success ? "success" : "failed");
return reevaluationStatus;
}
-bool PrivateDnsConfiguration::needValidateThread(const DnsTlsServer& server, unsigned netId)
- REQUIRES(mPrivateDnsLock) {
- // Create the thread tracker if it was not present
- auto threadPair = mPrivateDnsValidateThreads.find(netId);
- if (threadPair == mPrivateDnsValidateThreads.end()) {
- // No thread tracker yet for this netId.
- bool added;
- std::tie(threadPair, added) = mPrivateDnsValidateThreads.emplace(netId, ThreadTracker());
- if (!added) {
- LOG(ERROR) << "Memory error while needValidateThread for netId " << netId;
- return true;
- }
+void PrivateDnsConfiguration::updateServerState(const ServerIdentity& identity, Validation state,
+ uint32_t netId) {
+ auto netPair = mPrivateDnsTransports.find(netId);
+ if (netPair == mPrivateDnsTransports.end()) {
+ notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
+ return;
}
- auto& threadTracker = threadPair->second;
- if (threadTracker.count(server)) {
- LOG(DEBUG) << "Server " << addrToString(&(server.ss))
- << " validate thread is already running. Thread tracker now has size "
- << threadTracker.size();
- return false;
- } else {
- threadTracker.insert(server);
- LOG(DEBUG) << "Server " << addrToString(&(server.ss))
- << " validate thread is not running. Thread tracker now has size "
- << threadTracker.size();
- return true;
+
+ auto& tracker = netPair->second;
+ if (tracker.find(identity) == tracker.end()) {
+ notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
+ return;
+ }
+
+ tracker[identity].setValidationState(state);
+ notifyValidationStateUpdate(identity.ip.toString(), state, netId);
+
+ RecordEntry record(netId, identity, state);
+ mPrivateDnsLog.push(std::move(record));
+}
+
+bool PrivateDnsConfiguration::needsValidation(const DnsTlsServer& server) {
+ // The server is not expected to be used on the network.
+ if (!server.active()) return false;
+
+ // The server is newly added.
+ if (server.validationState() == Validation::unknown_server) return true;
+
+ // The server has failed at least one validation attempt. Give it another try.
+ if (server.validationState() == Validation::fail) return true;
+
+ // The previous validation result might be unreliable.
+ if (server.validationState() == Validation::success_but_expired) return true;
+
+ return false;
+}
+
+void PrivateDnsConfiguration::setObserver(PrivateDnsValidationObserver* observer) {
+ std::lock_guard guard(mPrivateDnsLock);
+ mObserver = observer;
+}
+
+void PrivateDnsConfiguration::notifyValidationStateUpdate(const std::string& serverIp,
+ Validation validation,
+ uint32_t netId) const {
+ if (mObserver) {
+ mObserver->onValidationStateUpdate(serverIp, validation, netId);
}
}
-void PrivateDnsConfiguration::cleanValidateThreadTracker(const DnsTlsServer& server,
- unsigned netId) {
- std::lock_guard<std::mutex> guard(mPrivateDnsLock);
- LOG(DEBUG) << "cleanValidateThreadTracker Server " << addrToString(&(server.ss))
- << " validate thread is stopped.";
+void PrivateDnsConfiguration::dump(netdutils::DumpWriter& dw) const {
+ dw.println("PrivateDnsLog:");
+ netdutils::ScopedIndent indentStats(dw);
- auto threadPair = mPrivateDnsValidateThreads.find(netId);
- if (threadPair != mPrivateDnsValidateThreads.end()) {
- auto& threadTracker = threadPair->second;
- threadTracker.erase(server);
- LOG(DEBUG) << "Server " << addrToString(&(server.ss))
- << " validate thread is stopped. Thread tracker now has size "
- << threadTracker.size();
+ for (const auto& record : mPrivateDnsLog.copy()) {
+ dw.println(fmt::format("{} - netId={} PrivateDns={{{}/{}}} state={}",
+ timestampToString(record.timestamp), record.netId,
+ record.serverIdentity.ip.toString(), record.serverIdentity.name,
+ validationStatusToString(record.state)));
}
-}
-
-// 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);
+ dw.blankline();
}
} // namespace net
diff --git a/PrivateDnsConfiguration.h b/PrivateDnsConfiguration.h
index 31fb546..07c6f2e 100644
--- a/PrivateDnsConfiguration.h
+++ b/PrivateDnsConfiguration.h
@@ -22,20 +22,20 @@
#include <vector>
#include <android-base/thread_annotations.h>
+#include <netdutils/DumpWriter.h>
+#include <netdutils/InternetAddresses.h>
#include "DnsTlsServer.h"
+#include "LockedQueue.h"
+#include "PrivateDnsValidationObserver.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;
+
+ // TODO: change the type to std::vector<DnsTlsServer>.
std::map<DnsTlsServer, Validation, AddressComparator> serversMap;
std::list<DnsTlsServer> validatedServers() const {
@@ -65,32 +65,80 @@
void clear(unsigned netId) EXCLUDES(mPrivateDnsLock);
+ // Request |server| to be revalidated on a connection tagged with |mark|.
+ // Return true if the request is accepted; otherwise, return false.
+ bool requestValidation(unsigned netId, const DnsTlsServer& server, uint32_t mark)
+ EXCLUDES(mPrivateDnsLock);
+
+ struct ServerIdentity {
+ const netdutils::IPAddress ip;
+ const std::string name;
+ const int protocol;
+
+ explicit ServerIdentity(const DnsTlsServer& server)
+ : ip(netdutils::IPSockAddr::toIPSockAddr(server.ss).ip()),
+ name(server.name),
+ protocol(server.protocol) {}
+
+ bool operator<(const ServerIdentity& other) const {
+ return std::tie(ip, name, protocol) < std::tie(other.ip, other.name, other.protocol);
+ }
+ bool operator==(const ServerIdentity& other) const {
+ return std::tie(ip, name, protocol) == std::tie(other.ip, other.name, other.protocol);
+ }
+ };
+
+ void setObserver(PrivateDnsValidationObserver* observer);
+
+ void dump(netdutils::DumpWriter& dw) const;
+
private:
- typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
+ typedef std::map<ServerIdentity, DnsTlsServer> PrivateDnsTracker;
typedef std::set<DnsTlsServer, AddressComparator> ThreadTracker;
PrivateDnsConfiguration() = default;
- void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
- unsigned netId, uint32_t mark) REQUIRES(mPrivateDnsLock);
+ void startValidation(const DnsTlsServer& server, unsigned netId) REQUIRES(mPrivateDnsLock);
- bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success);
+ bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success)
+ EXCLUDES(mPrivateDnsLock);
- bool needValidateThread(const DnsTlsServer& server, unsigned netId) REQUIRES(mPrivateDnsLock);
- void cleanValidateThreadTracker(const DnsTlsServer& server, unsigned netId);
-
- // Start validation for newly added servers as well as any servers that have
- // landed in Validation::fail state. Note that servers that have failed
+ // Decide if a validation for |server| is needed. 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);
+ bool needsValidation(const DnsTlsServer& server) REQUIRES(mPrivateDnsLock);
+
+ void updateServerState(const ServerIdentity& identity, Validation state, uint32_t netId)
+ REQUIRES(mPrivateDnsLock);
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.
+
+ // Contains all servers for a network, along with their current validation status.
+ // In case a server is removed due to a configuration change, it remains in this map,
+ // but is marked inactive.
+ // Any pending validation threads will continue running because we have no way to cancel them.
std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
- std::map<unsigned, ThreadTracker> mPrivateDnsValidateThreads GUARDED_BY(mPrivateDnsLock);
+
+ void notifyValidationStateUpdate(const std::string& serverIp, Validation validation,
+ uint32_t netId) const REQUIRES(mPrivateDnsLock);
+
+ // TODO: fix the reentrancy problem.
+ PrivateDnsValidationObserver* mObserver GUARDED_BY(mPrivateDnsLock);
+
+ friend class PrivateDnsConfigurationTest;
+
+ struct RecordEntry {
+ RecordEntry(uint32_t netId, const ServerIdentity& identity, Validation state)
+ : netId(netId), serverIdentity(identity), state(state) {}
+
+ const uint32_t netId;
+ const ServerIdentity serverIdentity;
+ const Validation state;
+ const std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
+ };
+
+ LockedRingBuffer<RecordEntry> mPrivateDnsLog{100};
};
} // namespace net
diff --git a/PrivateDnsConfigurationTest.cpp b/PrivateDnsConfigurationTest.cpp
new file mode 100644
index 0000000..8da3efb
--- /dev/null
+++ b/PrivateDnsConfigurationTest.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2020 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "PrivateDnsConfiguration.h"
+#include "tests/dns_responder/dns_responder.h"
+#include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_utils.h"
+
+namespace android::net {
+
+using namespace std::chrono_literals;
+
+class PrivateDnsConfigurationTest : public ::testing::Test {
+ public:
+ static void SetUpTestSuite() {
+ // stopServer() will be called in their destructor.
+ ASSERT_TRUE(tls1.startServer());
+ ASSERT_TRUE(tls2.startServer());
+ ASSERT_TRUE(backend.startServer());
+ }
+
+ void SetUp() {
+ mPdc.setObserver(&mObserver);
+
+ // The default and sole action when the observer is notified of onValidationStateUpdate.
+ // Don't override the action. In other words, don't use WillOnce() or WillRepeatedly()
+ // when mObserver.onValidationStateUpdate is expected to be called, like:
+ //
+ // EXPECT_CALL(mObserver, onValidationStateUpdate).WillOnce(Return());
+ //
+ // This is to ensure that tests can monitor how many validation threads are running. Tests
+ // must wait until every validation thread finishes.
+ ON_CALL(mObserver, onValidationStateUpdate)
+ .WillByDefault([&](const std::string& server, Validation validation, uint32_t) {
+ if (validation == Validation::in_process) {
+ mObserver.runningThreads++;
+ } else if (validation == Validation::success ||
+ validation == Validation::fail) {
+ mObserver.runningThreads--;
+ }
+ std::lock_guard guard(mObserver.lock);
+ mObserver.serverStateMap[server] = validation;
+ });
+ }
+
+ protected:
+ class MockObserver : public PrivateDnsValidationObserver {
+ public:
+ MOCK_METHOD(void, onValidationStateUpdate,
+ (const std::string& serverIp, Validation validation, uint32_t netId),
+ (override));
+
+ std::map<std::string, Validation> getServerStateMap() const {
+ std::lock_guard guard(lock);
+ return serverStateMap;
+ }
+
+ void removeFromServerStateMap(const std::string& server) {
+ std::lock_guard guard(lock);
+ if (const auto it = serverStateMap.find(server); it != serverStateMap.end())
+ serverStateMap.erase(it);
+ }
+
+ // The current number of validation threads running.
+ std::atomic<int> runningThreads = 0;
+
+ mutable std::mutex lock;
+ std::map<std::string, Validation> serverStateMap GUARDED_BY(lock);
+ };
+
+ void expectPrivateDnsStatus(PrivateDnsMode mode) {
+ const PrivateDnsStatus status = mPdc.getStatus(kNetId);
+ EXPECT_EQ(status.mode, mode);
+
+ std::map<std::string, Validation> serverStateMap;
+ for (const auto& [server, validation] : status.serversMap) {
+ serverStateMap[ToString(&server.ss)] = validation;
+ }
+ EXPECT_EQ(serverStateMap, mObserver.getServerStateMap());
+ }
+
+ static constexpr uint32_t kNetId = 30;
+ static constexpr uint32_t kMark = 30;
+ static constexpr char kBackend[] = "127.0.2.1";
+ static constexpr char kServer1[] = "127.0.2.2";
+ static constexpr char kServer2[] = "127.0.2.3";
+
+ MockObserver mObserver;
+ PrivateDnsConfiguration mPdc;
+
+ // TODO: Because incorrect CAs result in validation failed in strict mode, have
+ // PrivateDnsConfiguration run mocked code rather than DnsTlsTransport::validate().
+ inline static test::DnsTlsFrontend tls1{kServer1, "853", kBackend, "53"};
+ inline static test::DnsTlsFrontend tls2{kServer2, "853", kBackend, "53"};
+ inline static test::DNSResponder backend{kBackend, "53"};
+};
+
+TEST_F(PrivateDnsConfigurationTest, ValidationSuccess) {
+ testing::InSequence seq;
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+}
+
+TEST_F(PrivateDnsConfigurationTest, ValidationFail_Opportunistic) {
+ ASSERT_TRUE(backend.stopServer());
+
+ testing::InSequence seq;
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ // Strictly wait for all of the validation finish; otherwise, the test can crash somehow.
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+ ASSERT_TRUE(backend.startServer());
+}
+
+TEST_F(PrivateDnsConfigurationTest, ValidationBlock) {
+ backend.setDeferredResp(true);
+
+ // onValidationStateUpdate() is called in sequence.
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 1; }));
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer2, Validation::in_process, kNetId));
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer2}, {}, {}), 0);
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 2; }));
+ mObserver.removeFromServerStateMap(kServer1);
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ // No duplicate validation as long as not in OFF mode; otherwise, an unexpected
+ // onValidationStateUpdate() will be caught.
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1, kServer2}, {}, {}), 0);
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer2}, {}, {}), 0);
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ // The status keeps unchanged if pass invalid arguments.
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {"invalid_addr"}, {}, {}), -EINVAL);
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+ }
+
+ // The update for |kServer1| will be Validation::fail because |kServer1| is not an expected
+ // server for the network.
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer2, Validation::success, kNetId));
+ backend.setDeferredResp(false);
+
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+
+ // kServer1 is not a present server and thus should not be available from
+ // PrivateDnsConfiguration::getStatus().
+ mObserver.removeFromServerStateMap(kServer1);
+
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+}
+
+TEST_F(PrivateDnsConfigurationTest, Validation_NetworkDestroyedOrOffMode) {
+ for (const std::string_view config : {"OFF", "NETWORK_DESTROYED"}) {
+ SCOPED_TRACE(config);
+ backend.setDeferredResp(true);
+
+ testing::InSequence seq;
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 1; }));
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ if (config == "OFF") {
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {}, {}, {}), 0);
+ } else if (config == "NETWORK_DESTROYED") {
+ mPdc.clear(kNetId);
+ }
+
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+ backend.setDeferredResp(false);
+
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+ mObserver.removeFromServerStateMap(kServer1);
+ expectPrivateDnsStatus(PrivateDnsMode::OFF);
+ }
+}
+
+TEST_F(PrivateDnsConfigurationTest, NoValidation) {
+ // If onValidationStateUpdate() is called, the test will fail with uninteresting mock
+ // function calls in the end of the test.
+
+ const auto expectStatus = [&]() {
+ const PrivateDnsStatus status = mPdc.getStatus(kNetId);
+ EXPECT_EQ(status.mode, PrivateDnsMode::OFF);
+ EXPECT_THAT(status.serversMap, testing::IsEmpty());
+ };
+
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {"invalid_addr"}, {}, {}), -EINVAL);
+ expectStatus();
+
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {}, {}, {}), 0);
+ expectStatus();
+}
+
+TEST_F(PrivateDnsConfigurationTest, ServerIdentity_Comparison) {
+ using ServerIdentity = PrivateDnsConfiguration::ServerIdentity;
+
+ DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr("127.0.0.1", 853));
+ server.name = "dns.example.com";
+ server.protocol = 1;
+
+ // Different IP address (port is ignored).
+ DnsTlsServer other = server;
+ EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+ other.ss = netdutils::IPSockAddr::toIPSockAddr("127.0.0.1", 5353);
+ EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+ other.ss = netdutils::IPSockAddr::toIPSockAddr("127.0.0.2", 853);
+ EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+
+ // Different provider hostname.
+ other = server;
+ EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+ other.name = "other.example.com";
+ EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+ other.name = "";
+ EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+
+ // Different protocol.
+ other = server;
+ EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+ other.protocol++;
+ EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+}
+
+TEST_F(PrivateDnsConfigurationTest, RequestValidation) {
+ const DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr(kServer1, 853));
+
+ testing::InSequence seq;
+
+ for (const std::string_view config : {"SUCCESS", "IN_PROGRESS", "FAIL"}) {
+ SCOPED_TRACE(config);
+
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+ if (config == "SUCCESS") {
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+ } else if (config == "IN_PROGRESS") {
+ backend.setDeferredResp(true);
+ } else {
+ // config = "FAIL"
+ ASSERT_TRUE(backend.stopServer());
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+ }
+ EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+ expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+ // Wait until the validation state is transitioned.
+ const int runningThreads = (config == "IN_PROGRESS") ? 1 : 0;
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == runningThreads; }));
+
+ bool requestAccepted = false;
+ if (config == "SUCCESS") {
+ EXPECT_CALL(mObserver,
+ onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+ requestAccepted = true;
+ } else if (config == "IN_PROGRESS") {
+ EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+ }
+
+ EXPECT_EQ(mPdc.requestValidation(kNetId, server, kMark), requestAccepted);
+
+ // Resending the same request or requesting nonexistent servers are denied.
+ EXPECT_FALSE(mPdc.requestValidation(kNetId, server, kMark));
+ EXPECT_FALSE(mPdc.requestValidation(kNetId, server, kMark + 1));
+ EXPECT_FALSE(mPdc.requestValidation(kNetId + 1, server, kMark));
+
+ // Reset the test state.
+ backend.setDeferredResp(false);
+ backend.startServer();
+ ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+ mPdc.clear(kNetId);
+ }
+}
+
+// TODO: add ValidationFail_Strict test.
+
+} // namespace android::net
diff --git a/PrivateDnsValidationObserver.h b/PrivateDnsValidationObserver.h
new file mode 100644
index 0000000..b0cd5e2
--- /dev/null
+++ b/PrivateDnsValidationObserver.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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 "PrivateDnsCommon.h"
+
+namespace android::net {
+
+// The observer is notified of onValidationStateUpdate 1) when a validation is
+// about to begin or 2) when a validation finishes. If a validation finishes when in OFF mode
+// or when the network has been destroyed, |validation| will be Validation::fail.
+// WARNING: The Observer is notified while the lock is being held. Be careful not to call
+// any method of PrivateDnsConfiguration from the observer to cause reentrancy problem.
+class PrivateDnsValidationObserver {
+ public:
+ virtual ~PrivateDnsValidationObserver(){};
+ virtual void onValidationStateUpdate(const std::string& serverIp, Validation validation,
+ uint32_t netId) = 0;
+};
+
+} // namespace android::net
diff --git a/ResolverController.cpp b/ResolverController.cpp
index 6a10326..7b181e2 100644
--- a/ResolverController.cpp
+++ b/ResolverController.cpp
@@ -22,8 +22,6 @@
#include <string>
#include <vector>
-#include <netdb.h>
-
#include <aidl/android/net/IDnsResolver.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
@@ -46,41 +44,6 @@
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) {
@@ -201,6 +164,10 @@
int ResolverController::setResolverConfiguration(const ResolverParamsParcel& resolverParams) {
using aidl::android::net::IDnsResolver;
+ if (!has_named_cache(resolverParams.netId)) {
+ return -ENOENT;
+ }
+
// Expect to get the mark with system permission.
android_net_context netcontext;
gResNetdCallbacks.get_network_context(resolverParams.netId, 0 /* uid */, &netcontext);
@@ -223,6 +190,10 @@
return err;
}
+ if (int err = resolv_stats_set_servers_for_dot(resolverParams.netId, tlsServers); err != 0) {
+ return err;
+ }
+
res_params res_params = {};
res_params.sample_validity = resolverParams.sampleValiditySeconds;
res_params.success_threshold = resolverParams.successThreshold;
@@ -255,8 +226,8 @@
ResolverStats::encodeAll(res_stats, stats);
const auto privateDnsStatus = PrivateDnsConfiguration::getInstance().getStatus(netId);
- for (const auto& pair : privateDnsStatus.serversMap) {
- tlsServers->push_back(addrToString(&pair.first.ss));
+ for (const auto& [server, _] : privateDnsStatus.serversMap) {
+ tlsServers->push_back(server.toIpString());
}
params->resize(IDnsResolver::RESOLVER_PARAMS_COUNT);
@@ -355,9 +326,9 @@
dw.println("Private DNS configuration (%u entries)",
static_cast<uint32_t>(privateDnsStatus.serversMap.size()));
dw.incIndent();
- for (const auto& pair : privateDnsStatus.serversMap) {
- dw.println("%s name{%s} status{%s}", addrToString(&pair.first.ss).c_str(),
- pair.first.name.c_str(), validationStatusToString(pair.second));
+ for (const auto& [server, validation] : privateDnsStatus.serversMap) {
+ dw.println("%s name{%s} status{%s}", server.toIpString().c_str(),
+ server.name.c_str(), validationStatusToString(validation));
}
dw.decIndent();
}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/.hash b/aidl_api/dnsresolver_aidl_interface/7/.hash
new file mode 100644
index 0000000..75f2e38
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/.hash
@@ -0,0 +1 @@
+a1dc9394598357ccaa74e96f564e7f248b72bad2
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl
new file mode 100644
index 0000000..1f80545
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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;
+/* @hide */
+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);
+ void flushNetworkCache(int netId);
+ void setPrefix64(int netId, @utf8InCpp String prefix);
+ void registerUnsolicitedEventListener(android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener listener);
+ 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;
+ const int TC_MODE_DEFAULT = 0;
+ const int TC_MODE_UDP_TCP = 1;
+ const int TRANSPORT_UNKNOWN = -1;
+ const int TRANSPORT_CELLULAR = 0;
+ const int TRANSPORT_WIFI = 1;
+ const int TRANSPORT_BLUETOOTH = 2;
+ const int TRANSPORT_ETHERNET = 3;
+ const int TRANSPORT_VPN = 4;
+ const int TRANSPORT_WIFI_AWARE = 5;
+ const int TRANSPORT_LOWPAN = 6;
+ const int TRANSPORT_TEST = 7;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl
new file mode 100644
index 0000000..c24eb61
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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;
+/* @hide */
+parcelable ResolverHostsParcel {
+ @utf8InCpp String ipAddr;
+ @utf8InCpp String hostName = "";
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl
new file mode 100644
index 0000000..e806d04
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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;
+/* @hide */
+parcelable ResolverOptionsParcel {
+ android.net.ResolverHostsParcel[] hosts = {};
+ int tcMode = 0;
+ boolean enforceDnsUid = false;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl
new file mode 100644
index 0000000..8fec710
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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;
+/* @hide */
+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 = {};
+ @utf8InCpp String caCertificate = "";
+ int tlsConnectTimeoutMs = 0;
+ android.net.ResolverOptionsParcel resolverOptions;
+ int[] transportTypes = {};
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..d32be91
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+ int netId;
+ int healthResult;
+ int[] successRttMicros;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..d8accd1
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+interface IDnsResolverUnsolicitedEventListener {
+ oneway void onDnsHealthEvent(in android.net.resolv.aidl.DnsHealthEventParcel dnsHealthEvent);
+ oneway void onNat64PrefixEvent(in android.net.resolv.aidl.Nat64PrefixEventParcel nat64PrefixEvent);
+ oneway void onPrivateDnsValidationEvent(in android.net.resolv.aidl.PrivateDnsValidationEventParcel privateDnsValidationEvent);
+ const int DNS_HEALTH_RESULT_OK = 0;
+ const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+ const int PREFIX_OPERATION_ADDED = 1;
+ const int PREFIX_OPERATION_REMOVED = 2;
+ const int VALIDATION_RESULT_SUCCESS = 1;
+ const int VALIDATION_RESULT_FAILURE = 2;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..2daccb0
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+ int netId;
+ int prefixOperation;
+ @utf8InCpp String prefixAddress;
+ int prefixLength;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e66e21c
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+ int netId;
+ @utf8InCpp String ipAddress;
+ @utf8InCpp String hostname;
+ int validation;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
index 863927b..1f80545 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a 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.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
//
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file 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
@@ -30,6 +31,7 @@
void setLogSeverity(int logSeverity);
void flushNetworkCache(int netId);
void setPrefix64(int netId, @utf8InCpp String prefix);
+ void registerUnsolicitedEventListener(android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener listener);
const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
index 3ab0533..c24eb61 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a 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.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
//
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file 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
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
index d55ae46..e806d04 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a 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.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
//
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file 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
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
index 5dae1ca..8fec710 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a 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.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
//
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file 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
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..d32be91
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+ int netId;
+ int healthResult;
+ int[] successRttMicros;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..d8accd1
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+interface IDnsResolverUnsolicitedEventListener {
+ oneway void onDnsHealthEvent(in android.net.resolv.aidl.DnsHealthEventParcel dnsHealthEvent);
+ oneway void onNat64PrefixEvent(in android.net.resolv.aidl.Nat64PrefixEventParcel nat64PrefixEvent);
+ oneway void onPrivateDnsValidationEvent(in android.net.resolv.aidl.PrivateDnsValidationEventParcel privateDnsValidationEvent);
+ const int DNS_HEALTH_RESULT_OK = 0;
+ const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+ const int PREFIX_OPERATION_ADDED = 1;
+ const int PREFIX_OPERATION_REMOVED = 2;
+ const int VALIDATION_RESULT_SUCCESS = 1;
+ const int VALIDATION_RESULT_FAILURE = 2;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..2daccb0
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+ int netId;
+ int prefixOperation;
+ @utf8InCpp String prefixAddress;
+ int prefixLength;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e66e21c
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+ int netId;
+ @utf8InCpp String ipAddress;
+ @utf8InCpp String hostname;
+ int validation;
+}
diff --git a/binder/android/net/IDnsResolver.aidl b/binder/android/net/IDnsResolver.aidl
index 538c3b8..33316e3 100644
--- a/binder/android/net/IDnsResolver.aidl
+++ b/binder/android/net/IDnsResolver.aidl
@@ -18,6 +18,7 @@
import android.net.ResolverParamsParcel;
import android.net.metrics.INetdEventListener;
+import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
/** {@hide} */
interface IDnsResolver {
@@ -145,7 +146,7 @@
*/
void destroyNetworkCache(int netId);
- // Refer to enum LogSeverity from system/core/base/include/android-base/logging.h
+ // Refer to enum LogSeverity from system/libbase/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;
@@ -214,4 +215,21 @@
* unix errno.
*/
void setPrefix64(int netId, @utf8InCpp String prefix);
+
+ /**
+ * Register unsolicited event listener
+ * DnsResolver supports multiple unsolicited event listeners.
+ *
+ * This is a non-public interface between DnsResolver and Connectivity/NetworkStack.
+ * It is subject to change on Mainline updates without notice. DO NOT DEPEND ON IT.
+ *
+ * Only system services(Connectivity/NetworkStack) will register the unsolicited listener.
+ * Besides, there is no unregister method since the system services will be always there to
+ * listen unsolicited events.
+ *
+ * @param listener unsolicited event listener to register
+ * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+ * unix errno.
+ */
+ void registerUnsolicitedEventListener(IDnsResolverUnsolicitedEventListener listener);
}
diff --git a/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..33f60da
--- /dev/null
+++ b/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+/**
+ * The DNS health result of queries.
+ *
+ * Only sent for API calls that actually generated network traffic:
+ * - No cache hits
+ * - No power management errors (rcode == INTERNAL_ERROR && errno == EPERM)
+ * - Only lookup result is OK or TIMEOUT.
+ *
+ * This must not be used for use-cases other than evaluating the health of DNS queries. The behavior
+ * of this interface will change across regular module updates, and in particular it will be updated
+ * to stop sending one event by DNS API call, and instead report network failures in realtime.
+ *
+ * Non-public API, only callable from Connectivity or NetworkStack Mainline modules.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+
+ /*** The ID of the network that lookup was performed on. */
+ int netId;
+
+ /**
+ * The health result of queries.
+ * Currently, this value is for data stall detection and it only checks whether result is
+ * timeout or not. So if the result is neither OK nor TIMEOUT, will not send this event.
+ */
+ int healthResult;
+
+ /*** The round trip time for each dns query that received a reply */
+ int[] successRttMicros;
+}
diff --git a/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..b0d6553
--- /dev/null
+++ b/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+import android.net.resolv.aidl.DnsHealthEventParcel;
+import android.net.resolv.aidl.Nat64PrefixEventParcel;
+import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
+
+/**
+ * Unsolicited dns resolver events listener.
+ * This one-way interface groups asynchronous notifications sent by dns resolver to any process that
+ * registered itself via IDnsResolver.registerUnsolicitedEventListener.
+ *
+ * {@hide}
+ */
+oneway interface IDnsResolverUnsolicitedEventListener {
+
+ /*** Types for {@code healthResult} of {@code DnsHealthEventParcel}. */
+ const int DNS_HEALTH_RESULT_OK = 0;
+ const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+
+ /**
+ * Represents a DNS health result of queries.
+ *
+ * Sent by the resolver when it has events to report about its observed network health.
+ * Refer to DnsHealthEventParcel for more details.
+ *
+ * @param dnsHealthEvent the DNS health result.
+ */
+ void onDnsHealthEvent(in DnsHealthEventParcel dnsHealthEvent);
+
+ /*** Types for {@code prefixOperation} of {@code Nat64PrefixEventParcel}. */
+ const int PREFIX_OPERATION_ADDED = 1;
+ const int PREFIX_OPERATION_REMOVED = 2;
+
+ /**
+ * Represents a NAT64 prefix operation.
+ *
+ * @param nat64PrefixEvent the NAT64 prefix status.
+ */
+ void onNat64PrefixEvent(in Nat64PrefixEventParcel nat64PrefixEvent);
+
+ /*** Types for {@code validation} of {@code PrivateDnsValidationEventParcel}. */
+ const int VALIDATION_RESULT_SUCCESS = 1;
+ const int VALIDATION_RESULT_FAILURE = 2;
+
+ /**
+ * Represents a private DNS validation result.
+ *
+ * @param privateDnsValidationEvent the private DNS validation result.
+ */
+ void onPrivateDnsValidationEvent(in PrivateDnsValidationEventParcel privateDnsValidationEvent);
+}
diff --git a/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..45e25a1
--- /dev/null
+++ b/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+/**
+ * Nat64 prefix operation event.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+
+ /** The ID of the network the prefix operation was performed on. */
+ int netId;
+
+ /**
+ * The NAT64 prefix operation.
+ * There is only one prefix at a time for each netId. If a prefix is added, it replaces the
+ * previous-added prefix.
+ */
+ int prefixOperation;
+
+ /** The detected NAT64 prefix address. */
+ @utf8InCpp String prefixAddress;
+
+ /** The prefix length associated with this NAT64 prefix. */
+ int prefixLength;
+}
diff --git a/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e5d7f78
--- /dev/null
+++ b/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+/**
+ * A private DNS validation result.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+
+ /** The ID of the network the validation was performed on. */
+ int netId;
+
+ /** The IP address for which validation was performed. */
+ @utf8InCpp String ipAddress;
+
+ /** The hostname for which validation was performed. */
+ @utf8InCpp String hostname;
+
+ /** The validation result. */
+ int validation;
+}
diff --git a/getaddrinfo.cpp b/getaddrinfo.cpp
index 4804762..b3911fc 100644
--- a/getaddrinfo.cpp
+++ b/getaddrinfo.cpp
@@ -62,7 +62,6 @@
#include "netd_resolv/resolv.h"
#include "res_comp.h"
#include "res_debug.h"
-#include "res_init.h"
#include "resolv_cache.h"
#include "resolv_private.h"
#include "util.h"
@@ -1436,8 +1435,7 @@
return EAI_FAMILY;
}
- ResState res;
- res_init(&res, netcontext, event);
+ ResState res(netcontext, event);
int he;
if (res_searchN(name, &q, &res, &he) < 0) {
@@ -1633,7 +1631,6 @@
NetworkDnsEventReported event;
if (n <= 0) {
LOG(ERROR) << __func__ << ": res_nmkquery failed";
- return {0, -1, NO_RECOVERY, event};
return {
.ancount = 0,
.rcode = -1,
@@ -1642,7 +1639,7 @@
};
}
- ResState res_temp = fromResState(*res, &event);
+ ResState res_temp = res->clone(&event);
int rcode = NOERROR;
n = res_nsend(&res_temp, buf, n, t->answer.data(), anslen, &rcode, 0, sleepTimeMs);
@@ -1710,7 +1707,7 @@
static int res_queryN_wrapper(const char* name, res_target* target, res_state res, int* herrno) {
const bool parallel_lookup =
- android::net::Experiments::getInstance()->getFlag("parallel_lookup", 1);
+ android::net::Experiments::getInstance()->getFlag("parallel_lookup_release", 1);
if (parallel_lookup) return res_queryN_parallel(name, target, res, herrno);
return res_queryN(name, target, res, herrno);
diff --git a/gethnamaddr.cpp b/gethnamaddr.cpp
index 23f94bb..4d01c64 100644
--- a/gethnamaddr.cpp
+++ b/gethnamaddr.cpp
@@ -76,8 +76,8 @@
#include "netd_resolv/resolv.h"
#include "res_comp.h"
#include "res_debug.h" // p_class(), p_type()
-#include "res_init.h"
#include "resolv_cache.h"
+#include "resolv_private.h"
#include "stats.pb.h"
using android::net::NetworkDnsEventReported;
@@ -388,10 +388,12 @@
int resolv_gethostbyname(const char* name, int af, hostent* hp, char* buf, size_t buflen,
const android_net_context* netcontext, hostent** result,
NetworkDnsEventReported* event) {
- getnamaddr info;
+ if (name == nullptr || hp == nullptr) {
+ return EAI_SYSTEM;
+ }
- ResState res;
- res_init(&res, netcontext, event);
+ getnamaddr info;
+ ResState res(netcontext, event);
size_t size;
switch (af) {
@@ -727,8 +729,7 @@
auto buf = std::make_unique<querybuf>();
- ResState res;
- res_init(&res, netcontext, event);
+ ResState res(netcontext, event);
int he;
n = res_nquery(&res, qbuf, C_IN, T_PTR, buf->buf, (int)sizeof(buf->buf), &he);
if (n < 0) {
diff --git a/res_cache.cpp b/res_cache.cpp
index ffa2929..dfd4a79 100644
--- a/res_cache.cpp
+++ b/res_cache.cpp
@@ -264,6 +264,22 @@
const uint8_t* cursor;
};
+static uint8_t res_tolower(uint8_t c) {
+ return (c >= 'A' && c <= 'Z') ? (c | 0x20) : c;
+}
+
+static int res_memcasecmp(const unsigned char *s1, const unsigned char *s2, size_t len) {
+ for (size_t i = 0; i < len; i++) {
+ int ch1 = *s1++;
+ int ch2 = *s2++;
+ int d = res_tolower(ch1) - res_tolower(ch2);
+ if (d != 0) {
+ return d;
+ }
+ }
+ return 0;
+}
+
static void _dnsPacket_init(DnsPacket* packet, const uint8_t* buff, int bufflen) {
packet->base = buff;
packet->end = buff + bufflen;
@@ -451,14 +467,12 @@
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++;
+ int c = *p++;
if (c == 0) break;
@@ -470,9 +484,12 @@
LOG(INFO) << __func__ << ": INTERNAL_ERROR: simple label read-overflow";
break;
}
+
while (c > 0) {
- hash = hash * FNV_MULT ^ *p++;
- c -= 1;
+ uint8_t ch = *p++;
+ ch = res_tolower(ch);
+ hash = hash * FNV_MULT ^ ch;
+ c--;
}
}
packet->cursor = p;
@@ -548,14 +565,12 @@
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++;
+ int c1 = *p1++;
+ int c2 = *p2++;
if (c1 != c2) break;
if (c1 == 0) {
@@ -571,7 +586,7 @@
LOG(INFO) << __func__ << ": INTERNAL_ERROR: simple label read-overflow";
break;
}
- if (memcmp(p1, p2, c1) != 0) break;
+ if (res_memcasecmp(p1, p2, c1) != 0) break;
p1 += c1;
p2 += c1;
/* we rely on the bound checks at the start of the loop */
@@ -1001,9 +1016,13 @@
// Map format: ReturnCode:rate_denom
std::unordered_map<int, uint32_t> dns_event_subsampling_map;
DnsStats dnsStats;
+
// Customized hostname/address table will be stored in customizedTable.
// If resolverParams.hosts is empty, the existing customized table will be erased.
+ typedef std::multimap<std::string /* hostname */, std::string /* IPv4/IPv6 address */>
+ HostMapping;
HostMapping customizedTable = {};
+
int tc_mode = aidl::android::net::IDnsResolver::TC_MODE_DEFAULT;
bool enforceDnsUid = false;
std::vector<int32_t> transportTypes;
diff --git a/res_init.cpp b/res_init.cpp
deleted file mode 100644
index a0004c4..0000000
--- a/res_init.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/* $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 "resolv"
-
-#include <sys/param.h>
-#include <sys/socket.h>
-#include <sys/time.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 <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "netd_resolv/resolv.h"
-#include "resolv_private.h"
-#include "stats.pb.h"
-
-void res_init(ResState* statp, const struct android_net_context* _Nonnull netcontext,
- android::net::NetworkDnsEventReported* _Nonnull event) {
- statp->netid = netcontext->dns_netid;
- statp->uid = netcontext->uid;
- statp->pid = netcontext->pid;
- statp->id = arc4random_uniform(65536);
-
- for (auto& sock : statp->nssocks) {
- sock.reset();
- }
- statp->ndots = 1;
- statp->_mark = netcontext->dns_mark;
- statp->tcp_nssock.reset();
- statp->event = event;
- statp->netcontext_flags = netcontext->flags;
-}
-
-// TODO: Have some proper constructors for ResState instead of this method and res_init().
-ResState fromResState(const ResState& other, android::net::NetworkDnsEventReported* event) {
- ResState resOutput;
- resOutput.netid = other.netid;
- resOutput.uid = other.uid;
- resOutput.pid = other.pid;
- resOutput.id = other.id;
-
- resOutput.nsaddrs = other.nsaddrs;
-
- for (auto& sock : resOutput.nssocks) {
- sock.reset();
- }
-
- resOutput.ndots = other.ndots;
- resOutput._mark = other._mark;
- resOutput.tcp_nssock.reset();
- resOutput.event = event;
- resOutput.netcontext_flags = other.netcontext_flags;
- return resOutput;
-}
diff --git a/res_init.h b/res_init.h
deleted file mode 100644
index 0cfd7b9..0000000
--- a/res_init.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 "resolv_private.h"
-#include "stats.pb.h"
-
-// TODO: make this a constructor for ResState
-void res_init(ResState* res, const struct android_net_context* netcontext,
- android::net::NetworkDnsEventReported* event);
-ResState fromResState(const ResState& other, android::net::NetworkDnsEventReported* event);
diff --git a/res_send.cpp b/res_send.cpp
index 3638e05..c78e02f 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -111,7 +111,6 @@
#include "res_comp.h"
#include "res_debug.h"
-#include "res_init.h"
#include "resolv_cache.h"
#include "stats.h"
#include "stats.pb.h"
@@ -144,8 +143,6 @@
using android::netdutils::Slice;
using android::netdutils::Stopwatch;
-static DnsTlsDispatcher sDnsTlsDispatcher;
-
static int send_vc(res_state statp, res_params* params, const uint8_t* buf, int buflen,
uint8_t* ans, int anssiz, int* terrno, size_t ns, time_t* at, int* rcode,
int* delay);
@@ -1255,8 +1252,8 @@
LOG(INFO) << __func__ << ": performing query over TLS";
- const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers(), statp, query,
- answer, &resplen);
+ const auto response = DnsTlsDispatcher::getInstance().query(privateDnsStatus.validatedServers(),
+ statp, query, answer, &resplen);
LOG(INFO) << __func__ << ": TLS query result: " << static_cast<int>(response);
@@ -1300,8 +1297,7 @@
uint8_t* ans, int ansLen, int* rcode, uint32_t flags,
NetworkDnsEventReported* event) {
assert(event != nullptr);
- ResState res;
- res_init(&res, netContext, event);
+ ResState res(netContext, event);
resolv_populate_res_for_net(&res);
*rcode = NOERROR;
return res_nsend(&res, msg, msgLen, ans, ansLen, rcode, flags);
diff --git a/resolv_cache.h b/resolv_cache.h
index d8a3afc..15baa14 100644
--- a/resolv_cache.h
+++ b/resolv_cache.h
@@ -46,9 +46,6 @@
// The name servers are retrieved from the cache which is associated
// with the network to which ResState is associated.
struct ResState;
-
-typedef std::multimap<std::string /* hostname */, std::string /* IPv4/IPv6 address */> HostMapping;
-
void resolv_populate_res_for_net(ResState* statp);
std::vector<unsigned> resolv_list_caches();
@@ -100,7 +97,6 @@
// Get transport types to a given network.
android::net::NetworkType resolv_get_network_types_for_net(unsigned netid);
-// For test only.
// Return true if the cache is existent in the given network, false otherwise.
bool has_named_cache(unsigned netid);
diff --git a/resolv_cache_unit_test.cpp b/resolv_cache_unit_test.cpp
index d604b2d..002bdaa 100644
--- a/resolv_cache_unit_test.cpp
+++ b/resolv_cache_unit_test.cpp
@@ -30,17 +30,16 @@
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
-#include "res_init.h"
#include "resolv_cache.h"
#include "resolv_private.h"
#include "stats.h"
#include "tests/dns_responder/dns_responder.h"
+#include "tests/resolv_test_utils.h"
using namespace std::chrono_literals;
using android::netdutils::IPSockAddr;
-constexpr int TEST_NETID = 30;
constexpr int TEST_NETID_2 = 31;
constexpr int DNS_PORT = 53;
@@ -227,7 +226,7 @@
// Server checking.
EXPECT_EQ(nscount, static_cast<int>(expected.setup.servers.size())) << msg;
for (int i = 0; i < nscount; i++) {
- EXPECT_EQ(addrToString(&servers[i]), expected.setup.servers[i]) << msg;
+ EXPECT_EQ(ToString(&servers[i]), expected.setup.servers[i]) << msg;
}
// Domain checking
diff --git a/resolv_private.h b/resolv_private.h
index 8bc5035..bc7e415 100644
--- a/resolv_private.h
+++ b/resolv_private.h
@@ -88,6 +88,33 @@
constexpr int MAXPACKET = 8 * 1024;
struct ResState {
+ ResState(const android_net_context* netcontext, android::net::NetworkDnsEventReported* dnsEvent)
+ : netid(netcontext->dns_netid),
+ uid(netcontext->uid),
+ pid(netcontext->pid),
+ _mark(netcontext->dns_mark),
+ event(dnsEvent),
+ netcontext_flags(netcontext->flags) {}
+
+ ResState clone(android::net::NetworkDnsEventReported* dnsEvent = nullptr) {
+ // TODO: Separate non-copyable members to other structures and let default copy
+ // constructor do its work for below copyable members.
+ ResState copy;
+ copy.netid = netid;
+ copy.uid = uid;
+ copy.pid = pid;
+ copy.search_domains = search_domains;
+ copy.nsaddrs = nsaddrs;
+ copy.ndots = ndots;
+ copy._mark = _mark;
+ copy._flags = _flags;
+ copy.event = (dnsEvent == nullptr) ? event : dnsEvent;
+ copy.netcontext_flags = netcontext_flags;
+ copy.tc_mode = tc_mode;
+ copy.enforce_dns_uid = enforce_dns_uid;
+ copy.sort_nameservers = sort_nameservers;
+ return copy;
+ }
void closeSockets() {
tcp_nssock.reset();
_flags &= ~RES_F_VC;
@@ -103,11 +130,10 @@
unsigned netid; // NetId: cache key and socket mark
uid_t uid; // uid of the app that sent the DNS lookup
pid_t pid; // pid of the app that sent the DNS lookup
- uint16_t id; // current message id
std::vector<std::string> search_domains{}; // domains to search
std::vector<android::netdutils::IPSockAddr> nsaddrs;
android::base::unique_fd nssocks[MAXNS]; // UDP sockets to nameservers
- unsigned ndots : 4; // threshold for initial abs. query
+ unsigned ndots : 4 = 1; // threshold for initial abs. query
unsigned _mark; // If non-0 SET_MARK to _mark on all request sockets
android::base::unique_fd tcp_nssock; // TCP socket (but why not one per nameserver?)
uint32_t _flags = 0; // See RES_F_* defines below
@@ -118,6 +144,8 @@
bool sort_nameservers = false; // A flag to indicate whether nsaddrs has been
// sorted or not.
// clang-format on
+ private:
+ ResState() {}
};
// TODO: remove these legacy aliases
@@ -183,10 +211,3 @@
PLOG(WARNING) << "Failed to chown socket";
}
}
-
-inline 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);
-}
diff --git a/resolv_tls_unit_test.cpp b/resolv_tls_unit_test.cpp
index 5a03db7..29171db 100644
--- a/resolv_tls_unit_test.cpp
+++ b/resolv_tls_unit_test.cpp
@@ -22,6 +22,7 @@
#include <android-base/logging.h>
#include <android-base/macros.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <netdutils/Slice.h>
@@ -31,6 +32,7 @@
#include "DnsTlsSessionCache.h"
#include "DnsTlsSocket.h"
#include "DnsTlsTransport.h"
+#include "Experiments.h"
#include "IDnsTlsSocket.h"
#include "IDnsTlsSocketFactory.h"
#include "IDnsTlsSocketObserver.h"
@@ -39,8 +41,10 @@
namespace android {
namespace net {
-using netdutils::Slice;
using netdutils::makeSlice;
+using netdutils::Slice;
+
+static const std::string DOT_MAXTRIES_FLAG = "dot_maxtries";
typedef std::vector<uint8_t> bytevec;
@@ -134,6 +138,7 @@
std::thread(&IDnsTlsSocketObserver::onResponse, mObserver, make_echo(id, query)).detach();
return true;
}
+ bool startHandshake() override { return true; }
private:
IDnsTlsSocketObserver* const mObserver;
@@ -169,6 +174,7 @@
std::thread(&IDnsTlsSocketObserver::onResponse, mObserver, response).detach();
return true;
}
+ bool startHandshake() override { return true; }
private:
IDnsTlsSocketObserver* const mObserver;
@@ -216,9 +222,15 @@
class FakeSocketDelay : public IDnsTlsSocket {
public:
explicit FakeSocketDelay(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
- ~FakeSocketDelay() { std::lock_guard guard(mLock); }
- static size_t sDelay;
- static bool sReverse;
+ ~FakeSocketDelay() {
+ std::lock_guard guard(mLock);
+ sDelay = 1;
+ sReverse = false;
+ sConnectable = true;
+ }
+ inline static size_t sDelay = 1;
+ inline static bool sReverse = false;
+ inline static bool sConnectable = true;
bool query(uint16_t id, const Slice query) override {
LOG(DEBUG) << "FakeSocketDelay got query with ID " << int(id);
@@ -236,6 +248,7 @@
}
return true;
}
+ bool startHandshake() override { return sConnectable; }
private:
void sendResponses() {
@@ -256,9 +269,6 @@
std::vector<bytevec> mResponses GUARDED_BY(mLock);
};
-size_t FakeSocketDelay::sDelay;
-bool FakeSocketDelay::sReverse;
-
TEST_F(TransportTest, ParallelColliding) {
FakeSocketDelay::sDelay = 10;
FakeSocketDelay::sReverse = false;
@@ -424,13 +434,24 @@
};
TEST_F(TransportTest, ConnectFail) {
- NullSocketFactory factory;
- DnsTlsTransport transport(SERVER1, MARK, &factory);
- auto r = transport.query(makeSlice(QUERY)).get();
+ // Failure on creating socket.
+ NullSocketFactory factory1;
+ DnsTlsTransport transport1(SERVER1, MARK, &factory1);
+ auto r = transport1.query(makeSlice(QUERY)).get();
EXPECT_EQ(DnsTlsTransport::Response::network_error, r.code);
EXPECT_TRUE(r.response.empty());
- EXPECT_EQ(transport.getConnectCounter(), 1);
+ EXPECT_EQ(transport1.getConnectCounter(), 1);
+
+ // Failure on handshaking.
+ FakeSocketDelay::sConnectable = false;
+ FakeSocketFactory<FakeSocketDelay> factory2;
+ DnsTlsTransport transport2(SERVER1, MARK, &factory2);
+ r = transport2.query(makeSlice(QUERY)).get();
+
+ EXPECT_EQ(DnsTlsTransport::Response::network_error, r.code);
+ EXPECT_TRUE(r.response.empty());
+ EXPECT_EQ(transport2.getConnectCounter(), 1);
}
// Simulate a socket that connects but then immediately receives a server
@@ -444,6 +465,7 @@
const Slice query ATTRIBUTE_UNUSED) override {
return true;
}
+ bool startHandshake() override { return true; }
private:
std::thread mCloser;
@@ -457,8 +479,9 @@
EXPECT_EQ(DnsTlsTransport::Response::network_error, r.code);
EXPECT_TRUE(r.response.empty());
- // Reconnections are triggered since DnsTlsQueryMap is not empty.
- EXPECT_EQ(transport.getConnectCounter(), DnsTlsQueryMap::kMaxTries);
+ // Reconnections might be triggered depending on the flag.
+ EXPECT_EQ(transport.getConnectCounter(),
+ Experiments::getInstance()->getFlag(DOT_MAXTRIES_FLAG, DnsTlsQueryMap::kMaxTries));
}
// Simulate a server that occasionally closes the connection and silently
@@ -506,6 +529,7 @@
}
return mQueries <= sLimit;
}
+ bool startHandshake() override { return true; }
private:
void sendClose() {
@@ -552,8 +576,9 @@
EXPECT_TRUE(r.response.empty());
}
- // Reconnections are triggered since DnsTlsQueryMap is not empty.
- EXPECT_EQ(transport.getConnectCounter(), DnsTlsQueryMap::kMaxTries);
+ // Reconnections might be triggered depending on the flag.
+ EXPECT_EQ(transport.getConnectCounter(),
+ Experiments::getInstance()->getFlag(DOT_MAXTRIES_FLAG, DnsTlsQueryMap::kMaxTries));
}
TEST_F(TransportTest, PartialDrop) {
@@ -632,6 +657,7 @@
mThreads.emplace_back(&IDnsTlsSocketObserver::onResponse, mObserver, make_query(id + 1, query.size() + 2));
return true;
}
+ bool startHandshake() override { return true; }
private:
std::mutex mLock;
@@ -774,6 +800,18 @@
EXPECT_FALSE(s2 == s1);
}
+void checkEqual(const DnsTlsServer& s1, const DnsTlsServer& s2) {
+ EXPECT_TRUE(s1 == s1);
+ EXPECT_TRUE(s2 == s2);
+ EXPECT_TRUE(isAddressEqual(s1, s1));
+ EXPECT_TRUE(isAddressEqual(s2, s2));
+
+ EXPECT_FALSE(s1 < s2);
+ EXPECT_FALSE(s2 < s1);
+ EXPECT_TRUE(s1 == s2);
+ EXPECT_TRUE(s2 == s1);
+}
+
class ServerTest : public BaseTest {};
TEST_F(ServerTest, IPv4) {
@@ -824,12 +862,16 @@
parseServer("192.0.2.1", 854, &s2.ss);
checkUnequal(s1, s2);
EXPECT_TRUE(isAddressEqual(s1, s2));
+ EXPECT_EQ(s1.toIpString(), "192.0.2.1");
+ EXPECT_EQ(s2.toIpString(), "192.0.2.1");
DnsTlsServer s3, s4;
parseServer("2001:db8::1", 853, &s3.ss);
parseServer("2001:db8::1", 852, &s4.ss);
checkUnequal(s3, s4);
EXPECT_TRUE(isAddressEqual(s3, s4));
+ EXPECT_EQ(s3.toIpString(), "2001:db8::1");
+ EXPECT_EQ(s4.toIpString(), "2001:db8::1");
EXPECT_FALSE(s1.wasExplicitlyConfigured());
EXPECT_FALSE(s2.wasExplicitlyConfigured());
@@ -847,16 +889,22 @@
EXPECT_TRUE(s2.wasExplicitlyConfigured());
}
-TEST_F(ServerTest, Timeout) {
+TEST_F(ServerTest, State) {
DnsTlsServer s1(V4ADDR1), s2(V4ADDR1);
- s1.connectTimeout = std::chrono::milliseconds(4000);
- checkUnequal(s1, s2);
- s2.connectTimeout = std::chrono::milliseconds(4000);
- EXPECT_EQ(s1, s2);
- EXPECT_TRUE(isAddressEqual(s1, s2));
+ checkEqual(s1, s2);
+ s1.setValidationState(Validation::success);
+ checkEqual(s1, s2);
+ s2.setValidationState(Validation::fail);
+ checkEqual(s1, s2);
+ s1.setActive(true);
+ checkEqual(s1, s2);
+ s2.setActive(false);
+ checkEqual(s1, s2);
- EXPECT_FALSE(s1.wasExplicitlyConfigured());
- EXPECT_FALSE(s2.wasExplicitlyConfigured());
+ EXPECT_EQ(s1.validationState(), Validation::success);
+ EXPECT_EQ(s2.validationState(), Validation::fail);
+ EXPECT_TRUE(s1.active());
+ EXPECT_FALSE(s2.active());
}
TEST(QueryMapTest, Basic) {
@@ -959,44 +1007,119 @@
EXPECT_FALSE(map.recordQuery(makeSlice(QUERY)));
}
-class StubObserver : public IDnsTlsSocketObserver {
- public:
- bool closed = false;
- void onResponse(std::vector<uint8_t>) override {}
+class DnsTlsSocketTest : public ::testing::Test {
+ protected:
+ class MockDnsTlsSocketObserver : public IDnsTlsSocketObserver {
+ public:
+ MOCK_METHOD(void, onClosed, (), (override));
+ MOCK_METHOD(void, onResponse, (std::vector<uint8_t>), (override));
+ };
- void onClosed() override { closed = true; }
-};
+ DnsTlsSocketTest() { parseServer(kTlsAddr, std::stoi(kTlsPort), &server.ss); }
-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";
+ std::unique_ptr<DnsTlsSocket> makeDnsTlsSocket(IDnsTlsSocketObserver* observer) {
+ return std::make_unique<DnsTlsSocket>(this->server, MARK, observer, &this->cache);
+ }
- test::DnsTlsFrontend tls(tls_addr, tls_port, backend_addr, backend_port);
- ASSERT_TRUE(tls.startServer());
+ void enableAsyncHandshake(const std::unique_ptr<DnsTlsSocket>& socket) {
+ ASSERT_TRUE(socket);
+ DnsTlsSocket* delegate = socket.get();
+ std::lock_guard guard(delegate->mLock);
+ delegate->mAsyncHandshake = true;
+ }
+
+ static constexpr char kTlsAddr[] = "127.0.0.3";
+ static constexpr char kTlsPort[] = "8530"; // High-numbered port so root isn't required.
+ static constexpr char kBackendAddr[] = "192.0.2.1";
+ static constexpr char kBackendPort[] = "8531"; // High-numbered port so root isn't required.
+
+ test::DnsTlsFrontend tls{kTlsAddr, kTlsPort, kBackendAddr, kBackendPort};
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);
+};
+
+TEST_F(DnsTlsSocketTest, SlowDestructor) {
+ ASSERT_TRUE(tls.startServer());
+
+ MockDnsTlsSocketObserver observer;
+ auto socket = makeDnsTlsSocket(&observer);
+
ASSERT_TRUE(socket->initialize());
+ ASSERT_TRUE(socket->startHandshake());
// Test: Time the socket destructor. This should be fast.
auto before = std::chrono::steady_clock::now();
+ EXPECT_CALL(observer, onClosed);
socket.reset();
auto after = std::chrono::steady_clock::now();
auto delay = after - before;
LOG(DEBUG) << "Shutdown took " << delay / std::chrono::nanoseconds{1} << "ns";
- 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});
}
+TEST_F(DnsTlsSocketTest, StartHandshake) {
+ ASSERT_TRUE(tls.startServer());
+
+ MockDnsTlsSocketObserver observer;
+ auto socket = makeDnsTlsSocket(&observer);
+
+ // Call the function before the call to initialize().
+ EXPECT_FALSE(socket->startHandshake());
+
+ // Call the function after the call to initialize().
+ EXPECT_TRUE(socket->initialize());
+ EXPECT_TRUE(socket->startHandshake());
+
+ // Call both of them again.
+ EXPECT_FALSE(socket->initialize());
+ EXPECT_FALSE(socket->startHandshake());
+
+ // Should happen when joining the loop thread in |socket| destruction.
+ EXPECT_CALL(observer, onClosed);
+}
+
+TEST_F(DnsTlsSocketTest, ShutdownSignal) {
+ ASSERT_TRUE(tls.startServer());
+
+ MockDnsTlsSocketObserver observer;
+ std::unique_ptr<DnsTlsSocket> socket;
+
+ const auto setupAndStartHandshake = [&]() {
+ socket = makeDnsTlsSocket(&observer);
+ EXPECT_TRUE(socket->initialize());
+ enableAsyncHandshake(socket);
+ EXPECT_TRUE(socket->startHandshake());
+ };
+ const auto triggerShutdown = [&](const std::string& traceLog) {
+ SCOPED_TRACE(traceLog);
+ auto before = std::chrono::steady_clock::now();
+ EXPECT_CALL(observer, onClosed);
+ socket.reset();
+ auto after = std::chrono::steady_clock::now();
+ auto delay = after - before;
+ LOG(INFO) << "Shutdown took " << delay / std::chrono::nanoseconds{1} << "ns";
+ EXPECT_LT(delay, std::chrono::seconds{1});
+ };
+
+ tls.setHangOnHandshakeForTesting(true);
+
+ // Test 1: Reset the DnsTlsSocket which is doing the handshake.
+ setupAndStartHandshake();
+ triggerShutdown("Shutdown handshake w/o query requests");
+
+ // Test 2: Reset the DnsTlsSocket which is doing the handshake with some query requests.
+ setupAndStartHandshake();
+
+ // DnsTlsSocket doesn't report the status of pending queries. The decision whether to mark
+ // a query request as failed or not is made in DnsTlsTransport.
+ EXPECT_CALL(observer, onResponse).Times(0);
+ EXPECT_TRUE(socket->query(1, makeSlice(QUERY)));
+ EXPECT_TRUE(socket->query(2, makeSlice(QUERY)));
+ triggerShutdown("Shutdown handshake w/ query requests");
+}
+
} // end of namespace net
} // end of namespace android
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 0000000..475ba8f
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/tests/Android.bp b/tests/Android.bp
index 5dcf89b..66c8632 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -102,7 +102,7 @@
"libnetdutils",
"libprotobuf-cpp-lite",
"netd_aidl_interface-ndk_platform",
- "netd_event_listener_interface-ndk_platform",
+ "netd_event_listener_interface-unstable-ndk_platform",
"server_configurable_flags",
"stats_proto",
],
@@ -135,7 +135,7 @@
"libnetd_test_resolv_utils",
"libnetdutils",
"libutils",
- "netd_event_listener_interface-ndk_platform",
+ "netd_event_listener_interface-unstable-ndk_platform",
"netd_aidl_interface-ndk_platform",
],
}
@@ -165,7 +165,6 @@
],
static_libs: [
"dnsresolver_aidl_interface-unstable-ndk_platform",
- "libbpf_android",
"libcrypto_static",
"libgmock",
"libnetd_test_dnsresponder_ndk",
@@ -175,7 +174,7 @@
"libssl",
"libutils",
"netd_aidl_interface-ndk_platform",
- "netd_event_listener_interface-ndk_platform",
+ "netd_event_listener_interface-unstable-ndk_platform",
"libipchecksum",
],
// This test talks to the DnsResolver module over a binary protocol on a socket, so keep it as
diff --git a/tests/dns_metrics_listener/Android.bp b/tests/dns_metrics_listener/Android.bp
index 74c545e..0122dc0 100644
--- a/tests/dns_metrics_listener/Android.bp
+++ b/tests/dns_metrics_listener/Android.bp
@@ -11,6 +11,6 @@
],
static_libs: [
"libutils",
- "netd_event_listener_interface-ndk_platform",
+ "netd_event_listener_interface-unstable-ndk_platform",
],
}
diff --git a/tests/dns_metrics_listener/base_metrics_listener.cpp b/tests/dns_metrics_listener/base_metrics_listener.cpp
index c46f093..e0dd972 100644
--- a/tests/dns_metrics_listener/base_metrics_listener.cpp
+++ b/tests/dns_metrics_listener/base_metrics_listener.cpp
@@ -45,7 +45,12 @@
::ndk::ScopedAStatus BaseMetricsListener::onWakeupEvent(
const std::string& /*prefix*/, int32_t /*uid*/, int32_t /*ethertype*/,
+// TODO: remove build flag when mainline release branch migrates to Android S (b/168163123).
+#ifdef __ANDROID_API_S__
+ int32_t /*ipNextHeader*/, const std::vector<uint8_t>& /*dstHw*/,
+#else
int32_t /*ipNextHeader*/, const std::vector<int8_t>& /*dstHw*/,
+#endif
const std::string& /*srcIp*/, const std::string& /*dstIp*/, int32_t /*srcPort*/,
int32_t /*dstPort*/, int64_t /*timestampNs*/) {
// default no-op
diff --git a/tests/dns_metrics_listener/base_metrics_listener.h b/tests/dns_metrics_listener/base_metrics_listener.h
index d50997d..61a3270 100644
--- a/tests/dns_metrics_listener/base_metrics_listener.h
+++ b/tests/dns_metrics_listener/base_metrics_listener.h
@@ -45,7 +45,12 @@
int32_t /*uid*/) override;
virtual ::ndk::ScopedAStatus onWakeupEvent(const std::string& /*prefix*/, int32_t /*uid*/,
int32_t /*ethertype*/, int32_t /*ipNextHeader*/,
+// TODO: remove build flag when mainline release branch migrates to Android S (b/168163123).
+#ifdef __ANDROID_API_S__
+ const std::vector<uint8_t>& /*dstHw*/,
+#else
const std::vector<int8_t>& /*dstHw*/,
+#endif
const std::string& /*srcIp*/,
const std::string& /*dstIp*/, int32_t /*srcPort*/,
int32_t /*dstPort*/,
diff --git a/tests/dns_responder/Android.bp b/tests/dns_responder/Android.bp
index 832ac88..855e72b 100644
--- a/tests/dns_responder/Android.bp
+++ b/tests/dns_responder/Android.bp
@@ -18,7 +18,7 @@
"libnetdutils",
"libssl",
"netd_aidl_interface-ndk_platform",
- "netd_event_listener_interface-ndk_platform",
+ "netd_event_listener_interface-unstable-ndk_platform",
],
srcs: [
"dns_responder.cpp",
diff --git a/tests/dns_responder/dns_tls_frontend.cpp b/tests/dns_responder/dns_tls_frontend.cpp
index d960254..3921297 100644
--- a/tests/dns_responder/dns_tls_frontend.cpp
+++ b/tests/dns_responder/dns_tls_frontend.cpp
@@ -114,7 +114,6 @@
PLOG(INFO) << "ignore creating socket failed " << 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)) {
@@ -223,6 +222,11 @@
// client, including cleanup actions.
queries_ += handleRequests(ssl.get(), client.get());
}
+
+ if (passiveClose_) {
+ LOG(DEBUG) << "hold the current connection until next connection request";
+ clientFd = std::move(client);
+ }
}
}
LOG(DEBUG) << "Ending loop";
@@ -230,6 +234,7 @@
int DnsTlsFrontend::handleRequests(SSL* ssl, int clientFd) {
int queryCounts = 0;
+ std::vector<uint8_t> reply;
pollfd fds = {.fd = clientFd, .events = POLLIN};
do {
uint8_t queryHeader[2];
@@ -263,16 +268,25 @@
uint8_t responseHeader[2];
responseHeader[0] = rlen >> 8;
responseHeader[1] = rlen;
- if (SSL_write(ssl, responseHeader, 2) != 2) {
- LOG(INFO) << "Failed to write response header";
- return queryCounts;
- }
- if (SSL_write(ssl, recv_buffer, rlen) != rlen) {
- LOG(INFO) << "Failed to write response body";
- return queryCounts;
- }
+ reply.insert(reply.end(), responseHeader, responseHeader + 2);
+ reply.insert(reply.end(), recv_buffer, recv_buffer + rlen);
+
++queryCounts;
- } while (poll(&fds, 1, 1) > 0);
+ if (queryCounts >= delayQueries_) {
+ break;
+ }
+ } while (poll(&fds, 1, delayQueriesTimeout_) > 0);
+
+ if (queryCounts < delayQueries_) {
+ LOG(WARNING) << "Expect " << delayQueries_ << " queries, but actually received "
+ << queryCounts << " queries";
+ }
+
+ const int replyLen = reply.size();
+ LOG(DEBUG) << "Sending " << queryCounts << "queries at once, byte = " << replyLen;
+ if (SSL_write(ssl, reply.data(), replyLen) != replyLen) {
+ LOG(WARNING) << "Failed to write response body";
+ }
LOG(DEBUG) << __func__ << " return: " << queryCounts;
return queryCounts;
diff --git a/tests/dns_responder/dns_tls_frontend.h b/tests/dns_responder/dns_tls_frontend.h
index 6ba5681..02cdc21 100644
--- a/tests/dns_responder/dns_tls_frontend.h
+++ b/tests/dns_responder/dns_tls_frontend.h
@@ -62,6 +62,12 @@
void set_chain_length(int length) { chain_length_ = length; }
void setHangOnHandshakeForTesting(bool hangOnHandshake) { hangOnHandshake_ = hangOnHandshake; }
+ // Set DnsTlsFrontend to not reply any response until there are |delay| responses or timeout.
+ void setDelayQueries(int delay) { delayQueries_ = delay; }
+ void setDelayQueriesTimeout(int timeout) { delayQueriesTimeout_ = timeout; }
+
+ void setPassiveClose(bool passiveClose) { passiveClose_ = passiveClose; }
+
static constexpr char kDefaultListenAddr[] = "127.0.0.3";
static constexpr char kDefaultListenService[] = "853";
static constexpr char kDefaultBackendAddr[] = "127.0.0.3";
@@ -94,6 +100,9 @@
std::mutex update_mutex_;
int chain_length_ = 1;
std::atomic<bool> hangOnHandshake_ = false;
+ std::atomic<int> delayQueries_ = 1;
+ std::atomic<int> delayQueriesTimeout_ = 1;
+ std::atomic<bool> passiveClose_ = false;
};
} // namespace test
diff --git a/tests/dnsresolver_binder_test.cpp b/tests/dnsresolver_binder_test.cpp
index 6fb73f3..0ad9b24 100644
--- a/tests/dnsresolver_binder_test.cpp
+++ b/tests/dnsresolver_binder_test.cpp
@@ -21,12 +21,17 @@
#include <netdb.h>
#include <iostream>
+#include <regex>
+#include <string>
+#include <thread>
#include <vector>
#include <aidl/android/net/IDnsResolver.h>
#include <android-base/file.h>
+#include <android-base/format.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <gmock/gmock-matchers.h>
@@ -41,9 +46,13 @@
#include "dns_responder_client_ndk.h"
using aidl::android::net::IDnsResolver;
+using aidl::android::net::ResolverHostsParcel;
+using aidl::android::net::ResolverOptionsParcel;
using aidl::android::net::ResolverParamsParcel;
using aidl::android::net::metrics::INetdEventListener;
+using android::base::ReadFdToString;
using android::base::StringPrintf;
+using android::base::unique_fd;
using android::net::ResolverStats;
using android::net::metrics::TestOnDnsEvent;
using android::netdutils::Stopwatch;
@@ -52,6 +61,37 @@
// Sync from TEST_NETID in dns_responder_client.cpp as resolv_integration_test.cpp does.
constexpr int TEST_NETID = 30;
+namespace {
+
+std::vector<std::string> dumpService(ndk::SpAIBinder binder) {
+ unique_fd localFd, remoteFd;
+ bool success = Pipe(&localFd, &remoteFd);
+ EXPECT_TRUE(success) << "Failed to open pipe for dumping: " << strerror(errno);
+ if (!success) return {};
+
+ // dump() blocks until another thread has consumed all its output.
+ std::thread dumpThread = std::thread([binder, remoteFd{std::move(remoteFd)}]() {
+ EXPECT_EQ(STATUS_OK, AIBinder_dump(binder.get(), remoteFd, nullptr, 0));
+ });
+
+ std::string dumpContent;
+
+ EXPECT_TRUE(ReadFdToString(localFd.get(), &dumpContent))
+ << "Error during dump: " << strerror(errno);
+ dumpThread.join();
+
+ std::stringstream dumpStream(std::move(dumpContent));
+ std::vector<std::string> lines;
+ std::string line;
+ while (std::getline(dumpStream, line)) {
+ lines.push_back(std::move(line));
+ }
+
+ return lines;
+}
+
+} // namespace
+
class DnsResolverBinderTest : public ::testing::Test {
public:
DnsResolverBinderTest() {
@@ -65,12 +105,144 @@
}
~DnsResolverBinderTest() {
+ expectLog();
// Destroy cache for test
mDnsResolver->destroyNetworkCache(TEST_NETID);
}
protected:
+ void expectLog() {
+ ndk::SpAIBinder netdBinder = ndk::SpAIBinder(AServiceManager_getService("netd"));
+ // This could happen when the test isn't running as root, or if netd isn't running.
+ assert(nullptr != netdBinder.get());
+ // Send the service dump request to netd.
+ std::vector<std::string> lines = dumpService(netdBinder);
+
+ // Basic regexp to match dump output lines. Matches the beginning and end of the line, and
+ // puts the output of the command itself into the first match group.
+ // Example: " 11-05 00:23:39.481 myCommand(args) <2.02ms>".
+ // Note: There are 4 leading blank characters in Q, but 6 in R.
+ const std::basic_regex lineRegex(
+ "^ {4,6}[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} "
+ "(.*)"
+ " <[0-9]+[.][0-9]{2}ms>$");
+
+ // For each element of testdata, check that the expected output appears in the dump output.
+ // If not, fail the test and use hintRegex to print similar lines to assist in debugging.
+ for (const auto& td : mExpectedLogData) {
+ const bool found =
+ std::any_of(lines.begin(), lines.end(), [&](const std::string& line) {
+ std::smatch match;
+ if (!std::regex_match(line, match, lineRegex)) return false;
+ return (match.size() == 2) && (match[1].str() == td.output);
+ });
+ EXPECT_TRUE(found) << "Didn't find line '" << td.output << "' in dumpsys output.";
+ if (found) continue;
+ std::cerr << "Similar lines" << std::endl;
+ for (const auto& line : lines) {
+ if (std::regex_search(line, std::basic_regex(td.hintRegex))) {
+ std::cerr << line << std::endl;
+ }
+ }
+ }
+
+ // The log output is different between R and S, either one is fine for the
+ // test to avoid test compatible issue.
+ // TODO: Remove after S.
+ for (const auto& td : mExpectedLogDataWithPacel) {
+ const bool found =
+ std::any_of(lines.begin(), lines.end(), [&](const std::string& line) {
+ std::smatch match;
+ if (!std::regex_match(line, match, lineRegex)) return false;
+ return (match.size() == 2) && ((match[1].str() == td.withPacel.output) ||
+ (match[1].str() == td.withoutPacel.output));
+ });
+ EXPECT_TRUE(found) << fmt::format("Didn't find line '{}' or '{}' in dumpsys output.",
+ td.withPacel.output, td.withoutPacel.output);
+ if (found) continue;
+ std::cerr << "Similar lines" << std::endl;
+ for (const auto& line : lines) {
+ if (std::regex_search(line, std::basic_regex(td.withPacel.hintRegex))) {
+ std::cerr << line << std::endl;
+ }
+ if (std::regex_search(line, std::basic_regex(td.withoutPacel.hintRegex))) {
+ std::cerr << line << std::endl;
+ }
+ }
+ }
+ }
+
+ struct LogData {
+ // Expected contents of the dump command.
+ const std::string output;
+ // A regex that might be helpful in matching relevant lines in the output.
+ // Used to make it easier to add test cases for this code.
+ const std::string hintRegex;
+ };
+
+ // TODO: Remove this struct and below toString methods after S.
+ struct PossibleLogData {
+ LogData withPacel;
+ LogData withoutPacel;
+ };
+
+ std::string toString(const std::vector<ResolverHostsParcel>& parms) {
+ std::string o;
+ const size_t size = parms.size();
+ for (size_t i = 0; i < size; ++i) {
+ o.append(fmt::format("ResolverHostsParcel{{ipAddr: {}, hostName: {}}}", parms[i].ipAddr,
+ parms[i].hostName));
+ if (i + 1 < size) o.append(", ");
+ }
+ return o;
+ }
+
+ std::string toString(const ResolverOptionsParcel& parms) {
+ return fmt::format("ResolverOptionsParcel{{hosts: [{}], tcMode: {}, enforceDnsUid: {}}}",
+ toString(parms.hosts), parms.tcMode, parms.enforceDnsUid);
+ }
+
+ std::string toString(const ResolverParamsParcel& parms) {
+ return fmt::format(
+ "ResolverParamsParcel{{netId: {}, sampleValiditySeconds: {}, successThreshold: {}, "
+ "minSamples: {}, "
+ "maxSamples: {}, baseTimeoutMsec: {}, retryCount: {}, "
+ "servers: [{}], domains: [{}], "
+ "tlsName: {}, tlsServers: [{}], "
+ "tlsFingerprints: [{}], "
+ "caCertificate: {}, tlsConnectTimeoutMs: {}, "
+ "resolverOptions: {}, transportTypes: [{}]}}",
+ parms.netId, parms.sampleValiditySeconds, parms.successThreshold, parms.minSamples,
+ parms.maxSamples, parms.baseTimeoutMsec, parms.retryCount,
+ fmt::join(parms.servers, ", "), fmt::join(parms.domains, ", "), parms.tlsName,
+ fmt::join(parms.tlsServers, ", "), fmt::join(parms.tlsFingerprints, ", "),
+ android::base::StringReplace(parms.caCertificate, "\n", "\\n", true),
+ parms.tlsConnectTimeoutMs, toString(parms.resolverOptions),
+ fmt::join(parms.transportTypes, ", "));
+ }
+
+ PossibleLogData toSetResolverConfigurationLogData(const ResolverParamsParcel& parms,
+ int returnCode = 0) {
+ std::string outputWithParcel = "setResolverConfiguration(" + toString(parms) + ")";
+ std::string hintRegexWithParcel = fmt::format("setResolverConfiguration.*{}", parms.netId);
+
+ std::string outputWithoutParcel = "setResolverConfiguration()";
+ std::string hintRegexWithoutParcel = "setResolverConfiguration";
+ if (returnCode != 0) {
+ outputWithParcel.append(fmt::format(" -> ServiceSpecificException({}, \"{}\")",
+ returnCode, strerror(returnCode)));
+ hintRegexWithParcel.append(fmt::format(".*{}", returnCode));
+ outputWithoutParcel.append(fmt::format(" -> ServiceSpecificException({}, \"{}\")",
+ returnCode, strerror(returnCode)));
+ hintRegexWithoutParcel.append(fmt::format(".*{}", returnCode));
+ }
+ return {{std::move(outputWithParcel), std::move(hintRegexWithParcel)},
+ {std::move(outputWithoutParcel), std::move(hintRegexWithoutParcel)}};
+ }
+
std::shared_ptr<aidl::android::net::IDnsResolver> mDnsResolver;
+ std::vector<LogData> mExpectedLogData;
+ std::vector<PossibleLogData> mExpectedLogDataWithPacel;
};
class TimedOperation : public Stopwatch {
@@ -95,6 +267,9 @@
::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(nullptr);
ASSERT_FALSE(status.isOk());
ASSERT_EQ(EINVAL, status.getServiceSpecificError());
+ mExpectedLogData.push_back(
+ {"registerEventListener() -> ServiceSpecificException(22, \"Invalid argument\")",
+ "registerEventListener.*22"});
}
TEST_F(DnsResolverBinderTest, RegisterEventListener_DuplicateSubscription) {
@@ -104,11 +279,15 @@
std::shared_ptr<DummyListener> dummyListener = ndk::SharedRefBase::make<DummyListener>();
::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(dummyListener);
ASSERT_TRUE(status.isOk()) << status.getMessage();
+ mExpectedLogData.push_back({"registerEventListener()", "registerEventListener.*"});
// Expect to subscribe failed with registered listener instance.
status = mDnsResolver->registerEventListener(dummyListener);
ASSERT_FALSE(status.isOk());
ASSERT_EQ(EEXIST, status.getServiceSpecificError());
+ mExpectedLogData.push_back(
+ {"registerEventListener() -> ServiceSpecificException(17, \"File exists\")",
+ "registerEventListener.*17"});
}
// TODO: Move this test to resolv_integration_test.cpp
@@ -161,6 +340,7 @@
ndk::SharedRefBase::make<TestOnDnsEvent>(expectedResults);
::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(testOnDnsEvent);
ASSERT_TRUE(status.isOk()) << status.getMessage();
+ mExpectedLogData.push_back({"registerEventListener()", "registerEventListener.*"});
// DNS queries.
// Once all expected events of expectedResults are received by the listener, the unit test will
@@ -238,10 +418,13 @@
SCOPED_TRACE(StringPrintf("test case %zu should have passed", i));
SCOPED_TRACE(status.getMessage());
EXPECT_EQ(0, status.getServiceSpecificError());
+ mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
} else {
SCOPED_TRACE(StringPrintf("test case %zu should have failed", i));
EXPECT_EQ(EX_SERVICE_SPECIFIC, status.getExceptionCode());
EXPECT_EQ(td.expectedReturnCode, status.getServiceSpecificError());
+ mExpectedLogDataWithPacel.push_back(
+ toSetResolverConfigurationLogData(resolverParams, td.expectedReturnCode));
}
}
}
@@ -252,6 +435,7 @@
resolverParams.transportTypes = {IDnsResolver::TRANSPORT_WIFI, IDnsResolver::TRANSPORT_VPN};
::ndk::ScopedAStatus status = mDnsResolver->setResolverConfiguration(resolverParams);
EXPECT_TRUE(status.isOk()) << status.getMessage();
+ mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
// TODO: Find a way to fix a potential deadlock here if it's larger than pipe buffer
// size(65535).
android::base::unique_fd writeFd, readFd;
@@ -268,6 +452,7 @@
auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
::ndk::ScopedAStatus status = mDnsResolver->setResolverConfiguration(resolverParams);
EXPECT_TRUE(status.isOk()) << status.getMessage();
+ mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
android::base::unique_fd writeFd, readFd;
EXPECT_TRUE(Pipe(&readFd, &writeFd));
EXPECT_EQ(mDnsResolver->dump(writeFd.get(), nullptr, 0), 0);
@@ -291,6 +476,7 @@
TEST_NETID, testParams, servers, domains, "", {});
::ndk::ScopedAStatus status = mDnsResolver->setResolverConfiguration(resolverParams);
EXPECT_TRUE(status.isOk()) << status.getMessage();
+ mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
std::vector<std::string> res_servers;
std::vector<std::string> res_domains;
@@ -335,46 +521,68 @@
// Create a new network cache.
EXPECT_TRUE(mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).isOk());
+ mExpectedLogData.push_back({"createNetworkCache(31)", "createNetworkCache.*31"});
// create it again, expect a EEXIST.
EXPECT_EQ(EEXIST,
mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).getServiceSpecificError());
+ mExpectedLogData.push_back(
+ {"createNetworkCache(31) -> ServiceSpecificException(17, \"File exists\")",
+ "createNetworkCache.*31.*17"});
// destroy it.
EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+ mExpectedLogData.push_back({"destroyNetworkCache(31)", "destroyNetworkCache.*31"});
// re-create it
EXPECT_TRUE(mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).isOk());
+ mExpectedLogData.push_back({"createNetworkCache(31)", "createNetworkCache.*31"});
// destroy it.
EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+ mExpectedLogData.push_back({"destroyNetworkCache(31)", "destroyNetworkCache.*31"});
// re-destroy it
EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+ mExpectedLogData.push_back({"destroyNetworkCache(31)", "destroyNetworkCache.*31"});
}
TEST_F(DnsResolverBinderTest, FlushNetworkCache) {
SKIP_IF_REMOTE_VERSION_LESS_THAN(mDnsResolver.get(), 4);
// cache has beed created in DnsResolverBinderTest constructor
EXPECT_TRUE(mDnsResolver->flushNetworkCache(TEST_NETID).isOk());
+ mExpectedLogData.push_back({"flushNetworkCache(30)", "destroyNetworkCache.*30"});
EXPECT_EQ(ENONET, mDnsResolver->flushNetworkCache(-1).getServiceSpecificError());
+ mExpectedLogData.push_back(
+ {"flushNetworkCache(-1) -> ServiceSpecificException(64, \"Machine is not on the "
+ "network\")",
+ "flushNetworkCache.*-1.*64"});
}
TEST_F(DnsResolverBinderTest, setLogSeverity) {
// Expect fail
EXPECT_EQ(EINVAL, mDnsResolver->setLogSeverity(-1).getServiceSpecificError());
+ mExpectedLogData.push_back(
+ {"setLogSeverity(-1) -> ServiceSpecificException(22, \"Invalid argument\")",
+ "flushNetworkCache.*-1.*22"});
// Test set different log level
EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_VERBOSE).isOk());
+ mExpectedLogData.push_back({"setLogSeverity(0)", "setLogSeverity.*0"});
EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_DEBUG).isOk());
+ mExpectedLogData.push_back({"setLogSeverity(1)", "setLogSeverity.*1"});
EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_INFO).isOk());
+ mExpectedLogData.push_back({"setLogSeverity(2)", "setLogSeverity.*2"});
EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_WARNING).isOk());
+ mExpectedLogData.push_back({"setLogSeverity(3)", "setLogSeverity.*3"});
EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_ERROR).isOk());
+ mExpectedLogData.push_back({"setLogSeverity(4)", "setLogSeverity.*4"});
// Set back to default
EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_WARNING).isOk());
+ mExpectedLogData.push_back({"setLogSeverity(3)", "setLogSeverity.*3"});
}
diff --git a/tests/resolv_gold_test.cpp b/tests/resolv_gold_test.cpp
index e8df5f6..c5b24aa 100644
--- a/tests/resolv_gold_test.cpp
+++ b/tests/resolv_gold_test.cpp
@@ -368,6 +368,8 @@
ASSERT_TRUE(tls.startServer());
ASSERT_NO_FATAL_FAILURE(SetResolversWithTls());
EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address()));
+ tls.setDelayQueries(2);
+ tls.setDelayQueriesTimeout(200);
dns.clearQueries();
addrinfo* res = nullptr;
@@ -429,6 +431,8 @@
test::DNSResponder dns(test::DNSResponder::MappingType::BINARY_PACKET);
ASSERT_TRUE(dns.startServer());
test::DnsTlsFrontend tls;
+ tls.setDelayQueries(2);
+ tls.setDelayQueriesTimeout(200);
if (protocol == DnsProtocol::CLEARTEXT) {
ASSERT_NO_FATAL_FAILURE(SetResolvers());
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index e347126..01ed421 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -27,7 +27,6 @@
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <binder/ProcessState.h>
-#include <bpf/BpfUtils.h>
#include <cutils/sockets.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
@@ -77,6 +76,12 @@
constexpr int TEST_VPN_NETID = 65502;
constexpr int MAXPACKET = (8 * 1024);
+const std::string kSortNameserversFlag("persist.device_config.netd_native.sort_nameservers");
+const std::string kDotConnectTimeoutMsFlag(
+ "persist.device_config.netd_native.dot_connect_timeout_ms");
+const std::string kDotAsyncHandshakeFlag("persist.device_config.netd_native.dot_async_handshake");
+const std::string kDotMaxretriesFlag("persist.device_config.netd_native.dot_maxtries");
+
// 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,
@@ -178,8 +183,8 @@
// service.
AIBinder* binder = AServiceManager_getService("dnsresolver");
- ndk::SpAIBinder resolvBinder = ndk::SpAIBinder(binder);
- auto resolvService = aidl::android::net::IDnsResolver::fromBinder(resolvBinder);
+ sResolvBinder = ndk::SpAIBinder(binder);
+ auto resolvService = aidl::android::net::IDnsResolver::fromBinder(sResolvBinder);
ASSERT_NE(nullptr, resolvService.get());
// Subscribe the death recipient to the service IDnsResolver for detecting Netd death.
@@ -365,11 +370,16 @@
// Use a shared static death recipient to monitor the service death. The static death
// recipient could monitor the death not only during the test but also between tests.
static AIBinder_DeathRecipient* sResolvDeathRecipient; // Initialized in SetUpTestSuite.
+
+ // The linked AIBinder_DeathRecipient will be automatically unlinked if the binder is deleted.
+ // The binder needs to be retained throughout tests.
+ static ndk::SpAIBinder sResolvBinder;
};
// Initialize static member of class.
std::shared_ptr<DnsMetricsListener> ResolverTest::sDnsMetricsListener;
AIBinder_DeathRecipient* ResolverTest::sResolvDeathRecipient;
+ndk::SpAIBinder ResolverTest::sResolvBinder;
TEST_F(ResolverTest, GetHostByName) {
constexpr char nonexistent_host_name[] = "nonexistent.example.com.";
@@ -382,7 +392,7 @@
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);
+ EXPECT_EQ(HOST_NOT_FOUND, h_errno);
dns.clearQueries();
result = gethostbyname("hello");
@@ -394,6 +404,19 @@
EXPECT_TRUE(result->h_addr_list[1] == nullptr);
}
+TEST_F(ResolverTest, GetHostByName_NULL) {
+ // Most libc implementations would just crash on gethostbyname(NULL). Instead, Bionic
+ // serializes the null argument over dnsproxyd, causing the server-side to crash!
+ // This is a regression test.
+ const char* const testcases[] = {nullptr, "", "^"};
+ for (const char* name : testcases) {
+ SCOPED_TRACE(fmt::format("gethostbyname({})", name ? name : "NULL"));
+ const hostent* result = gethostbyname(name);
+ EXPECT_TRUE(result == nullptr);
+ EXPECT_EQ(HOST_NOT_FOUND, h_errno);
+ }
+}
+
TEST_F(ResolverTest, GetHostByName_cnames) {
constexpr char host_name[] = "host.example.com.";
size_t cnamecount = 0;
@@ -893,6 +916,51 @@
EXPECT_TRUE(result == nullptr);
}
+TEST_F(ResolverTest, GetAddrInfoForCaseInSensitiveDomains) {
+ test::DNSResponder dns;
+ const char* host_name = "howdy.example.com.";
+ const char* host_name2 = "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"},
+ {host_name2, ns_type::ns_t_a, "1.2.3.5"},
+ {host_name2, ns_type::ns_t_aaaa, "::1.2.3.5"},
+ };
+ StartDns(dns, records);
+ ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+ ScopedAddrinfo hostname_result = safe_getaddrinfo("howdy", nullptr, nullptr);
+ EXPECT_TRUE(hostname_result != nullptr);
+ const size_t hostname1_count_after_first_query = GetNumQueries(dns, host_name);
+ EXPECT_LE(1U, hostname1_count_after_first_query);
+ // Could be A or AAAA
+ std::string hostname_result_str = ToString(hostname_result);
+ EXPECT_TRUE(hostname_result_str == "1.2.3.4" || hostname_result_str == "::1.2.3.4");
+
+ // Verify that the name is cached.
+ ScopedAddrinfo hostname2_result = safe_getaddrinfo("HOWDY", nullptr, nullptr);
+ EXPECT_TRUE(hostname2_result != nullptr);
+ const size_t hostname1_count_after_second_query = GetNumQueries(dns, host_name);
+ EXPECT_LE(1U, hostname1_count_after_second_query);
+
+ // verify that there is no change in num of queries for howdy.example.com
+ EXPECT_EQ(hostname1_count_after_first_query, hostname1_count_after_second_query);
+
+ // Number of queries for HOWDY.example.com would be >= 1 if domain names
+ // are considered case-sensitive, else number of queries should be 0.
+ const size_t hostname2_count = GetNumQueries(dns, host_name2);
+ EXPECT_EQ(0U,hostname2_count);
+ std::string hostname2_result_str = ToString(hostname2_result);
+ EXPECT_TRUE(hostname2_result_str == "1.2.3.4" || hostname2_result_str == "::1.2.3.4");
+
+ // verify that the result is still the same address even though
+ // mixed-case string is not in the DNS
+ ScopedAddrinfo result = safe_getaddrinfo("HowDY", nullptr, nullptr);
+ EXPECT_TRUE(result != nullptr);
+ std::string result_str = ToString(result);
+ EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4");
+}
+
TEST_F(ResolverTest, MultidomainResolution) {
constexpr char host_name[] = "nihao.example2.com.";
std::vector<std::string> searchDomains = {"example1.com", "example2.com", "example3.com"};
@@ -1081,7 +1149,6 @@
}
TEST_F(ResolverTest, SkipBadServersDueToInternalError) {
- const std::string kSortNameserversFlag("persist.device_config.netd_native.sort_nameservers");
constexpr char listen_addr1[] = "fe80::1";
constexpr char listen_addr2[] = "255.255.255.255";
constexpr char listen_addr3[] = "127.0.0.3";
@@ -1101,6 +1168,9 @@
SCOPED_TRACE(fmt::format("sortNameversFlag_{}", sortNameserversFlag));
ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, sortNameserversFlag);
+ // Re-setup test network to make experiment flag take effect.
+ resetNetwork();
+
ASSERT_TRUE(mDnsClient.SetResolversFromParcel(setupParams));
// Start sending synchronized querying.
@@ -1129,7 +1199,6 @@
}
TEST_F(ResolverTest, SkipBadServersDueToTimeout) {
- const std::string kSortNameserversFlag("persist.device_config.netd_native.sort_nameservers");
constexpr char listen_addr1[] = "127.0.0.3";
constexpr char listen_addr2[] = "127.0.0.4";
int counter = 0; // To generate unique hostnames.
@@ -1153,6 +1222,9 @@
SCOPED_TRACE(fmt::format("sortNameversFlag_{}", sortNameserversFlag));
ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, sortNameserversFlag);
+ // Re-setup test network to make experiment flag take effect.
+ resetNetwork();
+
ASSERT_TRUE(mDnsClient.SetResolversFromParcel(setupParams));
// Start sending synchronized querying.
@@ -2050,12 +2122,7 @@
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 unused;
- EXPECT_EQ(-1, read(fd, &unused, sizeof unused));
- EXPECT_EQ(EBADF, errno);
- return n;
+ return resNetworkResult(fd, rcode, buf, bufLen);
}
return -1;
}
@@ -4093,10 +4160,6 @@
}
TEST_F(ResolverTest, BlockDnsQueryWithUidRule) {
- // This test relies on blocking traffic on loopback, which xt_qtaguid does not do.
- // See aosp/358413 and b/34444781 for why.
- SKIP_IF_BPF_NOT_SUPPORTED;
-
constexpr char listen_addr1[] = "127.0.0.4";
constexpr char listen_addr2[] = "::1";
constexpr char host_name[] = "howdy.example.com.";
@@ -4144,8 +4207,6 @@
}
TEST_F(ResolverTest, EnforceDnsUid) {
- SKIP_IF_BPF_NOT_SUPPORTED;
-
constexpr char listen_addr1[] = "127.0.0.4";
constexpr char listen_addr2[] = "::1";
constexpr char host_name[] = "howdy.example.com.";
@@ -4217,9 +4278,6 @@
}
TEST_F(ResolverTest, ConnectTlsServerTimeout) {
- const std::string kDotConnectTimeoutMsFlag(
- "persist.device_config.netd_native.dot_connect_timeout_ms");
- constexpr int expectedTimeout = 1000;
constexpr char hostname1[] = "query1.example.com.";
constexpr char hostname2[] = "query2.example.com.";
const std::vector<DnsRecord> records = {
@@ -4227,53 +4285,177 @@
{hostname2, ns_type::ns_t_a, "1.2.3.5"},
};
- test::DNSResponder dns;
- StartDns(dns, records);
- test::DnsTlsFrontend tls;
- ASSERT_TRUE(tls.startServer());
-
// The resolver will adjust the timeout value to 1000ms since the value is too small.
ScopedSystemProperties scopedSystemProperties(kDotConnectTimeoutMsFlag, "100");
- // Set up resolver to opportunistic mode with the default configuration.
- const ResolverParamsParcel parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
- ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
- EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
- EXPECT_TRUE(tls.waitForQueries(1));
- tls.clearQueries();
- dns.clearQueries();
+ static const struct TestConfig {
+ bool asyncHandshake;
+ int maxRetries;
- // The server becomes unresponsive to the handshake request.
- tls.setHangOnHandshakeForTesting(true);
+ // if asyncHandshake:
+ // expectedTimeout = dotConnectTimeoutMs * maxRetries
+ // otherwise:
+ // expectedTimeout = dotConnectTimeoutMs
+ int expectedTimeout;
+ } testConfigs[] = {
+ // Test mis-configured dot_maxtries flag.
+ {false, 0, 1000}, {true, 0, 1000},
- // Expect the things happening in getaddrinfo():
- // 1. Connect to the private DNS server.
- // 2. SSL handshake times out.
- // 3. Fallback to UDP transport, and then get the answer.
- const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
- auto [result, timeTakenMs] = safe_getaddrinfo_time_taken(hostname1, nullptr, hints);
+ {false, 1, 1000}, {false, 3, 1000}, {true, 1, 1000}, {true, 3, 3000},
+ };
- EXPECT_NE(nullptr, result);
- EXPECT_EQ(0, tls.queries());
- EXPECT_EQ(1U, GetNumQueries(dns, hostname1));
- EXPECT_EQ(records.at(0).addr, ToString(result));
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.asyncHandshake, config.maxRetries));
- // A loose upper bound is set by adding 2000ms buffer time. Theoretically, getaddrinfo()
- // should just take a bit more than expetTimeout milliseconds.
- EXPECT_GE(timeTakenMs, expectedTimeout);
- EXPECT_LE(timeTakenMs, expectedTimeout + 2000);
+ // Because a DnsTlsTransport lasts at least 5 minutes in spite of network
+ // destroyed, let the resolver creates an unique DnsTlsTransport every time
+ // so that the DnsTlsTransport won't interfere the other tests.
+ const std::string addr = getUniqueIPv4Address();
+ test::DNSResponder dns(addr);
+ StartDns(dns, records);
+ test::DnsTlsFrontend tls(addr, "853", addr, "53");
+ ASSERT_TRUE(tls.startServer());
- // Set the server to be responsive. Verify that the resolver will attempt to reconnect
- // to the server and then get the result within the timeout.
- tls.setHangOnHandshakeForTesting(false);
- std::tie(result, timeTakenMs) = safe_getaddrinfo_time_taken(hostname2, nullptr, hints);
+ ScopedSystemProperties scopedSystemProperties1(kDotAsyncHandshakeFlag,
+ config.asyncHandshake ? "1" : "0");
+ ScopedSystemProperties scopedSystemProperties2(kDotMaxretriesFlag,
+ std::to_string(config.maxRetries));
+ resetNetwork();
- EXPECT_NE(nullptr, result);
- EXPECT_TRUE(tls.waitForQueries(1));
- EXPECT_EQ(1U, GetNumQueries(dns, hostname2));
- EXPECT_EQ(records.at(1).addr, ToString(result));
+ // Set up resolver to opportunistic mode.
+ auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+ parcel.servers = {addr};
+ parcel.tlsServers = {addr};
+ ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+ EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
+ EXPECT_TRUE(tls.waitForQueries(1));
+ tls.clearQueries();
+ dns.clearQueries();
- EXPECT_LE(timeTakenMs, expectedTimeout);
+ // The server becomes unresponsive to the handshake request.
+ tls.setHangOnHandshakeForTesting(true);
+
+ // Expect the things happening in getaddrinfo():
+ // 1. Connect to the private DNS server.
+ // 2. SSL handshake times out.
+ // 3. Fallback to UDP transport, and then get the answer.
+ const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+ auto [result, timeTakenMs] = safe_getaddrinfo_time_taken(hostname1, nullptr, hints);
+
+ EXPECT_NE(nullptr, result);
+ EXPECT_EQ(0, tls.queries());
+ EXPECT_EQ(1U, GetNumQueries(dns, hostname1));
+ EXPECT_EQ(records.at(0).addr, ToString(result));
+
+ // A loose upper bound is set by adding 1000ms buffer time. Theoretically, getaddrinfo()
+ // should just take a bit more than expetTimeout milliseconds.
+ EXPECT_GE(timeTakenMs, config.expectedTimeout);
+ EXPECT_LE(timeTakenMs, config.expectedTimeout + 1000);
+
+ // Set the server to be responsive. Verify that the resolver will attempt to reconnect
+ // to the server and then get the result within the timeout.
+ tls.setHangOnHandshakeForTesting(false);
+ std::tie(result, timeTakenMs) = safe_getaddrinfo_time_taken(hostname2, nullptr, hints);
+
+ EXPECT_NE(nullptr, result);
+ EXPECT_TRUE(tls.waitForQueries(1));
+ EXPECT_EQ(1U, GetNumQueries(dns, hostname2));
+ EXPECT_EQ(records.at(1).addr, ToString(result));
+
+ EXPECT_LE(timeTakenMs, 200);
+ }
+}
+
+TEST_F(ResolverTest, ConnectTlsServerTimeout_ConcurrentQueries) {
+ constexpr uint32_t cacheFlag = ANDROID_RESOLV_NO_CACHE_LOOKUP;
+ constexpr char hostname[] = "hello.example.com.";
+ const std::vector<DnsRecord> records = {
+ {hostname, ns_type::ns_t_a, "1.2.3.4"},
+ };
+
+ static const struct TestConfig {
+ bool asyncHandshake;
+ int dotConnectTimeoutMs;
+ int maxRetries;
+ int concurrency;
+
+ // if asyncHandshake:
+ // expectedTimeout = dotConnectTimeoutMs * maxRetries
+ // otherwise:
+ // expectedTimeout = dotConnectTimeoutMs * concurrency
+ int expectedTimeout;
+ } testConfigs[] = {
+ // clang-format off
+ {false, 1000, 1, 5, 5000},
+ {false, 1000, 3, 5, 5000},
+ {true, 1000, 1, 5, 1000},
+ {true, 2500, 1, 10, 2500},
+ {true, 1000, 3, 5, 3000},
+ // clang-format on
+ };
+
+ // Launch query threads. Expected behaviors are:
+ // - when dot_async_handshake is disabled, one of the query threads triggers a
+ // handshake and then times out. Then same as another query thread, and so forth.
+ // - when dot_async_handshake is enabled, only one handshake is triggered, and then
+ // all of the query threads time out at the same time.
+ for (const auto& config : testConfigs) {
+ ScopedSystemProperties scopedSystemProperties1(kDotConnectTimeoutMsFlag,
+ std::to_string(config.dotConnectTimeoutMs));
+ ScopedSystemProperties scopedSystemProperties2(kDotAsyncHandshakeFlag,
+ config.asyncHandshake ? "1" : "0");
+ ScopedSystemProperties scopedSystemProperties3(kDotMaxretriesFlag,
+ std::to_string(config.maxRetries));
+ resetNetwork();
+
+ for (const auto& dnsMode : {"OPPORTUNISTIC", "STRICT"}) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.asyncHandshake, dnsMode));
+
+ // Because a DnsTlsTransport lasts at least 5 minutes in spite of network
+ // destroyed, let the resolver creates an unique DnsTlsTransport every time
+ // so that the DnsTlsTransport won't interfere the other tests.
+ const std::string addr = getUniqueIPv4Address();
+ test::DNSResponder dns(addr);
+ StartDns(dns, records);
+ test::DnsTlsFrontend tls(addr, "853", addr, "53");
+ ASSERT_TRUE(tls.startServer());
+
+ auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+ parcel.servers = {addr};
+ parcel.tlsServers = {addr};
+ if (dnsMode == "STRICT") parcel.tlsName = kDefaultPrivateDnsHostName;
+ ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+ EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
+ EXPECT_TRUE(tls.waitForQueries(1));
+
+ // The server becomes unresponsive to the handshake request.
+ tls.setHangOnHandshakeForTesting(true);
+
+ Stopwatch s;
+ std::vector<std::thread> threads(config.concurrency);
+ for (std::thread& thread : threads) {
+ thread = std::thread([&]() {
+ int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
+ dnsMode == "STRICT" ? expectAnswersNotValid(fd, -ETIMEDOUT)
+ : expectAnswersValid(fd, AF_INET, "1.2.3.4");
+ });
+ }
+ for (std::thread& thread : threads) {
+ thread.join();
+ }
+
+ const int timeTakenMs = s.timeTakenUs() / 1000;
+ // A loose upper bound is set by adding 1000ms buffer time. Theoretically, it should
+ // just take a bit more than expetTimeout milliseconds for the result.
+ EXPECT_GE(timeTakenMs, config.expectedTimeout);
+ EXPECT_LE(timeTakenMs, config.expectedTimeout + 1000);
+
+ // Recover the server from being unresponsive and try again.
+ tls.setHangOnHandshakeForTesting(false);
+ int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
+ expectAnswersValid(fd, AF_INET, "1.2.3.4");
+ }
+ }
}
TEST_F(ResolverTest, FlushNetworkCache) {
@@ -4574,6 +4756,7 @@
StartDns(dns2, {});
test::DnsTlsFrontend workableTls(addr1, "853", addr1, "53");
test::DnsTlsFrontend unresponsiveTls(addr2, "853", addr2, "53");
+ int validationAttemptsToUnresponsiveTls = 1;
unresponsiveTls.setHangOnHandshakeForTesting(true);
ASSERT_TRUE(workableTls.startServer());
ASSERT_TRUE(unresponsiveTls.startServer());
@@ -4587,7 +4770,9 @@
// Check the validation results.
EXPECT_TRUE(WaitForPrivateDnsValidation(workableTls.listen_address(), true));
EXPECT_TRUE(WaitForPrivateDnsValidation(unusable_addr, false));
- EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), 1); // The validation is still in progress.
+
+ // The validation is still in progress.
+ EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), validationAttemptsToUnresponsiveTls);
static const struct TestConfig {
std::vector<std::string> tlsServers;
@@ -4617,13 +4802,23 @@
SCOPED_TRACE(serverAddr);
if (serverAddr == workableTls.listen_address()) {
if (dnsModeChanged) {
- // In despite of the identical IP address, the server is regarded as a different
+ // Despite the identical IP address, the server is regarded as a different
// server when DnsTlsServer.name is different. The resolver treats it as a
// different object and begins the validation process.
EXPECT_TRUE(WaitForPrivateDnsValidation(serverAddr, true));
}
} else if (serverAddr == unresponsiveTls.listen_address()) {
- // No revalidation needed for the server which have been marked as in_progesss.
+ if (dnsModeChanged) {
+ // Despite the identical IP address, the server is regarded as a different
+ // server when DnsTlsServer.name is different. The resolver treats it as a
+ // different object and begins the validation process.
+ validationAttemptsToUnresponsiveTls++;
+
+ // This is the limitation from DnsTlsFrontend. DnsTlsFrontend can't operate
+ // concurrently. As soon as there's another connection request,
+ // DnsTlsFrontend resets the unique_fd to the new connection.
+ EXPECT_TRUE(WaitForPrivateDnsValidation(serverAddr, false));
+ }
} else {
// Must be unusable_addr.
// In opportunistic mode, when a validation for a private DNS server fails, the
@@ -4650,7 +4845,7 @@
EXPECT_TRUE(WaitForPrivateDnsValidation(unusable_addr, false));
}
- EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), 1);
+ EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), validationAttemptsToUnresponsiveTls);
TlsNameLastTime = config.tlsName;
}
@@ -4696,17 +4891,18 @@
{{addr2}, "", false, false},
{{addr1}, "", false, true},
{{addr2}, "", false, true},
- {{addr1}, kDefaultPrivateDnsHostName, false, true},
- {{addr2}, kDefaultPrivateDnsHostName, false, true},
+
+ // expectNothingHappenWhenServerUnresponsive is false in the two cases because of the
+ // limitation from DnsTlsFrontend which can't operate concurrently.
+ {{addr1}, kDefaultPrivateDnsHostName, false, false},
+ {{addr2}, kDefaultPrivateDnsHostName, false, false},
{{addr1}, kDefaultPrivateDnsHostName, true, true},
{{addr2}, kDefaultPrivateDnsHostName, true, true},
- // There's no new validation to start because there are already two validation threads
- // running (one is for addr1, the other is for addr2). This is because the comparator
- // doesn't compare DnsTlsServer.name. Keep the design as-is until it's known to be
- // harmful.
- {{addr1}, "", true, true},
- {{addr2}, "", true, true},
+ // expectNothingHappenWhenServerUnresponsive is true in the two cases because of the
+ // limitation from DnsTlsFrontend which can't operate concurrently.
+ {{addr1}, "", true, false},
+ {{addr2}, "", true, false},
{{addr1}, "", true, true},
{{addr2}, "", true, true},
};
@@ -4755,6 +4951,8 @@
// It's possible that the resolver hasn't yet started to
// connect. Wait a while.
std::this_thread::sleep_for(100ms);
+ } else {
+ EXPECT_TRUE(WaitForPrivateDnsValidation(config.tlsServer, false));
}
const auto condition = [&]() {
return tls.acceptConnectionsCount() == connectCountsBefore + expectCountDiff;
@@ -5096,7 +5294,7 @@
neverRespondDns.setResponseProbability(0.0);
StartDns(neverRespondDns, records);
ScopedSystemProperties scopedSystemProperties(
- "persist.device_config.netd_native.parallel_lookup", "1");
+ "persist.device_config.netd_native.parallel_lookup_release", "1");
// The default value of parallel_lookup_sleep_time should be very small
// that we can ignore in this test case.
// Re-setup test network to make experiment flag take effect.
@@ -5131,7 +5329,7 @@
test::DNSResponder dns(listen_addr);
StartDns(dns, records);
ScopedSystemProperties scopedSystemProperties1(
- "persist.device_config.netd_native.parallel_lookup", "1");
+ "persist.device_config.netd_native.parallel_lookup_release", "1");
constexpr int PARALLEL_LOOKUP_SLEEP_TIME_MS = 500;
ScopedSystemProperties scopedSystemProperties2(
"persist.device_config.netd_native.parallel_lookup_sleep_time",
@@ -5165,10 +5363,6 @@
}
TEST_F(ResolverTest, BlockDnsQueryUidDoesNotLeadToBadServer) {
- // This test relies on blocking traffic on loopback, which xt_qtaguid does not do.
- // See aosp/358413 and b/34444781 for why.
- SKIP_IF_BPF_NOT_SUPPORTED;
-
constexpr char listen_addr1[] = "127.0.0.4";
constexpr char listen_addr2[] = "::1";
test::DNSResponder dns1(listen_addr1);
@@ -5206,6 +5400,138 @@
EXPECT_EQ(dns2.queries().size(), 0U);
}
+TEST_F(ResolverTest, DnsServerSelection) {
+ test::DNSResponder dns1("127.0.0.3");
+ test::DNSResponder dns2("127.0.0.4");
+ test::DNSResponder dns3("127.0.0.5");
+
+ dns1.setResponseDelayMs(10);
+ dns2.setResponseDelayMs(25);
+ dns3.setResponseDelayMs(50);
+ StartDns(dns1, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
+ StartDns(dns2, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
+ StartDns(dns3, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
+
+ ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, "1");
+
+ // NOTE: the servers must be sorted alphabetically.
+ std::vector<std::string> serverList = {
+ dns1.listen_address(),
+ dns2.listen_address(),
+ dns3.listen_address(),
+ };
+
+ do {
+ SCOPED_TRACE(fmt::format("testConfig: [{}]", fmt::join(serverList, ", ")));
+ const int queryNum = 50;
+ int64_t accumulatedTime = 0;
+
+ // Restart the testing network to 1) make the flag take effect and 2) reset the statistics.
+ resetNetwork();
+
+ // DnsServerSelection doesn't apply to private DNS.
+ ResolverParamsParcel setupParams = DnsResponderClient::GetDefaultResolverParamsParcel();
+ setupParams.servers = serverList;
+ setupParams.tlsServers.clear();
+ ASSERT_TRUE(mDnsClient.SetResolversFromParcel(setupParams));
+
+ // DNSResponder doesn't handle queries concurrently, so don't allow more than
+ // one in-flight query.
+ for (int i = 0; i < queryNum; i++) {
+ Stopwatch s;
+ int fd = resNetworkQuery(TEST_NETID, kHelloExampleCom, ns_c_in, ns_t_a,
+ ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ expectAnswersValid(fd, AF_INET, kHelloExampleComAddrV4);
+ accumulatedTime += s.timeTakenUs();
+ }
+
+ const int dns1Count = dns1.queries().size();
+ const int dns2Count = dns2.queries().size();
+ const int dns3Count = dns3.queries().size();
+
+ // All of the servers have ever been selected. In addition, the less latency server
+ // is selected more frequently.
+ EXPECT_GT(dns1Count, 0);
+ EXPECT_GT(dns2Count, 0);
+ EXPECT_GT(dns3Count, 0);
+ EXPECT_GT(dns1Count, dns2Count);
+ EXPECT_GT(dns2Count, dns3Count);
+
+ const int averageTime = accumulatedTime / queryNum;
+ LOG(INFO) << "ResolverTest#DnsServerSelection: averageTime " << averageTime << "us";
+
+ // Since the average Time might differ depending on parameters, set [10ms, 20ms] as
+ // acceptable range.
+ EXPECT_GE(averageTime, 10000);
+ EXPECT_LE(averageTime, 20000);
+
+ dns1.clearQueries();
+ dns2.clearQueries();
+ dns3.clearQueries();
+ } while (std::next_permutation(serverList.begin(), serverList.end()));
+}
+
+TEST_F(ResolverTest, MultipleDotQueriesInOnePacket) {
+ constexpr char hostname1[] = "query1.example.com.";
+ constexpr char hostname2[] = "query2.example.com.";
+ const std::vector<DnsRecord> records = {
+ {hostname1, ns_type::ns_t_a, "1.2.3.4"},
+ {hostname2, ns_type::ns_t_a, "1.2.3.5"},
+ };
+
+ const std::string addr = getUniqueIPv4Address();
+ test::DNSResponder dns(addr);
+ StartDns(dns, records);
+ test::DnsTlsFrontend tls(addr, "853", addr, "53");
+ ASSERT_TRUE(tls.startServer());
+
+ // Set up resolver to strict mode.
+ auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+ parcel.servers = {addr};
+ parcel.tlsServers = {addr};
+ parcel.tlsName = kDefaultPrivateDnsHostName;
+ parcel.caCertificate = kCaCert;
+ ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+ EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
+ EXPECT_TRUE(tls.waitForQueries(1));
+ tls.clearQueries();
+ dns.clearQueries();
+
+ const auto queryAndCheck = [&](const std::string& hostname,
+ const std::vector<DnsRecord>& records) {
+ SCOPED_TRACE(hostname);
+
+ const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+ auto [result, timeTakenMs] = safe_getaddrinfo_time_taken(hostname.c_str(), nullptr, hints);
+
+ std::vector<std::string> expectedAnswers;
+ for (const auto& r : records) {
+ if (r.host_name == hostname) expectedAnswers.push_back(r.addr);
+ }
+
+ EXPECT_LE(timeTakenMs, 200);
+ ASSERT_NE(result, nullptr);
+ EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray(expectedAnswers));
+ };
+
+ // Set tls to reply DNS responses in one TCP packet and not to close the connection from its
+ // side.
+ tls.setDelayQueries(2);
+ tls.setDelayQueriesTimeout(500);
+ tls.setPassiveClose(true);
+
+ // Start sending DNS requests at the same time.
+ std::array<std::thread, 2> threads;
+ threads[0] = std::thread(queryAndCheck, hostname1, records);
+ threads[1] = std::thread(queryAndCheck, hostname2, records);
+
+ threads[0].join();
+ threads[1].join();
+
+ // Also check no additional queries due to DoT reconnection.
+ EXPECT_EQ(tls.queries(), 2);
+}
+
// ResolverMultinetworkTest is used to verify multinetwork functionality. Here's how it works:
// The resolver sends queries to address A, and then there will be a TunForwarder helping forward
// the packets to address B, which is the address on which the testing server is listening. The
@@ -5666,7 +5992,6 @@
TEST_F(ResolverMultinetworkTest, DnsWithVpn) {
SKIP_IF_REMOTE_VERSION_LESS_THAN(mDnsClient.resolvService(), 4);
- SKIP_IF_BPF_NOT_SUPPORTED;
constexpr char host_name[] = "ohayou.example.com.";
constexpr char ipv4_addr[] = "192.0.2.0";
constexpr char ipv6_addr[] = "2001:db8:cafe:d00d::31";
diff --git a/util.cpp b/util.cpp
index b8df74c..458f3c6 100644
--- a/util.cpp
+++ b/util.cpp
@@ -17,6 +17,7 @@
#include "util.h"
+#include <android-base/format.h>
#include <android-base/parseint.h>
#include <server_configurable_flags/get_flags.h>
@@ -45,3 +46,13 @@
ParseInt(GetServerConfigurableFlag("netd_native", flagName, ""), &val);
return val;
}
+
+std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ const auto time_sec = std::chrono::system_clock::to_time_t(ts);
+ char buf[32];
+ std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
+ int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
+ return fmt::format("{}.{:03d}", buf, ms);
+}
diff --git a/util.h b/util.h
index 38f09b8..bb4a598 100644
--- a/util.h
+++ b/util.h
@@ -17,6 +17,7 @@
#pragma once
+#include <chrono>
#include <string>
#include <netinet/in.h>
@@ -30,6 +31,9 @@
// TODO: Migrate it to DnsResolverExperiments.cpp
int getExperimentFlagInt(const std::string& flagName, int defaultValue);
+// Convert time_point to readable string format "hr:min:sec.ms".
+std::string timestampToString(const std::chrono::system_clock::time_point& ts);
+
// When sdk X release branch is created, aosp's sdk version would still be X-1,
// internal would be X. Also there might be some different setting between real devices and
// CF. Below is the example for the sdk related properties in later R development stage. (internal
@@ -47,3 +51,8 @@
android::base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
return std::max(buildVersionSdk + !!buildVersionPreviewSdk, firstApiLevel);
}
+
+// It's the identical strategy as frameworks/base/core/java/android/os/Build.java did.
+inline bool isUserDebugBuild() {
+ return (android::base::GetProperty("ro.build.type", "user") == "userdebug");
+}