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