Merge "Remove support for DNS-over-TLS certificate fingerprints."
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index 033465c..f684ff2 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -300,7 +300,14 @@
 }
 
 void initDnsEvent(NetworkDnsEventReported* event) {
-    // The value 0 has the special meaning of unset/unknown in Westworld atoms.
+    // The value 0 has the special meaning of unset/unknown in Westworld atoms. So, we set both
+    // flags to -1 as default value.
+    //  1. The hints flag is only used in resolv_getaddrinfo. When user set it to -1 in
+    //     resolv_getaddrinfo, the flag will cause validation (validateHints) failure in
+    //     getaddrinfo, so it will not do DNS query and will upload DNS stats log with
+    //     return_code = RC_EAI_BADFLAGS.
+    //  2. The res_nsend flags are only used in resolv_res_nsend. When user set it to -1 in
+    //     resolv_res_nsend,res_nsend will do nothing special by the setting.
     event->set_hints_ai_flags(-1);
     event->set_res_nsend_flags(-1);
 }
diff --git a/DnsTlsDispatcher.cpp b/DnsTlsDispatcher.cpp
index d1c4022..58ef2a7 100644
--- a/DnsTlsDispatcher.cpp
+++ b/DnsTlsDispatcher.cpp
@@ -97,11 +97,10 @@
     for (const auto& server : orderedServers) {
         DnsQueryEvent* dnsQueryEvent =
                 statp->event->mutable_dns_query_events()->add_dns_query_event();
-        dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
-        Stopwatch query_stopwatch;
+        Stopwatch queryStopwatch;
         code = this->query(server, statp->_mark, query, ans, resplen);
 
-        dnsQueryEvent->set_latency_micros(saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+        dnsQueryEvent->set_latency_micros(saturate_cast<int32_t>(queryStopwatch.timeTakenUs()));
         dnsQueryEvent->set_dns_server_index(serverCount++);
         dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(server.ss.ss_family));
         dnsQueryEvent->set_protocol(PROTO_DOT);
@@ -113,16 +112,18 @@
             case DnsTlsTransport::Response::success:
                 dnsQueryEvent->set_rcode(
                         static_cast<NsRcode>(reinterpret_cast<HEADER*>(ans.base())->rcode));
-                [[fallthrough]];
+                return code;
             case DnsTlsTransport::Response::limit_error:
+                dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
                 return code;
             // These response codes might differ when trying other servers, so
             // keep iterating to see if we can get a different (better) result.
             case DnsTlsTransport::Response::network_error:
                 // Sync from res_tls_send in res_send.cpp
                 dnsQueryEvent->set_rcode(NS_R_TIMEOUT);
-                [[fallthrough]];
+                continue;
             case DnsTlsTransport::Response::internal_error:
+                dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
                 continue;
             // No "default" statement.
         }
diff --git a/apex/Android.bp b/apex/Android.bp
new file mode 100644
index 0000000..c1d33e1
--- /dev/null
+++ b/apex/Android.bp
@@ -0,0 +1,41 @@
+// 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.
+
+
+apex {
+    name: "com.android.resolv",
+    manifest: "manifest.json",
+    multilib: {
+        first: {
+             native_shared_libs: ["libnetd_resolv"],
+        },
+    },
+    key: "com.android.resolv.key",
+    certificate: ":com.android.resolv.certificate",
+
+    // Use a custom AndroidManifest.xml used for API targeting.
+    androidManifest: "AndroidManifest.xml",
+}
+
+apex_key {
+    name: "com.android.resolv.key",
+    public_key: "com.android.resolv.avbpubkey",
+    private_key: "com.android.resolv.pem",
+}
+
+android_app_certificate {
+     name: "com.android.resolv.certificate",
+     // will use cert.pk8 and cert.x509.pem
+     certificate: "testcert",
+}
diff --git a/apex/AndroidManifest.xml b/apex/AndroidManifest.xml
new file mode 100644
index 0000000..29b9342
--- /dev/null
+++ b/apex/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.android.resolv">
+  <!-- APEX does not have classes.dex -->
+  <application android:hasCode="false" />
+  <!--
+    * The interface between the resolver and netd is backwards compatible, so the resolver APEX
+    * can work with any version of netd.
+    -->
+  <uses-sdk
+      android:minSdkVersion="28"
+      android:targetSdkVersion="28"
+  />
+</manifest>
diff --git a/apex/OWNERS b/apex/OWNERS
new file mode 100644
index 0000000..bc97a1b
--- /dev/null
+++ b/apex/OWNERS
@@ -0,0 +1,4 @@
+chenbruce@google.com
+codewiz@google.com
+martinwu@google.com
+
diff --git a/apex/com.android.resolv.avbpubkey b/apex/com.android.resolv.avbpubkey
new file mode 100644
index 0000000..e0af34c
--- /dev/null
+++ b/apex/com.android.resolv.avbpubkey
Binary files differ
diff --git a/apex/com.android.resolv.pem b/apex/com.android.resolv.pem
new file mode 100644
index 0000000..6eb688d
--- /dev/null
+++ b/apex/com.android.resolv.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/apex/manifest.json b/apex/manifest.json
new file mode 100644
index 0000000..c770b0a
--- /dev/null
+++ b/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.resolv",
+  "version": 1
+}
diff --git a/apex/testcert.pk8 b/apex/testcert.pk8
new file mode 100644
index 0000000..adfc589
--- /dev/null
+++ b/apex/testcert.pk8
Binary files differ
diff --git a/apex/testcert.x509.pem b/apex/testcert.x509.pem
new file mode 100644
index 0000000..cba30d6
--- /dev/null
+++ b/apex/testcert.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGADCCA+igAwIBAgIJAJiRMVvanGUaMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xODEwMzAxMjEzNTFaFw00NjAzMTcxMjEzNTFaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAL3ghKA8Gz9qOORY8gMY4wlB2tCJLDUO2tFG
+LVK1UphtQMp+YEcz/0VQKVV7de7z6V4EMQ5P1HbxHOsjcKn/zXAl4YgFt7b5kZbC
+bpNK4CYHEfho3j6fpYtq5d9q8rIA2kI0uZkkqPy1zXKTl2C2PjOoAnLQRk5xBVQG
+M10/wYsf7yX36mSWoJJwKPp/EzVFpA+hX8HpljeIiZ6CFzKwJdqv9zO/xzfp6NsX
+Tv5EGdkDxmw3qQqKgyl8dLMTZ/2zNfvVOMeZDusEPDF7A/lbU1byLWrKQdCzVb40
+yc7BCSRGYwM29R/byOcgD+lslwKSGzgzNmQXICt1tXz9bSJR8qh4tlAaiRc3ZKBe
+hJWIFGkGtD/cDGtDE5DbNAOz6CdSDdE2XN0Qf0cfN1RHVE6fo2FtFicRRVuFBt8M
+2cbQ7bzmEvtHD6W6dsf120FH7gppXKmnhMx1WazpxR2QltbiYDTy2ZZi4paS/jDB
+fL9gMCWp3Ohg2y74NGfUw5CQWQsDpcki6I7RvwClBCyOV51LHn5LE/nY4DkVrZxk
+Pw0/YrTWz5J5PbdMetTuIunE4ec4lm8nZnh1ET+2MHx2+RoyF5vBs4rp1KHHRaEA
+veD2AfQOWxz7kOG9+akFot7n+QoWEGdwY0mJ9jsO/IITCjv3VbD7o0OoJv1R2AW5
+sK2KQ4PDAgMBAAGjUzBRMB0GA1UdDgQWBBT2EbrayXGhY6VCvSlLtRNyjW9ceDAf
+BgNVHSMEGDAWgBT2EbrayXGhY6VCvSlLtRNyjW9ceDAPBgNVHRMBAf8EBTADAQH/
+MA0GCSqGSIb3DQEBCwUAA4ICAQC7SsWap9zDKmuR0qMUZ6wlualnag0hUG1jZHQP
+t63KO6LmNNMSuXRX60Zcq6WWzgLOyoT4HqHZZ47Jamfb4XQQcnWMMW0tJ3pDtTkz
+dZILBInHJO8QPYI8Du6XWsDLSvMajq6ueBtO3NdcgsNL7eiHf3WoOtajLZxFM94Z
+MESkUQOIsqHolYeTMHLTsuGkX1CK2Zw3Xn18bUSTYwZCHa6mYH00ItUBfetGCnWh
+Y7bth/R15Cc+hocSB7ZsOa/R5kDyDdFDIKrnV5nH5Yd7CryrYC6Ac5UarYrxSJTq
+eKPwqUlJB/tJW/lvdLt8YaURbFGzf/ZqU12zZRafYjmMjcQvfpzMoDSnbvHTA9IR
+ZGO7dwhwykoSaL4/8LWde49xQUq6F2pQBRmEr+7mTzml1MaM5cWEk5emkCMXgLog
+k+c56CAk1EdM1teWik7wR0TIqkkYyYJHTSg61GkXUIXrZJ6iYx2ejDg1+QTPm9rU
+Yr7nP52gVkQuUAX1+xB6wKLSDizQJw8SNiUGXl5+2vwV6+0BI3/CXlQ8I/nRPBC1
+oqOIkRSbE+IF7DP9QvYuNG/3bZZQ8LUVeHxqI5Mq8K2VIJZd95AIwPNMH34SaDGz
+9xjG28Fq4ZkuDP0pCsHM9d2XEwK5PEVS18WW5fJ/QcJKMno4IPTB70ZBBjVzv6Y+
+MYjOrw==
+-----END CERTIFICATE-----
diff --git a/res_send.cpp b/res_send.cpp
index 17f71ed..85e8a58 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -416,9 +416,9 @@
     terrno = ETIMEDOUT;
 
     int anslen = 0;
-    Stopwatch cache_stopwatch;
+    Stopwatch cacheStopwatch;
     cache_status = resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen, flags);
-    const int32_t cacheLatencyUs = saturate_cast<int32_t>(cache_stopwatch.timeTakenUs());
+    const int32_t cacheLatencyUs = saturate_cast<int32_t>(cacheStopwatch.timeTakenUs());
     if (cache_status == RESOLV_CACHE_FOUND) {
         HEADER* hp = (HEADER*)(void*)ans;
         *rcode = hp->rcode;
@@ -560,7 +560,7 @@
                 LOG(DEBUG) << __func__ << ": Querying server (# " << ns + 1
                            << ") address = " << abuf;
 
-            Stopwatch query_stopwatch;
+            Stopwatch queryStopwatch;
             if (v_circuit) {
                 /* Use VC; at most one attempt per server. */
                 bool shouldRecordStats = (attempt == 0);
@@ -570,7 +570,7 @@
                             &delay);
 
                 dnsQueryEvent->set_latency_micros(
-                        saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+                        saturate_cast<int32_t>(queryStopwatch.timeTakenUs()));
                 dnsQueryEvent->set_dns_server_index(ns);
                 dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(nsap->sa_family));
                 dnsQueryEvent->set_retry_times(attempt);
@@ -607,7 +607,7 @@
                             &gotsomewhere, &now, rcode, &delay);
 
                 dnsQueryEvent->set_latency_micros(
-                        saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+                        saturate_cast<int32_t>(queryStopwatch.timeTakenUs()));
                 dnsQueryEvent->set_dns_server_index(ns);
                 dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(nsap->sa_family));
                 dnsQueryEvent->set_retry_times(attempt);
diff --git a/tests/dns_responder/dns_responder.cpp b/tests/dns_responder/dns_responder.cpp
index caa0072..51eb7dd 100644
--- a/tests/dns_responder/dns_responder.cpp
+++ b/tests/dns_responder/dns_responder.cpp
@@ -459,11 +459,11 @@
     std::lock_guard lock(mappings_mutex_);
     auto it = mappings_.find(QueryKey(name, type));
     if (it != mappings_.end()) {
-        LOG(ERROR) << "Cannot remove mapping mapping from (" << name << ", " << dnstype2str(type)
-                   << "), not present";
+        mappings_.erase(it);
         return;
     }
-    mappings_.erase(it);
+    LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
+               << "), not present";
 }
 
 void DNSResponder::setResponseProbability(double response_probability) {
@@ -667,24 +667,9 @@
         }
     }
 
-    for (const DNSQuestion& question : header.questions) {
-        if (question.qclass != ns_class::ns_c_in && question.qclass != ns_class::ns_c_any) {
-            LOG(INFO) << "unsupported question class " << question.qclass;
-            return makeErrorResponse(&header, ns_rcode::ns_r_notimpl, response, response_len);
-        }
-
-        if (!addAnswerRecords(question, &header.answers)) {
-            return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response, response_len);
-        }
-    }
-
-    header.qr = true;
-    char* response_cur = header.write(response, response + *response_len);
-    if (response_cur == nullptr) {
-        return false;
-    }
-    *response_len = response_cur - response;
-    return true;
+    // Make the response. The query has been read into |header| which is used to build and return
+    // the response as well.
+    return makeResponse(&header, response, response_len);
 }
 
 bool DNSResponder::addAnswerRecords(const DNSQuestion& question,
@@ -796,6 +781,27 @@
     return true;
 }
 
+bool DNSResponder::makeResponse(DNSHeader* header, char* response, size_t* response_len) const {
+    for (const DNSQuestion& question : header->questions) {
+        if (question.qclass != ns_class::ns_c_in && question.qclass != ns_class::ns_c_any) {
+            LOG(INFO) << "unsupported question class " << question.qclass;
+            return makeErrorResponse(header, ns_rcode::ns_r_notimpl, response, response_len);
+        }
+
+        if (!addAnswerRecords(question, &header->answers)) {
+            return makeErrorResponse(header, ns_rcode::ns_r_servfail, response, response_len);
+        }
+    }
+
+    header->qr = true;
+    char* response_cur = header->write(response, response + *response_len);
+    if (response_cur == nullptr) {
+        return false;
+    }
+    *response_len = response_cur - response;
+    return true;
+}
+
 void DNSResponder::setDeferredResp(bool deferred_resp) {
     std::lock_guard<std::mutex> guard(cv_mutex_for_deferred_resp_);
     deferred_resp_ = deferred_resp;
diff --git a/tests/dns_responder/dns_responder.h b/tests/dns_responder/dns_responder.h
index 2ef8da0..41e7312 100644
--- a/tests/dns_responder/dns_responder.h
+++ b/tests/dns_responder/dns_responder.h
@@ -116,6 +116,13 @@
  */
 class DNSResponder {
   public:
+    enum class Edns : uint8_t {
+        ON,
+        FORMERR_ON_EDNS,  // DNS server not supporting EDNS will reply FORMERR.
+        FORMERR_UNCOND,   // DNS server reply FORMERR unconditionally
+        DROP              // DNS server not supporting EDNS will not do any response.
+    };
+
     DNSResponder(std::string listen_address = kDefaultListenAddr,
                  std::string listen_service = kDefaultListenService,
                  ns_rcode error_rcode = ns_rcode::ns_r_servfail);
@@ -125,13 +132,6 @@
 
     ~DNSResponder();
 
-    enum class Edns : uint8_t {
-        ON,
-        FORMERR_ON_EDNS,  // DNS server not supporting EDNS will reply FORMERR.
-        FORMERR_UNCOND,   // DNS server reply FORMERR unconditionally
-        DROP              // DNS server not supporting EDNS will not do any response.
-    };
-
     void addMapping(const std::string& name, ns_type type, const std::string& addr);
     void removeMapping(const std::string& name, ns_type type);
     void setResponseProbability(double response_probability);
@@ -184,6 +184,7 @@
                                size_t* response_len) const;
     bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
                            size_t* response_len) const;
+    bool makeResponse(DNSHeader* header, char* response, size_t* response_len) const;
 
     // Add a new file descriptor to be polled by the handler thread.
     bool addFd(int fd, uint32_t events);