• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.server.connectivity;
18 
19 import static android.system.OsConstants.*;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.net.InetAddresses;
24 import android.net.LinkAddress;
25 import android.net.LinkProperties;
26 import android.net.Network;
27 import android.net.RouteInfo;
28 import android.net.TrafficStats;
29 import android.net.shared.PrivateDnsConfig;
30 import android.net.util.NetworkConstants;
31 import android.os.SystemClock;
32 import android.system.ErrnoException;
33 import android.system.Os;
34 import android.system.StructTimeval;
35 import android.text.TextUtils;
36 import android.util.Pair;
37 
38 import com.android.internal.util.IndentingPrintWriter;
39 import com.android.net.module.util.NetworkStackConstants;
40 
41 import libcore.io.IoUtils;
42 
43 import java.io.Closeable;
44 import java.io.DataInputStream;
45 import java.io.DataOutputStream;
46 import java.io.FileDescriptor;
47 import java.io.IOException;
48 import java.io.InterruptedIOException;
49 import java.net.Inet4Address;
50 import java.net.Inet6Address;
51 import java.net.InetAddress;
52 import java.net.InetSocketAddress;
53 import java.net.NetworkInterface;
54 import java.net.SocketAddress;
55 import java.net.SocketException;
56 import java.net.UnknownHostException;
57 import java.nio.ByteBuffer;
58 import java.nio.charset.StandardCharsets;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Random;
65 import java.util.concurrent.CountDownLatch;
66 import java.util.concurrent.TimeUnit;
67 
68 import javax.net.ssl.SNIHostName;
69 import javax.net.ssl.SNIServerName;
70 import javax.net.ssl.SSLParameters;
71 import javax.net.ssl.SSLSocket;
72 import javax.net.ssl.SSLSocketFactory;
73 
74 /**
75  * NetworkDiagnostics
76  *
77  * A simple class to diagnose network connectivity fundamentals.  Current
78  * checks performed are:
79  *     - ICMPv4/v6 echo requests for all routers
80  *     - ICMPv4/v6 echo requests for all DNS servers
81  *     - DNS UDP queries to all DNS servers
82  *
83  * Currently unimplemented checks include:
84  *     - report ARP/ND data about on-link neighbors
85  *     - DNS TCP queries to all DNS servers
86  *     - HTTP DIRECT and PROXY checks
87  *     - port 443 blocking/TLS intercept checks
88  *     - QUIC reachability checks
89  *     - MTU checks
90  *
91  * The supplied timeout bounds the entire diagnostic process.  Each specific
92  * check class must implement this upper bound on measurements in whichever
93  * manner is most appropriate and effective.
94  *
95  * @hide
96  */
97 public class NetworkDiagnostics {
98     private static final String TAG = "NetworkDiagnostics";
99 
100     private static final InetAddress TEST_DNS4 = InetAddresses.parseNumericAddress("8.8.8.8");
101     private static final InetAddress TEST_DNS6 = InetAddresses.parseNumericAddress(
102             "2001:4860:4860::8888");
103 
104     // For brevity elsewhere.
now()105     private static final long now() {
106         return SystemClock.elapsedRealtime();
107     }
108 
109     // Values from RFC 1035 section 4.1.1, names from <arpa/nameser.h>.
110     // Should be a member of DnsUdpCheck, but "compiler says no".
111     public static enum DnsResponseCode { NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED };
112 
113     private final Network mNetwork;
114     private final LinkProperties mLinkProperties;
115     private final PrivateDnsConfig mPrivateDnsCfg;
116     private final Integer mInterfaceIndex;
117 
118     private final long mTimeoutMs;
119     private final long mStartTime;
120     private final long mDeadlineTime;
121 
122     // A counter, initialized to the total number of measurements,
123     // so callers can wait for completion.
124     private final CountDownLatch mCountDownLatch;
125 
126     public class Measurement {
127         private static final String SUCCEEDED = "SUCCEEDED";
128         private static final String FAILED = "FAILED";
129 
130         private boolean succeeded;
131 
132         // Package private.  TODO: investigate better encapsulation.
133         String description = "";
134         long startTime;
135         long finishTime;
136         String result = "";
137         Thread thread;
138 
checkSucceeded()139         public boolean checkSucceeded() { return succeeded; }
140 
recordSuccess(String msg)141         void recordSuccess(String msg) {
142             maybeFixupTimes();
143             succeeded = true;
144             result = SUCCEEDED + ": " + msg;
145             if (mCountDownLatch != null) {
146                 mCountDownLatch.countDown();
147             }
148         }
149 
recordFailure(String msg)150         void recordFailure(String msg) {
151             maybeFixupTimes();
152             succeeded = false;
153             result = FAILED + ": " + msg;
154             if (mCountDownLatch != null) {
155                 mCountDownLatch.countDown();
156             }
157         }
158 
maybeFixupTimes()159         private void maybeFixupTimes() {
160             // Allows the caller to just set success/failure and not worry
161             // about also setting the correct finishing time.
162             if (finishTime == 0) { finishTime = now(); }
163 
164             // In cases where, for example, a failure has occurred before the
165             // measurement even began, fixup the start time to reflect as much.
166             if (startTime == 0) { startTime = finishTime; }
167         }
168 
169         @Override
toString()170         public String toString() {
171             return description + ": " + result + " (" + (finishTime - startTime) + "ms)";
172         }
173     }
174 
175     private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
176     private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks =
177             new HashMap<>();
178     private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
179     private final Map<InetAddress, Measurement> mDnsTlsChecks = new HashMap<>();
180     private final String mDescription;
181 
182 
NetworkDiagnostics(Network network, LinkProperties lp, @NonNull PrivateDnsConfig privateDnsCfg, long timeoutMs)183     public NetworkDiagnostics(Network network, LinkProperties lp,
184             @NonNull PrivateDnsConfig privateDnsCfg, long timeoutMs) {
185         mNetwork = network;
186         mLinkProperties = lp;
187         mPrivateDnsCfg = privateDnsCfg;
188         mInterfaceIndex = getInterfaceIndex(mLinkProperties.getInterfaceName());
189         mTimeoutMs = timeoutMs;
190         mStartTime = now();
191         mDeadlineTime = mStartTime + mTimeoutMs;
192 
193         // Hardcode measurements to TEST_DNS4 and TEST_DNS6 in order to test off-link connectivity.
194         // We are free to modify mLinkProperties with impunity because ConnectivityService passes us
195         // a copy and not the original object. It's easier to do it this way because we don't need
196         // to check whether the LinkProperties already contains these DNS servers because
197         // LinkProperties#addDnsServer checks for duplicates.
198         if (mLinkProperties.isReachable(TEST_DNS4)) {
199             mLinkProperties.addDnsServer(TEST_DNS4);
200         }
201         // TODO: we could use mLinkProperties.isReachable(TEST_DNS6) here, because we won't set any
202         // DNS servers for which isReachable() is false, but since this is diagnostic code, be extra
203         // careful.
204         if (mLinkProperties.hasGlobalIpv6Address() || mLinkProperties.hasIpv6DefaultRoute()) {
205             mLinkProperties.addDnsServer(TEST_DNS6);
206         }
207 
208         for (RouteInfo route : mLinkProperties.getRoutes()) {
209             if (route.hasGateway()) {
210                 InetAddress gateway = route.getGateway();
211                 prepareIcmpMeasurement(gateway);
212                 if (route.isIPv6Default()) {
213                     prepareExplicitSourceIcmpMeasurements(gateway);
214                 }
215             }
216         }
217         for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
218             prepareIcmpMeasurement(nameserver);
219             prepareDnsMeasurement(nameserver);
220 
221             // Unlike the DnsResolver which doesn't do certificate validation in opportunistic mode,
222             // DoT probes to the DNS servers will fail if certificate validation fails.
223             prepareDnsTlsMeasurement(null /* hostname */, nameserver);
224         }
225 
226         for (InetAddress tlsNameserver : mPrivateDnsCfg.ips) {
227             // Reachability check is necessary since when resolving the strict mode hostname,
228             // NetworkMonitor always queries for both A and AAAA records, even if the network
229             // is IPv4-only or IPv6-only.
230             if (mLinkProperties.isReachable(tlsNameserver)) {
231                 // If there are IPs, there must have been a name that resolved to them.
232                 prepareDnsTlsMeasurement(mPrivateDnsCfg.hostname, tlsNameserver);
233             }
234         }
235 
236         mCountDownLatch = new CountDownLatch(totalMeasurementCount());
237 
238         startMeasurements();
239 
240         mDescription = "ifaces{" + TextUtils.join(",", mLinkProperties.getAllInterfaceNames()) + "}"
241                 + " index{" + mInterfaceIndex + "}"
242                 + " network{" + mNetwork + "}"
243                 + " nethandle{" + mNetwork.getNetworkHandle() + "}";
244     }
245 
getInterfaceIndex(String ifname)246     private static Integer getInterfaceIndex(String ifname) {
247         try {
248             NetworkInterface ni = NetworkInterface.getByName(ifname);
249             return ni.getIndex();
250         } catch (NullPointerException | SocketException e) {
251             return null;
252         }
253     }
254 
socketAddressToString(@onNull SocketAddress sockAddr)255     private static String socketAddressToString(@NonNull SocketAddress sockAddr) {
256         // The default toString() implementation is not the prettiest.
257         InetSocketAddress inetSockAddr = (InetSocketAddress) sockAddr;
258         InetAddress localAddr = inetSockAddr.getAddress();
259         return String.format(
260                 (localAddr instanceof Inet6Address ? "[%s]:%d" : "%s:%d"),
261                 localAddr.getHostAddress(), inetSockAddr.getPort());
262     }
263 
prepareIcmpMeasurement(InetAddress target)264     private void prepareIcmpMeasurement(InetAddress target) {
265         if (!mIcmpChecks.containsKey(target)) {
266             Measurement measurement = new Measurement();
267             measurement.thread = new Thread(new IcmpCheck(target, measurement));
268             mIcmpChecks.put(target, measurement);
269         }
270     }
271 
prepareExplicitSourceIcmpMeasurements(InetAddress target)272     private void prepareExplicitSourceIcmpMeasurements(InetAddress target) {
273         for (LinkAddress l : mLinkProperties.getLinkAddresses()) {
274             InetAddress source = l.getAddress();
275             if (source instanceof Inet6Address && l.isGlobalPreferred()) {
276                 Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target);
277                 if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) {
278                     Measurement measurement = new Measurement();
279                     measurement.thread = new Thread(new IcmpCheck(source, target, measurement));
280                     mExplicitSourceIcmpChecks.put(srcTarget, measurement);
281                 }
282             }
283         }
284     }
285 
prepareDnsMeasurement(InetAddress target)286     private void prepareDnsMeasurement(InetAddress target) {
287         if (!mDnsUdpChecks.containsKey(target)) {
288             Measurement measurement = new Measurement();
289             measurement.thread = new Thread(new DnsUdpCheck(target, measurement));
290             mDnsUdpChecks.put(target, measurement);
291         }
292     }
293 
prepareDnsTlsMeasurement(@ullable String hostname, @NonNull InetAddress target)294     private void prepareDnsTlsMeasurement(@Nullable String hostname, @NonNull InetAddress target) {
295         // This might overwrite an existing entry in mDnsTlsChecks, because |target| can be an IP
296         // address configured by the network as well as an IP address learned by resolving the
297         // strict mode DNS hostname. If the entry is overwritten, the overwritten measurement
298         // thread will not execute.
299         Measurement measurement = new Measurement();
300         measurement.thread = new Thread(new DnsTlsCheck(hostname, target, measurement));
301         mDnsTlsChecks.put(target, measurement);
302     }
303 
totalMeasurementCount()304     private int totalMeasurementCount() {
305         return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size()
306                 + mDnsTlsChecks.size();
307     }
308 
startMeasurements()309     private void startMeasurements() {
310         for (Measurement measurement : mIcmpChecks.values()) {
311             measurement.thread.start();
312         }
313         for (Measurement measurement : mExplicitSourceIcmpChecks.values()) {
314             measurement.thread.start();
315         }
316         for (Measurement measurement : mDnsUdpChecks.values()) {
317             measurement.thread.start();
318         }
319         for (Measurement measurement : mDnsTlsChecks.values()) {
320             measurement.thread.start();
321         }
322     }
323 
waitForMeasurements()324     public void waitForMeasurements() {
325         try {
326             mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
327         } catch (InterruptedException ignored) {}
328     }
329 
getMeasurements()330     public List<Measurement> getMeasurements() {
331         // TODO: Consider moving waitForMeasurements() in here to minimize the
332         // chance of caller errors.
333 
334         ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount());
335 
336         // Sort measurements IPv4 first.
337         for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
338             if (entry.getKey() instanceof Inet4Address) {
339                 measurements.add(entry.getValue());
340             }
341         }
342         for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
343                 mExplicitSourceIcmpChecks.entrySet()) {
344             if (entry.getKey().first instanceof Inet4Address) {
345                 measurements.add(entry.getValue());
346             }
347         }
348         for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
349             if (entry.getKey() instanceof Inet4Address) {
350                 measurements.add(entry.getValue());
351             }
352         }
353         for (Map.Entry<InetAddress, Measurement> entry : mDnsTlsChecks.entrySet()) {
354             if (entry.getKey() instanceof Inet4Address) {
355                 measurements.add(entry.getValue());
356             }
357         }
358 
359         // IPv6 measurements second.
360         for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
361             if (entry.getKey() instanceof Inet6Address) {
362                 measurements.add(entry.getValue());
363             }
364         }
365         for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
366                 mExplicitSourceIcmpChecks.entrySet()) {
367             if (entry.getKey().first instanceof Inet6Address) {
368                 measurements.add(entry.getValue());
369             }
370         }
371         for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
372             if (entry.getKey() instanceof Inet6Address) {
373                 measurements.add(entry.getValue());
374             }
375         }
376         for (Map.Entry<InetAddress, Measurement> entry : mDnsTlsChecks.entrySet()) {
377             if (entry.getKey() instanceof Inet6Address) {
378                 measurements.add(entry.getValue());
379             }
380         }
381 
382         return measurements;
383     }
384 
dump(IndentingPrintWriter pw)385     public void dump(IndentingPrintWriter pw) {
386         pw.println(TAG + ":" + mDescription);
387         final long unfinished = mCountDownLatch.getCount();
388         if (unfinished > 0) {
389             // This can't happen unless a caller forgets to call waitForMeasurements()
390             // or a measurement isn't implemented to correctly honor the timeout.
391             pw.println("WARNING: countdown wait incomplete: "
392                     + unfinished + " unfinished measurements");
393         }
394 
395         pw.increaseIndent();
396 
397         String prefix;
398         for (Measurement m : getMeasurements()) {
399             prefix = m.checkSucceeded() ? "." : "F";
400             pw.println(prefix + "  " + m.toString());
401         }
402 
403         pw.decreaseIndent();
404     }
405 
406 
407     private class SimpleSocketCheck implements Closeable {
408         protected final InetAddress mSource;  // Usually null.
409         protected final InetAddress mTarget;
410         protected final int mAddressFamily;
411         protected final Measurement mMeasurement;
412         protected FileDescriptor mFileDescriptor;
413         protected SocketAddress mSocketAddress;
414 
SimpleSocketCheck( InetAddress source, InetAddress target, Measurement measurement)415         protected SimpleSocketCheck(
416                 InetAddress source, InetAddress target, Measurement measurement) {
417             mMeasurement = measurement;
418 
419             if (target instanceof Inet6Address) {
420                 Inet6Address targetWithScopeId = null;
421                 if (target.isLinkLocalAddress() && mInterfaceIndex != null) {
422                     try {
423                         targetWithScopeId = Inet6Address.getByAddress(
424                                 null, target.getAddress(), mInterfaceIndex);
425                     } catch (UnknownHostException e) {
426                         mMeasurement.recordFailure(e.toString());
427                     }
428                 }
429                 mTarget = (targetWithScopeId != null) ? targetWithScopeId : target;
430                 mAddressFamily = AF_INET6;
431             } else {
432                 mTarget = target;
433                 mAddressFamily = AF_INET;
434             }
435 
436             // We don't need to check the scope ID here because we currently only do explicit-source
437             // measurements from global IPv6 addresses.
438             mSource = source;
439         }
440 
SimpleSocketCheck(InetAddress target, Measurement measurement)441         protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
442             this(null, target, measurement);
443         }
444 
setupSocket( int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)445         protected void setupSocket(
446                 int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
447                 throws ErrnoException, IOException {
448             final int oldTag = TrafficStats.getAndSetThreadStatsTag(
449                     NetworkStackConstants.TAG_SYSTEM_PROBE);
450             try {
451                 mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
452             } finally {
453                 // TODO: The tag should remain set until all traffic is sent and received.
454                 // Consider tagging the socket after the measurement thread is started.
455                 TrafficStats.setThreadStatsTag(oldTag);
456             }
457             // Setting SNDTIMEO is purely for defensive purposes.
458             Os.setsockoptTimeval(mFileDescriptor,
459                     SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
460             Os.setsockoptTimeval(mFileDescriptor,
461                     SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout));
462             // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability.
463             mNetwork.bindSocket(mFileDescriptor);
464             if (mSource != null) {
465                 Os.bind(mFileDescriptor, mSource, 0);
466             }
467             Os.connect(mFileDescriptor, mTarget, dstPort);
468             mSocketAddress = Os.getsockname(mFileDescriptor);
469         }
470 
ensureMeasurementNecessary()471         protected boolean ensureMeasurementNecessary() {
472             if (mMeasurement.finishTime == 0) return false;
473 
474             // Countdown latch was not decremented when the measurement failed during setup.
475             mCountDownLatch.countDown();
476             return true;
477         }
478 
479         @Override
close()480         public void close() {
481             IoUtils.closeQuietly(mFileDescriptor);
482         }
483     }
484 
485 
486     private class IcmpCheck extends SimpleSocketCheck implements Runnable {
487         private static final int TIMEOUT_SEND = 100;
488         private static final int TIMEOUT_RECV = 300;
489         private static final int PACKET_BUFSIZE = 512;
490         private final int mProtocol;
491         private final int mIcmpType;
492 
IcmpCheck(InetAddress source, InetAddress target, Measurement measurement)493         public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) {
494             super(source, target, measurement);
495 
496             if (mAddressFamily == AF_INET6) {
497                 mProtocol = IPPROTO_ICMPV6;
498                 mIcmpType = NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
499                 mMeasurement.description = "ICMPv6";
500             } else {
501                 mProtocol = IPPROTO_ICMP;
502                 mIcmpType = NetworkConstants.ICMPV4_ECHO_REQUEST_TYPE;
503                 mMeasurement.description = "ICMPv4";
504             }
505 
506             mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
507         }
508 
IcmpCheck(InetAddress target, Measurement measurement)509         public IcmpCheck(InetAddress target, Measurement measurement) {
510             this(null, target, measurement);
511         }
512 
513         @Override
run()514         public void run() {
515             if (ensureMeasurementNecessary()) return;
516 
517             try {
518                 setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
519             } catch (ErrnoException | IOException e) {
520                 mMeasurement.recordFailure(e.toString());
521                 return;
522             }
523             mMeasurement.description += " src{" + socketAddressToString(mSocketAddress) + "}";
524 
525             // Build a trivial ICMP packet.
526             final byte[] icmpPacket = {
527                     (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0  // ICMP header
528             };
529 
530             int count = 0;
531             mMeasurement.startTime = now();
532             while (now() < mDeadlineTime - (TIMEOUT_SEND + TIMEOUT_RECV)) {
533                 count++;
534                 icmpPacket[icmpPacket.length - 1] = (byte) count;
535                 try {
536                     Os.write(mFileDescriptor, icmpPacket, 0, icmpPacket.length);
537                 } catch (ErrnoException | InterruptedIOException e) {
538                     mMeasurement.recordFailure(e.toString());
539                     break;
540                 }
541 
542                 try {
543                     ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
544                     Os.read(mFileDescriptor, reply);
545                     // TODO: send a few pings back to back to guesstimate packet loss.
546                     mMeasurement.recordSuccess("1/" + count);
547                     break;
548                 } catch (ErrnoException | InterruptedIOException e) {
549                     continue;
550                 }
551             }
552             if (mMeasurement.finishTime == 0) {
553                 mMeasurement.recordFailure("0/" + count);
554             }
555 
556             close();
557         }
558     }
559 
560 
561     private class DnsUdpCheck extends SimpleSocketCheck implements Runnable {
562         private static final int TIMEOUT_SEND = 100;
563         private static final int TIMEOUT_RECV = 500;
564         private static final int RR_TYPE_A = 1;
565         private static final int RR_TYPE_AAAA = 28;
566         private static final int PACKET_BUFSIZE = 512;
567 
568         protected final Random mRandom = new Random();
569 
570         // Should be static, but the compiler mocks our puny, human attempts at reason.
responseCodeStr(int rcode)571         protected String responseCodeStr(int rcode) {
572             try {
573                 return DnsResponseCode.values()[rcode].toString();
574             } catch (IndexOutOfBoundsException e) {
575                 return String.valueOf(rcode);
576             }
577         }
578 
579         protected final int mQueryType;
580 
DnsUdpCheck(InetAddress target, Measurement measurement)581         public DnsUdpCheck(InetAddress target, Measurement measurement) {
582             super(target, measurement);
583 
584             // TODO: Ideally, query the target for both types regardless of address family.
585             if (mAddressFamily == AF_INET6) {
586                 mQueryType = RR_TYPE_AAAA;
587             } else {
588                 mQueryType = RR_TYPE_A;
589             }
590 
591             mMeasurement.description = "DNS UDP dst{" + mTarget.getHostAddress() + "}";
592         }
593 
594         @Override
run()595         public void run() {
596             if (ensureMeasurementNecessary()) return;
597 
598             try {
599                 setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV,
600                         NetworkConstants.DNS_SERVER_PORT);
601             } catch (ErrnoException | IOException e) {
602                 mMeasurement.recordFailure(e.toString());
603                 return;
604             }
605 
606             // This needs to be fixed length so it can be dropped into the pre-canned packet.
607             final String sixRandomDigits = String.valueOf(mRandom.nextInt(900000) + 100000);
608             appendDnsToMeasurementDescription(sixRandomDigits, mSocketAddress);
609 
610             // Build a trivial DNS packet.
611             final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
612 
613             int count = 0;
614             mMeasurement.startTime = now();
615             while (now() < mDeadlineTime - (TIMEOUT_RECV + TIMEOUT_RECV)) {
616                 count++;
617                 try {
618                     Os.write(mFileDescriptor, dnsPacket, 0, dnsPacket.length);
619                 } catch (ErrnoException | InterruptedIOException e) {
620                     mMeasurement.recordFailure(e.toString());
621                     break;
622                 }
623 
624                 try {
625                     ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
626                     Os.read(mFileDescriptor, reply);
627                     // TODO: more correct and detailed evaluation of the response,
628                     // possibly adding the returned IP address(es) to the output.
629                     final String rcodeStr = (reply.limit() > 3)
630                             ? " " + responseCodeStr((int) (reply.get(3)) & 0x0f)
631                             : "";
632                     mMeasurement.recordSuccess("1/" + count + rcodeStr);
633                     break;
634                 } catch (ErrnoException | InterruptedIOException e) {
635                     continue;
636                 }
637             }
638             if (mMeasurement.finishTime == 0) {
639                 mMeasurement.recordFailure("0/" + count);
640             }
641 
642             close();
643         }
644 
getDnsQueryPacket(String sixRandomDigits)645         protected byte[] getDnsQueryPacket(String sixRandomDigits) {
646             byte[] rnd = sixRandomDigits.getBytes(StandardCharsets.US_ASCII);
647             return new byte[] {
648                 (byte) mRandom.nextInt(), (byte) mRandom.nextInt(),  // [0-1]   query ID
649                 1, 0,  // [2-3]   flags; byte[2] = 1 for recursion desired (RD).
650                 0, 1,  // [4-5]   QDCOUNT (number of queries)
651                 0, 0,  // [6-7]   ANCOUNT (number of answers)
652                 0, 0,  // [8-9]   NSCOUNT (number of name server records)
653                 0, 0,  // [10-11] ARCOUNT (number of additional records)
654                 17, rnd[0], rnd[1], rnd[2], rnd[3], rnd[4], rnd[5],
655                         '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 's',
656                 6, 'm', 'e', 't', 'r', 'i', 'c',
657                 7, 'g', 's', 't', 'a', 't', 'i', 'c',
658                 3, 'c', 'o', 'm',
659                 0,  // null terminator of FQDN (root TLD)
660                 0, (byte) mQueryType,  // QTYPE
661                 0, 1  // QCLASS, set to 1 = IN (Internet)
662             };
663         }
664 
appendDnsToMeasurementDescription( String sixRandomDigits, SocketAddress sockAddr)665         protected void appendDnsToMeasurementDescription(
666                 String sixRandomDigits, SocketAddress sockAddr) {
667             mMeasurement.description += " src{" + socketAddressToString(sockAddr) + "}"
668                     + " qtype{" + mQueryType + "}"
669                     + " qname{" + sixRandomDigits + "-android-ds.metric.gstatic.com}";
670         }
671     }
672 
673     // TODO: Have it inherited from SimpleSocketCheck, and separate common DNS helpers out of
674     // DnsUdpCheck.
675     private class DnsTlsCheck extends DnsUdpCheck {
676         private static final int TCP_CONNECT_TIMEOUT_MS = 2500;
677         private static final int TCP_TIMEOUT_MS = 2000;
678         private static final int DNS_TLS_PORT = 853;
679         private static final int DNS_HEADER_SIZE = 12;
680 
681         private final String mHostname;
682 
DnsTlsCheck(@ullable String hostname, @NonNull InetAddress target, @NonNull Measurement measurement)683         public DnsTlsCheck(@Nullable String hostname, @NonNull InetAddress target,
684                 @NonNull Measurement measurement) {
685             super(target, measurement);
686 
687             mHostname = hostname;
688             mMeasurement.description = "DNS TLS dst{" + mTarget.getHostAddress() + "} hostname{"
689                     + (mHostname == null ? "" : mHostname) + "}";
690         }
691 
setupSSLSocket()692         private SSLSocket setupSSLSocket() throws IOException {
693             // A TrustManager will be created and initialized with a KeyStore containing system
694             // CaCerts. During SSL handshake, it will be used to validate the certificates from
695             // the server.
696             SSLSocket sslSocket = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
697             sslSocket.setSoTimeout(TCP_TIMEOUT_MS);
698 
699             if (!TextUtils.isEmpty(mHostname)) {
700                 // Set SNI.
701                 final List<SNIServerName> names =
702                         Collections.singletonList(new SNIHostName(mHostname));
703                 SSLParameters params = sslSocket.getSSLParameters();
704                 params.setServerNames(names);
705                 sslSocket.setSSLParameters(params);
706             }
707 
708             mNetwork.bindSocket(sslSocket);
709             return sslSocket;
710         }
711 
sendDoTProbe(@ullable SSLSocket sslSocket)712         private void sendDoTProbe(@Nullable SSLSocket sslSocket) throws IOException {
713             final String sixRandomDigits = String.valueOf(mRandom.nextInt(900000) + 100000);
714             final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
715 
716             mMeasurement.startTime = now();
717             sslSocket.connect(new InetSocketAddress(mTarget, DNS_TLS_PORT), TCP_CONNECT_TIMEOUT_MS);
718 
719             // Synchronous call waiting for the TLS handshake complete.
720             sslSocket.startHandshake();
721             appendDnsToMeasurementDescription(sixRandomDigits, sslSocket.getLocalSocketAddress());
722 
723             final DataOutputStream output = new DataOutputStream(sslSocket.getOutputStream());
724             output.writeShort(dnsPacket.length);
725             output.write(dnsPacket, 0, dnsPacket.length);
726 
727             final DataInputStream input = new DataInputStream(sslSocket.getInputStream());
728             final int replyLength = Short.toUnsignedInt(input.readShort());
729             final byte[] reply = new byte[replyLength];
730             int bytesRead = 0;
731             while (bytesRead < replyLength) {
732                 bytesRead += input.read(reply, bytesRead, replyLength - bytesRead);
733             }
734 
735             if (bytesRead > DNS_HEADER_SIZE && bytesRead == replyLength) {
736                 mMeasurement.recordSuccess("1/1 " + responseCodeStr((int) (reply[3]) & 0x0f));
737             } else {
738                 mMeasurement.recordFailure("1/1 Read " + bytesRead + " bytes while expected to be "
739                         + replyLength + " bytes");
740             }
741         }
742 
743         @Override
run()744         public void run() {
745             if (ensureMeasurementNecessary()) return;
746 
747             // No need to restore the tag, since this thread is only used for this measurement.
748             TrafficStats.getAndSetThreadStatsTag(NetworkStackConstants.TAG_SYSTEM_PROBE);
749 
750             try (SSLSocket sslSocket = setupSSLSocket()) {
751                 sendDoTProbe(sslSocket);
752             } catch (IOException e) {
753                 mMeasurement.recordFailure(e.toString());
754             }
755         }
756     }
757 }
758