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/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