Merge PrivateDnsConfiguration and qhook into libnetd_resolv
PrivateDnsConfiguration handles the operations which adds, validates,
and removes private DNS servers. It will be hidden inside the library
after this change.
PrivateDnsConfiguration and DnsTlsDispatcher will be constructed along
with netd starts. Their instances are now moved to the library. Netd
can use public functions to update private DNS servers. In addition,
qhook() is no longer needed for TLS query.
This change comprises:
[1] Provide APIs for netd to add and delete private DNS servers as
well as get status.
[2] Provide a way for netd to register callback which will be invoked
whenever private DNS servers validation finishes. This is used for
onPrivateDnsValidationEvent().
[3] Remove qhook in android_net_context, since DnsTls* have been moved
to libnetd_resolv library. Also, qhook and rhook are removed in the
library.
[4] The visibility of DnsTls* symbols are hidden, while they have been
visible for a while.
Bug: 113628807
Test: as follows
- built, flashed, booted
- system/netd/tests/runtests.sh
- DNS-over-TLS in live network passed
Change-Id: I235004e4019d88d0d162d7ebd452148cd14cfd39
diff --git a/Android.bp b/Android.bp
index 252264d..5e88ab8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,16 +22,17 @@
"DnsTlsServer.cpp",
"DnsTlsSessionCache.cpp",
"DnsTlsSocket.cpp",
+ "PrivateDnsConfiguration.cpp",
],
// Link everything statically (except for libc) to minimize our dependence
// on system ABIs
stl: "libc++_static",
static_libs: [
"libbase",
+ "libcrypto",
"liblog",
"libnetdutils",
"libssl",
- "libcrypto",
],
export_include_dirs: ["include"],
// TODO: pie in the sky: make this code clang-tidy clean
@@ -48,17 +49,28 @@
cc_test {
name: "libnetd_resolv_test",
defaults: ["netd_defaults"],
+ // Add DnsTls* files since they are not visible outside libnetd_resolv library.
srcs: [
+ "DnsTlsDispatcher.cpp",
+ "DnsTlsQueryMap.cpp",
+ "DnsTlsTransport.cpp",
+ "DnsTlsServer.cpp",
+ "DnsTlsSessionCache.cpp",
+ "DnsTlsSocket.cpp",
"dns_tls_test.cpp",
],
+ // TODO: Remove the include path "system/netd/include" after Fwmark used in DnsTlsTransport is
+ // moved out.
include_dirs: [
+ "system/netd/include",
+ "system/netd/resolv/include",
"system/netd/server",
],
shared_libs: [
"libbase",
+ "libcrypto",
"liblog",
"libnetdutils",
- "libnetd_resolv",
"libssl",
],
}
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
new file mode 100644
index 0000000..b854a38
--- /dev/null
+++ b/PrivateDnsConfiguration.cpp
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PrivateDnsConfiguration"
+#define DBG 0
+
+#include <log/log.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include "netd_resolv/DnsTlsTransport.h"
+#include "netd_resolv/PrivateDnsConfiguration.h"
+#include "netdutils/BackoffSequence.h"
+
+int resolv_set_private_dns_for_net(unsigned netid, uint32_t mark, const char** servers,
+ const unsigned numServers, const char* tlsName,
+ const uint8_t** fingerprints, const unsigned numFingerprint) {
+ std::vector<std::string> tlsServers;
+ for (unsigned i = 0; i < numServers; i++) {
+ tlsServers.push_back(std::string(servers[i]));
+ }
+
+ std::set<std::vector<uint8_t>> tlsFingerprints;
+ for (unsigned i = 0; i < numFingerprint; i++) {
+ // Each fingerprint stored are 32(SHA256_SIZE) bytes long.
+ tlsFingerprints.emplace(std::vector<uint8_t>(fingerprints[i], fingerprints[i] + 32));
+ }
+
+ return android::net::gPrivateDnsConfiguration.set(netid, mark, tlsServers, std::string(tlsName),
+ tlsFingerprints);
+}
+
+void resolv_delete_private_dns_for_net(unsigned netid) {
+ android::net::gPrivateDnsConfiguration.clear(netid);
+}
+
+void resolv_get_private_dns_status_for_net(unsigned netid, ExternalPrivateDnsStatus* status) {
+ android::net::gPrivateDnsConfiguration.getStatus(netid, status);
+}
+
+void resolv_register_private_dns_callback(private_dns_validated_callback callback) {
+ android::net::gPrivateDnsConfiguration.setCallback(callback);
+}
+
+namespace android {
+
+using android::netdutils::BackoffSequence;
+
+namespace net {
+
+std::string addrToString(const sockaddr_storage* addr) {
+ char out[INET6_ADDRSTRLEN] = {0};
+ getnameinfo((const sockaddr*) addr, sizeof(sockaddr_storage), out, INET6_ADDRSTRLEN, nullptr, 0,
+ NI_NUMERICHOST);
+ return std::string(out);
+}
+
+bool parseServer(const char* server, sockaddr_storage* parsed) {
+ addrinfo hints = {.ai_family = AF_UNSPEC, .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV};
+ addrinfo* res;
+
+ int err = getaddrinfo(server, "853", &hints, &res);
+ if (err != 0) {
+ ALOGW("Failed to parse server address (%s): %s", server, gai_strerror(err));
+ return false;
+ }
+
+ memcpy(parsed, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ return true;
+}
+
+void PrivateDnsConfiguration::setCallback(private_dns_validated_callback callback) {
+ if (mCallback == nullptr) {
+ mCallback = callback;
+ }
+}
+
+int PrivateDnsConfiguration::set(int32_t netId, uint32_t mark,
+ const std::vector<std::string>& servers, const std::string& name,
+ const std::set<std::vector<uint8_t>>& fingerprints) {
+ if (DBG) {
+ ALOGD("PrivateDnsConfiguration::set(%u, %zu, %s, %zu)", netId, servers.size(), name.c_str(),
+ fingerprints.size());
+ }
+
+ const bool explicitlyConfigured = !name.empty() || !fingerprints.empty();
+
+ // Parse the list of servers that has been passed in
+ std::set<DnsTlsServer> tlsServers;
+ for (size_t i = 0; i < servers.size(); ++i) {
+ sockaddr_storage parsed;
+ if (!parseServer(servers[i].c_str(), &parsed)) {
+ return -EINVAL;
+ }
+ DnsTlsServer server(parsed);
+ server.name = name;
+ server.fingerprints = fingerprints;
+ tlsServers.insert(server);
+ }
+
+ std::lock_guard guard(mPrivateDnsLock);
+ if (explicitlyConfigured) {
+ mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
+ } else if (!tlsServers.empty()) {
+ mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
+ } else {
+ mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
+ mPrivateDnsTransports.erase(netId);
+ return 0;
+ }
+
+ // Create the tracker if it was not present
+ auto netPair = mPrivateDnsTransports.find(netId);
+ if (netPair == mPrivateDnsTransports.end()) {
+ // No TLS tracker yet for this netId.
+ bool added;
+ std::tie(netPair, added) = mPrivateDnsTransports.emplace(netId, PrivateDnsTracker());
+ if (!added) {
+ ALOGE("Memory error while recording private DNS for netId %d", netId);
+ return -ENOMEM;
+ }
+ }
+ auto& tracker = netPair->second;
+
+ // Remove any servers from the tracker that are not in |servers| exactly.
+ for (auto it = tracker.begin(); it != tracker.end();) {
+ if (tlsServers.count(it->first) == 0) {
+ it = tracker.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // Add any new or changed servers to the tracker, and initiate async checks for them.
+ for (const auto& server : tlsServers) {
+ if (needsValidation(tracker, server)) {
+ validatePrivateDnsProvider(server, tracker, netId, mark);
+ }
+ }
+ return 0;
+}
+
+PrivateDnsStatus PrivateDnsConfiguration::getStatus(unsigned netId) {
+ PrivateDnsStatus status{PrivateDnsMode::OFF, {}};
+
+ // This mutex is on the critical path of every DNS lookup.
+ //
+ // If the overhead of mutex acquisition proves too high, we could reduce
+ // it by maintaining an atomic_int32_t counter of TLS-enabled netids, or
+ // by using an RWLock.
+ std::lock_guard guard(mPrivateDnsLock);
+
+ const auto mode = mPrivateDnsModes.find(netId);
+ if (mode == mPrivateDnsModes.end()) return status;
+ status.mode = mode->second;
+
+ const auto netPair = mPrivateDnsTransports.find(netId);
+ if (netPair != mPrivateDnsTransports.end()) {
+ for (const auto& serverPair : netPair->second) {
+ if (serverPair.second == Validation::success) {
+ status.validatedServers.push_back(serverPair.first);
+ }
+ }
+ }
+
+ return status;
+}
+
+void PrivateDnsConfiguration::getStatus(unsigned netId, ExternalPrivateDnsStatus* status) {
+ // This mutex is on the critical path of every DNS lookup.
+ //
+ // If the overhead of mutex acquisition proves too high, we could reduce
+ // it by maintaining an atomic_int32_t counter of TLS-enabled netids, or
+ // by using an RWLock.
+ std::lock_guard guard(mPrivateDnsLock);
+
+ const auto mode = mPrivateDnsModes.find(netId);
+ if (mode == mPrivateDnsModes.end()) return;
+ status->mode = mode->second;
+
+ const auto netPair = mPrivateDnsTransports.find(netId);
+ if (netPair != mPrivateDnsTransports.end()) {
+ status->numServers = netPair->second.size();
+ int count = 0;
+ for (const auto& serverPair : netPair->second) {
+ status->serverStatus[count].ss = serverPair.first.ss;
+ status->serverStatus[count].hostname =
+ serverPair.first.name.empty() ? "" : serverPair.first.name.c_str();
+ status->serverStatus[count].validation = serverPair.second;
+ /*
+ unsigned numFingerprint = 0;
+ for (const auto& fp : serverPair.first.fingerprints) {
+ std::copy(
+ fp.begin(), fp.end(),
+ status->serverStatus[count].fingerprints.fingerprint[numFingerprint].data);
+ numFingerprint++;
+ }
+ status->serverStatus[count].fingerprints.num = numFingerprint;
+ */
+ count++;
+ }
+ }
+}
+
+void PrivateDnsConfiguration::clear(unsigned netId) {
+ if (DBG) {
+ ALOGD("PrivateDnsConfiguration::clear(%u)", netId);
+ }
+ std::lock_guard guard(mPrivateDnsLock);
+ mPrivateDnsModes.erase(netId);
+ mPrivateDnsTransports.erase(netId);
+}
+
+void PrivateDnsConfiguration::validatePrivateDnsProvider(const DnsTlsServer& server,
+ PrivateDnsTracker& tracker, unsigned netId,
+ uint32_t mark) REQUIRES(mPrivateDnsLock) {
+ if (DBG) {
+ ALOGD("validatePrivateDnsProvider(%s, %u)", addrToString(&(server.ss)).c_str(), netId);
+ }
+
+ tracker[server] = Validation::in_process;
+ if (DBG) {
+ ALOGD("Server %s marked as in_process. Tracker now has size %zu",
+ addrToString(&(server.ss)).c_str(), tracker.size());
+ }
+ // Note that capturing |server| and |netId| in this lambda create copies.
+ std::thread validate_thread([this, server, netId, mark] {
+ // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
+ //
+ // Start with a 1 minute delay and backoff to once per hour.
+ //
+ // Assumptions:
+ // [1] Each TLS validation is ~10KB of certs+handshake+payload.
+ // [2] Network typically provision clients with <=4 nameservers.
+ // [3] Average month has 30 days.
+ //
+ // Each validation pass in a given hour is ~1.2MB of data. And 24
+ // such validation passes per day is about ~30MB per month, in the
+ // worst case. Otherwise, this will cost ~600 SYNs per month
+ // (6 SYNs per ip, 4 ips per validation pass, 24 passes per day).
+ auto backoff = BackoffSequence<>::Builder()
+ .withInitialRetransmissionTime(std::chrono::seconds(60))
+ .withMaximumRetransmissionTime(std::chrono::seconds(3600))
+ .build();
+
+ while (true) {
+ // ::validate() is a blocking call that performs network operations.
+ // It can take milliseconds to minutes, up to the SYN retry limit.
+ const bool success = DnsTlsTransport::validate(server, netId, mark);
+ if (DBG) {
+ ALOGD("validateDnsTlsServer returned %d for %s", success,
+ addrToString(&(server.ss)).c_str());
+ }
+
+ const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
+ if (!needs_reeval) {
+ break;
+ }
+
+ if (backoff.hasNextTimeout()) {
+ std::this_thread::sleep_for(backoff.getNextTimeout());
+ } else {
+ break;
+ }
+ }
+ });
+ validate_thread.detach();
+}
+
+bool PrivateDnsConfiguration::recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId,
+ bool success) {
+ constexpr bool NEEDS_REEVALUATION = true;
+ constexpr bool DONT_REEVALUATE = false;
+
+ std::lock_guard guard(mPrivateDnsLock);
+
+ auto netPair = mPrivateDnsTransports.find(netId);
+ if (netPair == mPrivateDnsTransports.end()) {
+ ALOGW("netId %u was erased during private DNS validation", netId);
+ return DONT_REEVALUATE;
+ }
+
+ const auto mode = mPrivateDnsModes.find(netId);
+ if (mode == mPrivateDnsModes.end()) {
+ ALOGW("netId %u has no private DNS validation mode", netId);
+ return DONT_REEVALUATE;
+ }
+ const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
+
+ bool reevaluationStatus =
+ (success || !modeDoesReevaluation) ? DONT_REEVALUATE : NEEDS_REEVALUATION;
+
+ auto& tracker = netPair->second;
+ auto serverPair = tracker.find(server);
+ if (serverPair == tracker.end()) {
+ ALOGW("Server %s was removed during private DNS validation",
+ addrToString(&(server.ss)).c_str());
+ success = false;
+ reevaluationStatus = DONT_REEVALUATE;
+ } else if (!(serverPair->first == server)) {
+ // TODO: It doesn't seem correct to overwrite the tracker entry for
+ // |server| down below in this circumstance... Fix this.
+ ALOGW("Server %s was changed during private DNS validation",
+ addrToString(&(server.ss)).c_str());
+ success = false;
+ reevaluationStatus = DONT_REEVALUATE;
+ }
+
+ // Invoke the callback to send a validation event to NetdEventListenerService.
+ if (mCallback != nullptr) {
+ const char* ipLiteral = addrToString(&(server.ss)).c_str();
+ const char* hostname = server.name.empty() ? "" : server.name.c_str();
+ mCallback(netId, ipLiteral, hostname, success);
+ }
+
+ if (success) {
+ tracker[server] = Validation::success;
+ if (DBG) {
+ ALOGD("Validation succeeded for %s! Tracker now has %zu entries.",
+ addrToString(&(server.ss)).c_str(), tracker.size());
+ }
+ } else {
+ // Validation failure is expected if a user is on a captive portal.
+ // TODO: Trigger a second validation attempt after captive portal login
+ // succeeds.
+ tracker[server] = (reevaluationStatus == NEEDS_REEVALUATION) ? Validation::in_process
+ : Validation::fail;
+ if (DBG) {
+ ALOGD("Validation failed for %s!", addrToString(&(server.ss)).c_str());
+ }
+ }
+
+ return reevaluationStatus;
+}
+
+// Start validation for newly added servers as well as any servers that have
+// landed in Validation::fail state. Note that servers that have failed
+// multiple validation attempts but for which there is still a validating
+// thread running are marked as being Validation::in_process.
+bool PrivateDnsConfiguration::needsValidation(const PrivateDnsTracker& tracker,
+ const DnsTlsServer& server) {
+ const auto& iter = tracker.find(server);
+ return (iter == tracker.end()) || (iter->second == Validation::fail);
+}
+
+PrivateDnsConfiguration gPrivateDnsConfiguration;
+
+} // namespace net
+} // namespace android
diff --git a/include/netd_resolv/DnsTlsDispatcher.h b/include/netd_resolv/DnsTlsDispatcher.h
index 0bb19f2..45142b8 100644
--- a/include/netd_resolv/DnsTlsDispatcher.h
+++ b/include/netd_resolv/DnsTlsDispatcher.h
@@ -38,8 +38,8 @@
// 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 LIBNETD_RESOLV_TLS_EXPORT DnsTlsDispatcher {
-public:
+class DnsTlsDispatcher {
+ public:
// Default constructor.
DnsTlsDispatcher();
diff --git a/include/netd_resolv/DnsTlsQueryMap.h b/include/netd_resolv/DnsTlsQueryMap.h
index 4c8010c..f013e01 100644
--- a/include/netd_resolv/DnsTlsQueryMap.h
+++ b/include/netd_resolv/DnsTlsQueryMap.h
@@ -35,8 +35,8 @@
// Keeps track of queries and responses. This class matches responses with queries.
// All methods are thread-safe and non-blocking.
-class LIBNETD_RESOLV_TLS_EXPORT DnsTlsQueryMap {
-public:
+class DnsTlsQueryMap {
+ public:
struct Query {
// The new ID number assigned to this query.
uint16_t newId;
diff --git a/include/netd_resolv/DnsTlsServer.h b/include/netd_resolv/DnsTlsServer.h
index 752dc5f..bb161f2 100644
--- a/include/netd_resolv/DnsTlsServer.h
+++ b/include/netd_resolv/DnsTlsServer.h
@@ -30,7 +30,7 @@
// DnsTlsServer represents a recursive resolver that supports, or may support, a
// secure protocol.
-struct LIBNETD_RESOLV_TLS_EXPORT DnsTlsServer {
+struct DnsTlsServer {
// Default constructor.
DnsTlsServer() {}
@@ -68,7 +68,7 @@
};
// This comparison only checks the IP address. It ignores ports, names, and fingerprints.
-struct LIBNETD_RESOLV_TLS_EXPORT AddressComparator {
+struct AddressComparator {
bool operator() (const DnsTlsServer& x, const DnsTlsServer& y) const;
};
diff --git a/include/netd_resolv/DnsTlsSocket.h b/include/netd_resolv/DnsTlsSocket.h
index 7190dd2..195705b 100644
--- a/include/netd_resolv/DnsTlsSocket.h
+++ b/include/netd_resolv/DnsTlsSocket.h
@@ -46,8 +46,8 @@
// 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).
-class LIBNETD_RESOLV_TLS_EXPORT DnsTlsSocket : public IDnsTlsSocket {
-public:
+class DnsTlsSocket : public IDnsTlsSocket {
+ public:
DnsTlsSocket(const DnsTlsServer& server, unsigned mark,
IDnsTlsSocketObserver* _Nonnull observer,
DnsTlsSessionCache* _Nonnull cache) :
diff --git a/include/netd_resolv/DnsTlsTransport.h b/include/netd_resolv/DnsTlsTransport.h
index 011be61..4590868 100644
--- a/include/netd_resolv/DnsTlsTransport.h
+++ b/include/netd_resolv/DnsTlsTransport.h
@@ -41,8 +41,8 @@
// Manages at most one DnsTlsSocket at a time. This class handles socket lifetime issues,
// such as reopening the socket and reissuing pending queries.
-class LIBNETD_RESOLV_TLS_EXPORT DnsTlsTransport : public IDnsTlsSocketObserver {
-public:
+class DnsTlsTransport : public IDnsTlsSocketObserver {
+ public:
DnsTlsTransport(const DnsTlsServer& server, unsigned mark,
IDnsTlsSocketFactory* _Nonnull factory) :
mMark(mark), mServer(server), mFactory(factory) {}
diff --git a/include/netd_resolv/PrivateDnsConfiguration.h b/include/netd_resolv/PrivateDnsConfiguration.h
new file mode 100644
index 0000000..cee0205
--- /dev/null
+++ b/include/netd_resolv/PrivateDnsConfiguration.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETD_RESOLV_PRIVATEDNSCONFIGURATION_H
+#define NETD_RESOLV_PRIVATEDNSCONFIGURATION_H
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+#include "DnsTlsServer.h"
+#include "resolv.h"
+
+namespace android {
+namespace net {
+
+struct PrivateDnsStatus {
+ PrivateDnsMode mode;
+ std::list<DnsTlsServer> validatedServers;
+};
+
+class PrivateDnsConfiguration {
+ public:
+ int set(int32_t netId, uint32_t mark, const std::vector<std::string>& servers,
+ const std::string& name, const std::set<std::vector<uint8_t>>& fingerprints);
+
+ PrivateDnsStatus getStatus(unsigned netId);
+
+ // Externally used for netd.
+ void getStatus(unsigned netId, ExternalPrivateDnsStatus* status);
+
+ int getPrivateDnsModeAndStatus(unsigned netId);
+ void clear(unsigned netId);
+ void setCallback(private_dns_validated_callback callback);
+
+ private:
+ typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
+
+ void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
+ unsigned netId, uint32_t mark) REQUIRES(mPrivateDnsLock);
+
+ bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success);
+
+ // Start validation for newly added servers as well as any servers that have
+ // landed in Validation::fail state. Note that servers that have failed
+ // multiple validation attempts but for which there is still a validating
+ // thread running are marked as being Validation::in_process.
+ bool needsValidation(const PrivateDnsTracker& tracker, const DnsTlsServer& server);
+
+ std::mutex mPrivateDnsLock;
+ std::map<unsigned, PrivateDnsMode> mPrivateDnsModes GUARDED_BY(mPrivateDnsLock);
+ // Structure for tracking the validation status of servers on a specific netId.
+ // Using the AddressComparator ensures at most one entry per IP address.
+ std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
+ private_dns_validated_callback mCallback = nullptr;
+};
+
+extern PrivateDnsConfiguration gPrivateDnsConfiguration;
+
+} // namespace net
+} // namespace android
+
+#endif /* NETD_RESOLV_PRIVATEDNSCONFIGURATION_H */
diff --git a/include/netd_resolv/params.h b/include/netd_resolv/params.h
index c3f3363..939bfc4 100644
--- a/include/netd_resolv/params.h
+++ b/include/netd_resolv/params.h
@@ -36,17 +36,12 @@
int base_timeout_msec; // base query retry timeout (if 0, use RES_TIMEOUT)
};
-typedef enum { res_goahead, res_nextns, res_modified, res_done, res_error } res_sendhookact;
+// The DNS over TLS mode on a specific netId.
+enum class PrivateDnsMode : uint8_t { OFF, OPPORTUNISTIC, STRICT };
-typedef res_sendhookact (*res_send_qhook)(struct sockaddr* const*, const uint8_t**, int*, uint8_t*,
- int, int*);
-
-typedef res_sendhookact (*res_send_rhook)(const struct sockaddr*, const uint8_t*, int, uint8_t*,
- int, int*);
+// 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 };
#define LIBNETD_RESOLV_PUBLIC extern "C" [[gnu::visibility("default")]]
-// TODO: Remove it after we move PrivateDnsConfiguration and qhook() into libnetd_resolv.
-#define LIBNETD_RESOLV_TLS_EXPORT [[gnu::visibility("default")]]
-
#endif // NETD_RESOLV_PARAMS_H
diff --git a/include/netd_resolv/resolv.h b/include/netd_resolv/resolv.h
index 2c33c71..9176996 100644
--- a/include/netd_resolv/resolv.h
+++ b/include/netd_resolv/resolv.h
@@ -75,7 +75,6 @@
unsigned dns_mark;
uid_t uid;
unsigned flags;
- res_send_qhook qhook;
};
#define NET_CONTEXT_INVALID_UID ((uid_t) -1)
@@ -83,6 +82,19 @@
#define NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS 0x00000001
#define NET_CONTEXT_FLAG_USE_EDNS 0x00000002
+struct ExternalPrivateDnsStatus {
+ PrivateDnsMode mode;
+ unsigned numServers;
+ struct PrivateDnsInfo {
+ sockaddr_storage ss;
+ const char* hostname;
+ Validation validation;
+ } serverStatus[MAXNS];
+};
+
+typedef void (*private_dns_validated_callback)(unsigned netid, const char* server,
+ const char* hostname, bool success);
+
LIBNETD_RESOLV_PUBLIC hostent* android_gethostbyaddrfornetcontext(const void*, socklen_t, int,
const android_net_context*);
LIBNETD_RESOLV_PUBLIC int android_gethostbynamefornetcontext(const char*, int,
@@ -96,6 +108,22 @@
unsigned numservers, const char* domains,
const __res_params* params);
+LIBNETD_RESOLV_PUBLIC int resolv_set_private_dns_for_net(unsigned netid, uint32_t mark,
+ const char** servers,
+ const unsigned numServers,
+ const char* tlsName,
+ const uint8_t** fingerprints,
+ const unsigned numFingerprints);
+
+LIBNETD_RESOLV_PUBLIC void resolv_delete_private_dns_for_net(unsigned netid);
+
+LIBNETD_RESOLV_PUBLIC void resolv_get_private_dns_status_for_net(unsigned netid,
+ ExternalPrivateDnsStatus* status);
+
+// Register callback to listen whether private DNS validated
+LIBNETD_RESOLV_PUBLIC void resolv_register_private_dns_callback(
+ private_dns_validated_callback callback);
+
// Flush the cache associated with a certain network
LIBNETD_RESOLV_PUBLIC void resolv_flush_cache_for_net(unsigned netid);
diff --git a/libnetd_resolv.map.txt b/libnetd_resolv.map.txt
index bb1e408..e139ab7 100644
--- a/libnetd_resolv.map.txt
+++ b/libnetd_resolv.map.txt
@@ -28,53 +28,10 @@
android_net_res_stats_get_usable_servers;
resolv_delete_cache_for_net;
resolv_set_nameservers_for_net;
-
- # These symbol names are generated by 'nm -D libnetd_resolv.so' with reverting the commit
- # (aosp/801454). They are temporarily necessary for netd to access DnsTls* classes before
- # we move qhook() and PrivateDnsConfiguration to libnetd_resolv library.
- # TODO: Remove them after we hide DnsTls* stuff thus netd no longer needs to access them.
- _ZN7android3net12DnsTlsSocket10initializeEv;
- _ZN7android3net12DnsTlsSocket10sslConnectEi;
- _ZN7android3net12DnsTlsSocket10tcpConnectEv;
- _ZN7android3net12DnsTlsSocket12readResponseEv;
- _ZN7android3net12DnsTlsSocket13sslDisconnectEv;
- _ZN7android3net12DnsTlsSocket4loopEv;
- _ZN7android3net12DnsTlsSocket5queryEtNS_9netdutils5SliceE;
- _ZN7android3net12DnsTlsSocket7sslReadENS_9netdutils5SliceEb;
- _ZN7android3net12DnsTlsSocket8sslWriteENS_9netdutils5SliceE;
- _ZN7android3net12DnsTlsSocket9sendQueryERKNS1_5QueryE;
- _ZN7android3net12DnsTlsSocketD0Ev;
- _ZN7android3net12DnsTlsSocketD1Ev;
- _ZN7android3net12DnsTlsSocketD2Ev;
- _ZN7android3net14DnsTlsQueryMap10onResponseENSt3__16vectorIhNS2_9allocatorIhEEEE;
- _ZN7android3net14DnsTlsQueryMap11recordQueryENS_9netdutils5SliceE;
- _ZN7android3net14DnsTlsQueryMap5clearEv;
- _ZN7android3net14DnsTlsQueryMap5emptyEv;
- _ZN7android3net14DnsTlsQueryMap6expireEPNS1_12QueryPromiseE;
- _ZN7android3net14DnsTlsQueryMap6getAllEv;
- _ZN7android3net14DnsTlsQueryMap7cleanupEv;
- _ZN7android3net14DnsTlsQueryMap9getFreeIdEv;
- _ZN7android3net14DnsTlsQueryMap9markTriedEt;
- _ZN7android3net15DnsTlsTransport10onResponseENSt3__16vectorIhNS2_9allocatorIhEEEE;
- _ZN7android3net15DnsTlsTransport11doReconnectEv;
- _ZN7android3net15DnsTlsTransport5queryENS_9netdutils5SliceE;
- _ZN7android3net15DnsTlsTransport8onClosedEv;
- _ZN7android3net15DnsTlsTransport8validateERKNS0_12DnsTlsServerEjj;
- _ZN7android3net15DnsTlsTransport9doConnectEv;
- _ZN7android3net15DnsTlsTransport9sendQueryENS0_14DnsTlsQueryMap5QueryE;
- _ZN7android3net15DnsTlsTransportD0Ev;
- _ZN7android3net15DnsTlsTransportD1Ev;
- _ZN7android3net15DnsTlsTransportD2Ev;
- _ZN7android3net16DnsTlsDispatcher5queryERKNS0_12DnsTlsServerEjNS_9netdutils5SliceES6_Pi;
- _ZN7android3net16DnsTlsDispatcher5queryERKNSt3__14listINS0_12DnsTlsServerENS2_9allocatorIS4_EEEEjNS_9netdutils5SliceESB_Pi;
- _ZN7android3net16DnsTlsDispatcherC1Ev;
- _ZN7android3net16DnsTlsDispatcherC2Ev;
- _ZNK7android3net12DnsTlsServer23wasExplicitlyConfiguredEv;
- _ZNK7android3net12DnsTlsServereqERKS1_;
- _ZNK7android3net12DnsTlsServerltERKS1_;
- _ZNK7android3net16DnsTlsDispatcher20getOrderedServerListERKNSt3__14listINS0_12DnsTlsServerENS2_9allocatorIS4_EEEEj;
- _ZNK7android3net17AddressComparatorclERKNS0_12DnsTlsServerES4_;
- _ZTVN7android3net15DnsTlsTransportE;
+ resolv_set_private_dns_for_net;
+ resolv_delete_private_dns_for_net;
+ resolv_get_private_dns_status_for_net;
+ resolv_register_private_dns_callback;
local:
*;
};
diff --git a/res_init.cpp b/res_init.cpp
index cfb6f08..06d4452 100644
--- a/res_init.cpp
+++ b/res_init.cpp
@@ -158,10 +158,9 @@
statp->pfcode = 0;
statp->_vcsock = -1;
statp->_flags = 0;
- statp->qhook = NULL;
- statp->rhook = NULL;
statp->_u._ext.nscount = 0;
statp->_u._ext.ext = (res_state_ext*) malloc(sizeof(*statp->_u._ext.ext));
+ statp->use_local_nameserver = false;
if (statp->_u._ext.ext != NULL) {
memset(statp->_u._ext.ext, 0, sizeof(*statp->_u._ext.ext));
statp->_u._ext.ext->nsaddrs[0].sin = statp->nsaddr;
@@ -419,9 +418,11 @@
if (statp != NULL) {
statp->netid = netcontext->dns_netid;
statp->_mark = netcontext->dns_mark;
- statp->qhook = netcontext->qhook;
if (netcontext->flags & NET_CONTEXT_FLAG_USE_EDNS) {
statp->options |= RES_USE_EDNS0 | RES_USE_DNSSEC;
}
+ if (netcontext->flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
+ statp->use_local_nameserver = true;
+ }
}
}
diff --git a/res_send.cpp b/res_send.cpp
index 5a79f28..4c0ca1a 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -100,6 +100,10 @@
#include <android-base/logging.h>
+#include <netdutils/Slice.h>
+#include "netd_resolv/DnsTlsDispatcher.h"
+#include "netd_resolv/DnsTlsTransport.h"
+#include "netd_resolv/PrivateDnsConfiguration.h"
#include "netd_resolv/resolv.h"
#include "netd_resolv/stats.h"
#include "private/android_filesystem_config.h"
@@ -107,6 +111,8 @@
#include "resolv_cache.h"
#include "resolv_private.h"
+// TODO: use the namespace something like android::netd_resolv for libnetd_resolv
+using namespace android::net;
#define VLOG if (!kVerboseLogging) {} else LOG(INFO)
@@ -134,6 +140,10 @@
}
#endif // DEBUG
+typedef enum { res_goahead, res_nextns, res_modified, res_done, res_error } res_sendhookact;
+
+static DnsTlsDispatcher sDnsTlsDispatcher;
+
static int get_salen(const struct sockaddr*);
static struct sockaddr* get_nsaddr(res_state, size_t);
static int send_vc(res_state, struct __res_params* params, const u_char*, int, u_char*, int, int*,
@@ -146,6 +156,7 @@
static int connect_with_timeout(int sock, const struct sockaddr* nsap, socklen_t salen,
const struct timespec timeout);
static int retrying_poll(const int sock, short events, const struct timespec* finish);
+static res_sendhookact res_tls_send(res_state, const Slice query, const Slice answer, int* resplen);
/* BIONIC-BEGIN: implement source port randomization */
@@ -514,37 +525,40 @@
statp->_flags |= (ns << RES_F_LASTSHIFT);
same_ns:
- if (statp->qhook) {
- int done = 0, loops = 0;
+ int done = 0, loops = 0;
- do {
- res_sendhookact act;
+ do {
+ res_sendhookact act;
- act = (*statp->qhook)(&nsap, &buf, &buflen, ans, anssiz, &resplen);
- switch (act) {
- case res_goahead:
- done = 1;
+ // TODO: This function tries to query on all of private DNS servers. Putting
+ // it here is strange since we should expect there is only one DNS server
+ // being queried. Consider moving it to other reasonable place. In addition,
+ // enum res_sendhookact can be removed.
+ act = res_tls_send(statp, Slice(const_cast<u_char*>(buf), buflen),
+ Slice(ans, anssiz), &resplen);
+ switch (act) {
+ case res_goahead:
+ done = 1;
+ break;
+ case res_nextns:
+ res_nclose(statp);
+ goto next_ns;
+ case res_done:
+ if (cache_status == RESOLV_CACHE_NOTFOUND) {
+ _resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
+ }
+ return (resplen);
+ case res_modified:
+ /* give the hook another try */
+ if (++loops < 42) /*doug adams*/
break;
- case res_nextns:
- res_nclose(statp);
- goto next_ns;
- case res_done:
- if (cache_status == RESOLV_CACHE_NOTFOUND) {
- _resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
- }
- return (resplen);
- case res_modified:
- /* give the hook another try */
- if (++loops < 42) /*doug adams*/
- break;
- [[fallthrough]];
- case res_error:
- [[fallthrough]];
- default:
- goto fail;
- }
- } while (!done);
- }
+ [[fallthrough]];
+ case res_error:
+ [[fallthrough]];
+ default:
+ goto fail;
+ }
+ } while (!done);
[[maybe_unused]] static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
[[maybe_unused]] char abuf[NI_MAXHOST];
@@ -620,33 +634,6 @@
(statp->options & RES_STAYOPEN) == 0U) {
res_nclose(statp);
}
- if (statp->rhook) {
- int done = 0, loops = 0;
-
- do {
- res_sendhookact act;
-
- act = (*statp->rhook)(nsap, buf, buflen, ans, anssiz, &resplen);
- switch (act) {
- case res_goahead:
- case res_done:
- done = 1;
- break;
- case res_nextns:
- res_nclose(statp);
- goto next_ns;
- case res_modified:
- /* give the hook another try */
- if (++loops < 42) /*doug adams*/
- break;
- [[fallthrough]];
- case res_error:
- [[fallthrough]];
- default:
- goto fail;
- }
- } while (!done);
- }
return (resplen);
next_ns:;
} // for each ns
@@ -1249,3 +1236,59 @@
return 0;
}
}
+
+res_sendhookact res_tls_send(res_state statp, const Slice query, const Slice answer, int* resplen) {
+ const unsigned netId = statp->netid;
+ const unsigned mark = statp->_mark;
+
+ // TLS bypass
+ if (statp->use_local_nameserver) {
+ return res_goahead;
+ }
+
+ const auto privateDnsStatus = gPrivateDnsConfiguration.getStatus(netId);
+
+ if (privateDnsStatus.mode == PrivateDnsMode::OFF) return res_goahead;
+
+ if (privateDnsStatus.validatedServers.empty()) {
+ if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
+ return res_goahead;
+ } else {
+ // Sleep and iterate some small number of times checking for the
+ // arrival of resolved and validated server IP addresses, instead
+ // of returning an immediate error.
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(100ms);
+ return res_modified;
+ }
+ }
+
+ VLOG << __func__ << ": performing query over TLS";
+
+ const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, mark, query,
+ answer, resplen);
+ if (response == DnsTlsTransport::Response::success) {
+ VLOG << __func__ << ": success";
+ return res_done;
+ }
+
+ VLOG << __func__ << ": abort: TLS query failed: " << static_cast<int>(response);
+
+ if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
+ // In opportunistic mode, handle falling back to cleartext in some
+ // cases (DNS shouldn't fail if a validated opportunistic mode server
+ // becomes unreachable for some reason).
+ switch (response) {
+ case DnsTlsTransport::Response::network_error:
+ case DnsTlsTransport::Response::internal_error:
+ // Note: this will cause cleartext queries to be emitted, with
+ // all of the EDNS0 goodness enabled. Fingers crossed. :-/
+ return res_goahead;
+ default:
+ break;
+ }
+ }
+
+ // There was an internal error. Fail hard.
+ return res_error;
+}
diff --git a/resolv_private.h b/resolv_private.h
index b6f86de..879eec7 100644
--- a/resolv_private.h
+++ b/resolv_private.h
@@ -126,8 +126,6 @@
struct in_addr addr;
uint32_t mask;
} sort_list[MAXRESOLVSORT];
- res_send_qhook qhook; /* query hook */
- res_send_rhook rhook; /* response hook */
int res_h_errno; /* last one set for this context */
unsigned _mark; /* If non-0 SET_MARK to _mark on all request sockets */
int _vcsock; /* PRIVATE: for res_send VC i/o */
@@ -144,6 +142,7 @@
} _ext;
} _u;
struct res_static rstatic[1];
+ bool use_local_nameserver; /* DNS-over-TLS bypass */
};
typedef struct __res_state* res_state;