Merge libnetddns into libnetd_resolv
libnetddns is the library for DNS-over-TLS and is statically
linked to netd. Deprecate it and move them to libnetd_resolv
as a more general DNS library for netd.
This change comprises:
[1] Clean up netd/server/dns/*. Move all DnsTls* files to
netd/resolv/ to parts of libnetd_resolv library.
[2] Export DnsTls* classes being visible for netd. It will only
be temporary for a while.
[3] Remove the libssl dependency in netd. The relevant stuff is
moved to libnetd_resolv.
Note that DnsTls* classes are still required for DnsProxyListener
and ResolverController to manipulate private DNS servers even after
this change.
Bug: 113628807
Test: as follows
- built, flashed, booted
- system/netd/tests/runtests.sh
- DNS-over-TLS in live network passed
Change-Id: Ieac5889b4ebe737f876b3dcbe1a8da2b2b1b629d
diff --git a/resolv/DnsTlsTransport.cpp b/resolv/DnsTlsTransport.cpp
new file mode 100644
index 0000000..b4294e2
--- /dev/null
+++ b/resolv/DnsTlsTransport.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 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 "DnsTlsTransport"
+//#define LOG_NDEBUG 0
+
+#include "netd_resolv/DnsTlsTransport.h"
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include "netd_resolv/DnsTlsSocketFactory.h"
+#include "netd_resolv/IDnsTlsSocketFactory.h"
+
+#include "log/log.h"
+#include "Fwmark.h"
+#include "Permission.h"
+
+namespace android {
+namespace net {
+
+std::future<DnsTlsTransport::Result> DnsTlsTransport::query(const netdutils::Slice query) {
+ std::lock_guard guard(mLock);
+
+ auto record = mQueries.recordQuery(query);
+ if (!record) {
+ return std::async(std::launch::deferred, []{
+ return (Result) { .code = Response::internal_error };
+ });
+ }
+
+ if (!mSocket) {
+ ALOGV("No socket for query. Opening socket and sending.");
+ doConnect();
+ } else {
+ sendQuery(record->query);
+ }
+
+ return std::move(record->result);
+}
+
+bool DnsTlsTransport::sendQuery(const DnsTlsQueryMap::Query q) {
+ // Strip off the ID number and send the new ID instead.
+ bool sent = mSocket->query(q.newId, netdutils::drop(q.query, 2));
+ if (sent) {
+ mQueries.markTried(q.newId);
+ }
+ return sent;
+}
+
+void DnsTlsTransport::doConnect() {
+ ALOGV("Constructing new socket");
+ mSocket = mFactory->createDnsTlsSocket(mServer, mMark, this, &mCache);
+
+ if (mSocket) {
+ auto queries = mQueries.getAll();
+ ALOGV("Initialization succeeded. Reissuing %zu queries.", queries.size());
+ for(auto& q : queries) {
+ if (!sendQuery(q)) {
+ break;
+ }
+ }
+ } else {
+ ALOGV("Initialization failed.");
+ mSocket.reset();
+ ALOGV("Failing all pending queries.");
+ mQueries.clear();
+ }
+}
+
+void DnsTlsTransport::onResponse(std::vector<uint8_t> response) {
+ mQueries.onResponse(std::move(response));
+}
+
+void DnsTlsTransport::onClosed() {
+ std::lock_guard guard(mLock);
+ if (mClosing) {
+ return;
+ }
+ // Move remaining operations to a new thread.
+ // This is necessary because
+ // 1. onClosed is currently running on a thread that blocks mSocket's destructor
+ // 2. doReconnect will call that destructor
+ if (mReconnectThread) {
+ // Complete cleanup of a previous reconnect thread, if present.
+ mReconnectThread->join();
+ // Joining a thread that is trying to acquire mLock, while holding mLock,
+ // looks like it risks a deadlock. However, a deadlock will not occur because
+ // once onClosed is called, it cannot be called again until after doReconnect
+ // acquires mLock.
+ }
+ mReconnectThread.reset(new std::thread(&DnsTlsTransport::doReconnect, this));
+}
+
+void DnsTlsTransport::doReconnect() {
+ std::lock_guard guard(mLock);
+ if (mClosing) {
+ return;
+ }
+ mQueries.cleanup();
+ if (!mQueries.empty()) {
+ ALOGV("Fast reconnect to retry remaining queries");
+ doConnect();
+ } else {
+ ALOGV("No pending queries. Going idle.");
+ mSocket.reset();
+ }
+}
+
+DnsTlsTransport::~DnsTlsTransport() {
+ ALOGV("Destructor");
+ {
+ std::lock_guard guard(mLock);
+ ALOGV("Locked destruction procedure");
+ mQueries.clear();
+ mClosing = true;
+ }
+ // It's possible that a reconnect thread was spawned and waiting for mLock.
+ // It's safe for that thread to run now because mClosing is true (and mQueries is empty),
+ // but we need to wait for it to finish before allowing destruction to proceed.
+ if (mReconnectThread) {
+ ALOGV("Waiting for reconnect thread to terminate");
+ mReconnectThread->join();
+ mReconnectThread.reset();
+ }
+ // Ensure that the socket is destroyed, and can clean up its callback threads,
+ // before any of this object's fields become invalid.
+ mSocket.reset();
+ ALOGV("Destructor completed");
+}
+
+// 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) {
+ ALOGV("Beginning validation on %u", netid);
+ // 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[] =
+ "abcdefhijklmnopqrstuvwxyz"
+ "ABCDEFHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ const auto c = [](uint8_t rnd) -> uint8_t {
+ return kDnsSafeChars[(rnd % std::size(kDnsSafeChars))];
+ };
+ uint8_t rnd[8];
+ arc4random_buf(rnd, std::size(rnd));
+ // We could try to use res_mkquery() here, but it's basically the same.
+ uint8_t query[] = {
+ rnd[6], rnd[7], // [0-1] query ID
+ 1, 0, // [2-3] flags; query[2] = 1 for recursion desired (RD).
+ 0, 1, // [4-5] QDCOUNT (number of queries)
+ 0, 0, // [6-7] ANCOUNT (number of answers)
+ 0, 0, // [8-9] NSCOUNT (number of name server records)
+ 0, 0, // [10-11] ARCOUNT (number of additional records)
+ 17, c(rnd[0]), c(rnd[1]), c(rnd[2]), c(rnd[3]), c(rnd[4]), c(rnd[5]),
+ '-', 'd', 'n', 's', 'o', 't', 'l', 's', '-', 'd', 's',
+ 6, 'm', 'e', 't', 'r', 'i', 'c',
+ 7, 'g', 's', 't', 'a', 't', 'i', 'c',
+ 3, 'c', 'o', 'm',
+ 0, // null terminator of FQDN (root TLD)
+ 0, ns_t_aaaa, // QTYPE
+ 0, ns_c_in // QCLASS
+ };
+ const int qlen = std::size(query);
+
+ // At validation time, we only know the netId, so we have to guess/compute the
+ // corresponding socket mark.
+ Fwmark fwmark;
+ fwmark.permission = PERMISSION_SYSTEM;
+ fwmark.explicitlySelected = true;
+ fwmark.protectedFromVpn = true;
+ fwmark.netId = netid;
+ unsigned mark = fwmark.intValue;
+ int replylen = 0;
+ DnsTlsSocketFactory factory;
+ DnsTlsTransport transport(server, mark, &factory);
+ auto r = transport.query(Slice(query, qlen)).get();
+ if (r.code != Response::success) {
+ ALOGV("query failed");
+ return false;
+ }
+
+ const std::vector<uint8_t>& recvbuf = r.response;
+ if (recvbuf.size() < NS_HFIXEDSZ) {
+ ALOGW("short response: %d", replylen);
+ return false;
+ }
+
+ const int qdcount = (recvbuf[4] << 8) | recvbuf[5];
+ if (qdcount != 1) {
+ ALOGW("reply query count != 1: %d", qdcount);
+ return false;
+ }
+
+ const int ancount = (recvbuf[6] << 8) | recvbuf[7];
+ ALOGV("%u answer count: %d", netid, ancount);
+
+ // TODO: Further validate the response contents (check for valid AAAA record, ...).
+ // Note that currently, integration tests rely on this function accepting a
+ // response with zero records.
+#if 0
+ for (int i = 0; i < resplen; i++) {
+ ALOGD("recvbuf[%d] = %d %c", i, recvbuf[i], recvbuf[i]);
+ }
+#endif
+ return true;
+}
+
+} // end of namespace net
+} // end of namespace android