/* * 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 "DnsTlsTransport.h" #include #include #include "DnsTlsSocketFactory.h" #include "IDnsTlsSocketFactory.h" #include "log/log.h" namespace android { namespace net { std::future 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 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, uint32_t mark) { ALOGV("Beginning validation on %u", netid); // Generate "-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); int replylen = 0; DnsTlsSocketFactory factory; DnsTlsTransport transport(server, mark, &factory); auto r = transport.query(netdutils::Slice(query, qlen)).get(); if (r.code != Response::success) { ALOGV("query failed"); return false; } const std::vector& 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