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.net.module.util.NetworkStackConstants; 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 NetworkStackConstants.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