1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.networkstack.util;
18 
19 import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
20 import static android.net.DnsResolver.TYPE_A;
21 import static android.net.DnsResolver.TYPE_AAAA;
22 
23 import android.net.DnsResolver;
24 import android.net.Network;
25 import android.net.TrafficStats;
26 import android.net.util.Stopwatch;
27 import android.util.Log;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 
32 import com.android.internal.util.TrafficStatsConstants;
33 import com.android.server.connectivity.NetworkMonitor.DnsLogFunc;
34 
35 import java.net.InetAddress;
36 import java.net.UnknownHostException;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.concurrent.CompletableFuture;
41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException;
44 
45 /**
46  * Collection of utilities for dns query.
47  */
48 public class DnsUtils {
49     // Decide what queries to make depending on what IP addresses are on the system.
50     public static final int TYPE_ADDRCONFIG = -1;
51     // A one time host name suffix of private dns probe.
52     // q.v. system/netd/server/dns/DnsTlsTransport.cpp
53     public static final String PRIVATE_DNS_PROBE_HOST_SUFFIX = "-dnsotls-ds.metric.gstatic.com";
54     private static final String TAG = DnsUtils.class.getSimpleName();
55     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
56 
57     /**
58      * Return both A and AAAA query results regardless the ip address type of the giving network.
59      * Used for probing in NetworkMonitor.
60      */
61     @NonNull
getAllByName(@onNull final DnsResolver dnsResolver, @NonNull final Network network, @NonNull String host, int timeout, @NonNull final DnsLogFunc logger)62     public static InetAddress[] getAllByName(@NonNull final DnsResolver dnsResolver,
63             @NonNull final Network network, @NonNull String host, int timeout,
64             @NonNull final DnsLogFunc logger) throws UnknownHostException {
65         final List<InetAddress> result = new ArrayList<InetAddress>();
66         final StringBuilder errorMsg = new StringBuilder(host);
67 
68         try {
69             result.addAll(Arrays.asList(
70                     getAllByName(dnsResolver, network, host, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
71                     timeout, logger)));
72         } catch (UnknownHostException e) {
73             // Might happen if the host is v4-only, still need to query TYPE_A
74             errorMsg.append(String.format(" (%s)%s", dnsTypeToStr(TYPE_AAAA), e.getMessage()));
75         }
76         try {
77             result.addAll(Arrays.asList(
78                     getAllByName(dnsResolver, network, host, TYPE_A, FLAG_NO_CACHE_LOOKUP,
79                     timeout, logger)));
80         } catch (UnknownHostException e) {
81             // Might happen if the host is v6-only, still need to return AAAA answers
82             errorMsg.append(String.format(" (%s)%s", dnsTypeToStr(TYPE_A), e.getMessage()));
83         }
84 
85         if (result.size() == 0) {
86             logger.log("FAIL: " + errorMsg.toString());
87             throw new UnknownHostException(host);
88         }
89         logger.log("OK: " + host + " " + result.toString());
90         return result.toArray(new InetAddress[0]);
91     }
92 
93     /**
94      * Return dns query result based on the given QueryType(TYPE_A, TYPE_AAAA) or TYPE_ADDRCONFIG.
95      * Used for probing in NetworkMonitor.
96      */
97     @NonNull
getAllByName(@onNull final DnsResolver dnsResolver, @NonNull final Network network, @NonNull final String host, int type, int flag, int timeoutMs, @Nullable final DnsLogFunc logger)98     public static InetAddress[] getAllByName(@NonNull final DnsResolver dnsResolver,
99             @NonNull final Network network, @NonNull final String host, int type, int flag,
100             int timeoutMs, @Nullable final DnsLogFunc logger) throws UnknownHostException {
101         final CompletableFuture<List<InetAddress>> resultRef = new CompletableFuture<>();
102         final Stopwatch watch = new Stopwatch().start();
103 
104 
105         final DnsResolver.Callback<List<InetAddress>> callback =
106                 new DnsResolver.Callback<List<InetAddress>>()  {
107             @Override
108             public void onAnswer(List<InetAddress> answer, int rcode) {
109                 if (rcode == 0 && answer != null && answer.size() != 0) {
110                     resultRef.complete(answer);
111                 } else {
112                     resultRef.completeExceptionally(new UnknownHostException());
113                 }
114             }
115 
116             @Override
117             public void onError(@NonNull DnsResolver.DnsException e) {
118                 if (DBG) {
119                     Log.d(TAG, "DNS error resolving " + host, e);
120                 }
121                 resultRef.completeExceptionally(e);
122             }
123         };
124         // TODO: Investigate whether this is still useful.
125         // The packets that actually do the DNS queries are sent by netd, but netd doesn't
126         // look at the tag at all. Given that this is a library, the tag should be passed in by the
127         // caller.
128         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
129                 TrafficStatsConstants.TAG_SYSTEM_PROBE);
130 
131         if (type == TYPE_ADDRCONFIG) {
132             dnsResolver.query(network, host, flag, r -> r.run(), null /* cancellationSignal */,
133                     callback);
134         } else {
135             dnsResolver.query(network, host, type, flag, r -> r.run(),
136                     null /* cancellationSignal */, callback);
137         }
138 
139         TrafficStats.setThreadStatsTag(oldTag);
140 
141         String errorMsg = null;
142         List<InetAddress> result = null;
143         try {
144             result = resultRef.get(timeoutMs, TimeUnit.MILLISECONDS);
145         } catch (ExecutionException e) {
146             errorMsg = e.getMessage();
147         } catch (TimeoutException | InterruptedException e) {
148             errorMsg = "Timeout";
149         } finally {
150             logDnsResult(result, watch.stop() / 1000 /* latencyMs */, logger, type, errorMsg);
151         }
152 
153         if (null != errorMsg) throw new UnknownHostException(host);
154 
155         return result.toArray(new InetAddress[0]);
156     }
157 
logDnsResult(@ullable final List<InetAddress> results, final long latencyMs, @Nullable final DnsLogFunc logger, int type, @NonNull final String errorMsg)158     private static void logDnsResult(@Nullable final List<InetAddress> results,
159             final long latencyMs, @Nullable final DnsLogFunc logger, int type,
160             @NonNull final String errorMsg) {
161         if (logger == null) {
162             return;
163         }
164 
165         if (results != null && results.size() != 0) {
166             final StringBuilder builder = new StringBuilder();
167             for (InetAddress address : results) {
168                 builder.append(',').append(address.getHostAddress());
169             }
170             logger.log(String.format("%dms OK %s", latencyMs, builder.substring(1)));
171         } else {
172             logger.log(String.format("%dms FAIL in type %s %s", latencyMs, dnsTypeToStr(type),
173                     errorMsg));
174         }
175     }
176 
dnsTypeToStr(int type)177     private static String dnsTypeToStr(int type) {
178         switch (type) {
179             case TYPE_A:
180                 return "A";
181             case TYPE_AAAA:
182                 return "AAAA";
183             case TYPE_ADDRCONFIG:
184                 return "ADDRCONFIG";
185             default:
186         }
187         return "UNDEFINED";
188     }
189 }
190