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