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 17package com.android.server.connectivity; 18 19import static android.system.OsConstants.*; 20 21import android.net.LinkAddress; 22import android.net.LinkProperties; 23import android.net.Network; 24import android.net.NetworkUtils; 25import android.net.RouteInfo; 26import android.os.SystemClock; 27import android.system.ErrnoException; 28import android.system.Os; 29import android.system.StructTimeval; 30import android.text.TextUtils; 31import android.util.Pair; 32 33import com.android.internal.util.IndentingPrintWriter; 34 35import java.io.Closeable; 36import java.io.FileDescriptor; 37import java.io.InterruptedIOException; 38import java.io.IOException; 39import java.net.Inet4Address; 40import java.net.Inet6Address; 41import java.net.InetAddress; 42import java.net.InetSocketAddress; 43import java.net.NetworkInterface; 44import java.net.SocketAddress; 45import java.net.SocketException; 46import java.net.UnknownHostException; 47import java.nio.ByteBuffer; 48import java.nio.charset.StandardCharsets; 49import java.util.concurrent.CountDownLatch; 50import java.util.concurrent.TimeUnit; 51import java.util.Arrays; 52import java.util.ArrayList; 53import java.util.HashMap; 54import java.util.List; 55import java.util.Map; 56import java.util.Random; 57 58import libcore.io.IoUtils; 59 60 61/** 62 * NetworkDiagnostics 63 * 64 * A simple class to diagnose network connectivity fundamentals. Current 65 * checks performed are: 66 * - ICMPv4/v6 echo requests for all routers 67 * - ICMPv4/v6 echo requests for all DNS servers 68 * - DNS UDP queries to all DNS servers 69 * 70 * Currently unimplemented checks include: 71 * - report ARP/ND data about on-link neighbors 72 * - DNS TCP queries to all DNS servers 73 * - HTTP DIRECT and PROXY checks 74 * - port 443 blocking/TLS intercept checks 75 * - QUIC reachability checks 76 * - MTU checks 77 * 78 * The supplied timeout bounds the entire diagnostic process. Each specific 79 * check class must implement this upper bound on measurements in whichever 80 * manner is most appropriate and effective. 81 * 82 * @hide 83 */ 84public class NetworkDiagnostics { 85 private static final String TAG = "NetworkDiagnostics"; 86 87 private static final InetAddress TEST_DNS4 = NetworkUtils.numericToInetAddress("8.8.8.8"); 88 private static final InetAddress TEST_DNS6 = NetworkUtils.numericToInetAddress( 89 "2001:4860:4860::8888"); 90 91 // For brevity elsewhere. 92 private static final long now() { 93 return SystemClock.elapsedRealtime(); 94 } 95 96 // Values from RFC 1035 section 4.1.1, names from <arpa/nameser.h>. 97 // Should be a member of DnsUdpCheck, but "compiler says no". 98 public static enum DnsResponseCode { NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED }; 99 100 private final Network mNetwork; 101 private final LinkProperties mLinkProperties; 102 private final Integer mInterfaceIndex; 103 104 private final long mTimeoutMs; 105 private final long mStartTime; 106 private final long mDeadlineTime; 107 108 // A counter, initialized to the total number of measurements, 109 // so callers can wait for completion. 110 private final CountDownLatch mCountDownLatch; 111 112 public class Measurement { 113 private static final String SUCCEEDED = "SUCCEEDED"; 114 private static final String FAILED = "FAILED"; 115 116 private boolean succeeded; 117 118 // Package private. TODO: investigate better encapsulation. 119 String description = ""; 120 long startTime; 121 long finishTime; 122 String result = ""; 123 Thread thread; 124 125 public boolean checkSucceeded() { return succeeded; } 126 127 void recordSuccess(String msg) { 128 maybeFixupTimes(); 129 succeeded = true; 130 result = SUCCEEDED + ": " + msg; 131 if (mCountDownLatch != null) { 132 mCountDownLatch.countDown(); 133 } 134 } 135 136 void recordFailure(String msg) { 137 maybeFixupTimes(); 138 succeeded = false; 139 result = FAILED + ": " + msg; 140 if (mCountDownLatch != null) { 141 mCountDownLatch.countDown(); 142 } 143 } 144 145 private void maybeFixupTimes() { 146 // Allows the caller to just set success/failure and not worry 147 // about also setting the correct finishing time. 148 if (finishTime == 0) { finishTime = now(); } 149 150 // In cases where, for example, a failure has occurred before the 151 // measurement even began, fixup the start time to reflect as much. 152 if (startTime == 0) { startTime = finishTime; } 153 } 154 155 @Override 156 public String toString() { 157 return description + ": " + result + " (" + (finishTime - startTime) + "ms)"; 158 } 159 } 160 161 private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>(); 162 private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks = 163 new HashMap<>(); 164 private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>(); 165 private final String mDescription; 166 167 168 public NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs) { 169 mNetwork = network; 170 mLinkProperties = lp; 171 mInterfaceIndex = getInterfaceIndex(mLinkProperties.getInterfaceName()); 172 mTimeoutMs = timeoutMs; 173 mStartTime = now(); 174 mDeadlineTime = mStartTime + mTimeoutMs; 175 176 // Hardcode measurements to TEST_DNS4 and TEST_DNS6 in order to test off-link connectivity. 177 // We are free to modify mLinkProperties with impunity because ConnectivityService passes us 178 // a copy and not the original object. It's easier to do it this way because we don't need 179 // to check whether the LinkProperties already contains these DNS servers because 180 // LinkProperties#addDnsServer checks for duplicates. 181 if (mLinkProperties.isReachable(TEST_DNS4)) { 182 mLinkProperties.addDnsServer(TEST_DNS4); 183 } 184 // TODO: we could use mLinkProperties.isReachable(TEST_DNS6) here, because we won't set any 185 // DNS servers for which isReachable() is false, but since this is diagnostic code, be extra 186 // careful. 187 if (mLinkProperties.hasGlobalIPv6Address() || mLinkProperties.hasIPv6DefaultRoute()) { 188 mLinkProperties.addDnsServer(TEST_DNS6); 189 } 190 191 for (RouteInfo route : mLinkProperties.getRoutes()) { 192 if (route.hasGateway()) { 193 InetAddress gateway = route.getGateway(); 194 prepareIcmpMeasurement(gateway); 195 if (route.isIPv6Default()) { 196 prepareExplicitSourceIcmpMeasurements(gateway); 197 } 198 } 199 } 200 for (InetAddress nameserver : mLinkProperties.getDnsServers()) { 201 prepareIcmpMeasurement(nameserver); 202 prepareDnsMeasurement(nameserver); 203 } 204 205 mCountDownLatch = new CountDownLatch(totalMeasurementCount()); 206 207 startMeasurements(); 208 209 mDescription = "ifaces{" + TextUtils.join(",", mLinkProperties.getAllInterfaceNames()) + "}" 210 + " index{" + mInterfaceIndex + "}" 211 + " network{" + mNetwork + "}" 212 + " nethandle{" + mNetwork.getNetworkHandle() + "}"; 213 } 214 215 private static Integer getInterfaceIndex(String ifname) { 216 try { 217 NetworkInterface ni = NetworkInterface.getByName(ifname); 218 return ni.getIndex(); 219 } catch (NullPointerException | SocketException e) { 220 return null; 221 } 222 } 223 224 private void prepareIcmpMeasurement(InetAddress target) { 225 if (!mIcmpChecks.containsKey(target)) { 226 Measurement measurement = new Measurement(); 227 measurement.thread = new Thread(new IcmpCheck(target, measurement)); 228 mIcmpChecks.put(target, measurement); 229 } 230 } 231 232 private void prepareExplicitSourceIcmpMeasurements(InetAddress target) { 233 for (LinkAddress l : mLinkProperties.getLinkAddresses()) { 234 InetAddress source = l.getAddress(); 235 if (source instanceof Inet6Address && l.isGlobalPreferred()) { 236 Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target); 237 if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) { 238 Measurement measurement = new Measurement(); 239 measurement.thread = new Thread(new IcmpCheck(source, target, measurement)); 240 mExplicitSourceIcmpChecks.put(srcTarget, measurement); 241 } 242 } 243 } 244 } 245 246 private void prepareDnsMeasurement(InetAddress target) { 247 if (!mDnsUdpChecks.containsKey(target)) { 248 Measurement measurement = new Measurement(); 249 measurement.thread = new Thread(new DnsUdpCheck(target, measurement)); 250 mDnsUdpChecks.put(target, measurement); 251 } 252 } 253 254 private int totalMeasurementCount() { 255 return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size(); 256 } 257 258 private void startMeasurements() { 259 for (Measurement measurement : mIcmpChecks.values()) { 260 measurement.thread.start(); 261 } 262 for (Measurement measurement : mExplicitSourceIcmpChecks.values()) { 263 measurement.thread.start(); 264 } 265 for (Measurement measurement : mDnsUdpChecks.values()) { 266 measurement.thread.start(); 267 } 268 } 269 270 public void waitForMeasurements() { 271 try { 272 mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS); 273 } catch (InterruptedException ignored) {} 274 } 275 276 public List<Measurement> getMeasurements() { 277 // TODO: Consider moving waitForMeasurements() in here to minimize the 278 // chance of caller errors. 279 280 ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount()); 281 282 // Sort measurements IPv4 first. 283 for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) { 284 if (entry.getKey() instanceof Inet4Address) { 285 measurements.add(entry.getValue()); 286 } 287 } 288 for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry : 289 mExplicitSourceIcmpChecks.entrySet()) { 290 if (entry.getKey().first instanceof Inet4Address) { 291 measurements.add(entry.getValue()); 292 } 293 } 294 for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) { 295 if (entry.getKey() instanceof Inet4Address) { 296 measurements.add(entry.getValue()); 297 } 298 } 299 300 // IPv6 measurements second. 301 for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) { 302 if (entry.getKey() instanceof Inet6Address) { 303 measurements.add(entry.getValue()); 304 } 305 } 306 for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry : 307 mExplicitSourceIcmpChecks.entrySet()) { 308 if (entry.getKey().first instanceof Inet6Address) { 309 measurements.add(entry.getValue()); 310 } 311 } 312 for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) { 313 if (entry.getKey() instanceof Inet6Address) { 314 measurements.add(entry.getValue()); 315 } 316 } 317 318 return measurements; 319 } 320 321 public void dump(IndentingPrintWriter pw) { 322 pw.println(TAG + ":" + mDescription); 323 final long unfinished = mCountDownLatch.getCount(); 324 if (unfinished > 0) { 325 // This can't happen unless a caller forgets to call waitForMeasurements() 326 // or a measurement isn't implemented to correctly honor the timeout. 327 pw.println("WARNING: countdown wait incomplete: " 328 + unfinished + " unfinished measurements"); 329 } 330 331 pw.increaseIndent(); 332 333 String prefix; 334 for (Measurement m : getMeasurements()) { 335 prefix = m.checkSucceeded() ? "." : "F"; 336 pw.println(prefix + " " + m.toString()); 337 } 338 339 pw.decreaseIndent(); 340 } 341 342 343 private class SimpleSocketCheck implements Closeable { 344 protected final InetAddress mSource; // Usually null. 345 protected final InetAddress mTarget; 346 protected final int mAddressFamily; 347 protected final Measurement mMeasurement; 348 protected FileDescriptor mFileDescriptor; 349 protected SocketAddress mSocketAddress; 350 351 protected SimpleSocketCheck( 352 InetAddress source, InetAddress target, Measurement measurement) { 353 mMeasurement = measurement; 354 355 if (target instanceof Inet6Address) { 356 Inet6Address targetWithScopeId = null; 357 if (target.isLinkLocalAddress() && mInterfaceIndex != null) { 358 try { 359 targetWithScopeId = Inet6Address.getByAddress( 360 null, target.getAddress(), mInterfaceIndex); 361 } catch (UnknownHostException e) { 362 mMeasurement.recordFailure(e.toString()); 363 } 364 } 365 mTarget = (targetWithScopeId != null) ? targetWithScopeId : target; 366 mAddressFamily = AF_INET6; 367 } else { 368 mTarget = target; 369 mAddressFamily = AF_INET; 370 } 371 372 // We don't need to check the scope ID here because we currently only do explicit-source 373 // measurements from global IPv6 addresses. 374 mSource = source; 375 } 376 377 protected SimpleSocketCheck(InetAddress target, Measurement measurement) { 378 this(null, target, measurement); 379 } 380 381 protected void setupSocket( 382 int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort) 383 throws ErrnoException, IOException { 384 mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol); 385 // Setting SNDTIMEO is purely for defensive purposes. 386 Os.setsockoptTimeval(mFileDescriptor, 387 SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout)); 388 Os.setsockoptTimeval(mFileDescriptor, 389 SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout)); 390 // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability. 391 mNetwork.bindSocket(mFileDescriptor); 392 if (mSource != null) { 393 Os.bind(mFileDescriptor, mSource, 0); 394 } 395 Os.connect(mFileDescriptor, mTarget, dstPort); 396 mSocketAddress = Os.getsockname(mFileDescriptor); 397 } 398 399 protected String getSocketAddressString() { 400 // The default toString() implementation is not the prettiest. 401 InetSocketAddress inetSockAddr = (InetSocketAddress) mSocketAddress; 402 InetAddress localAddr = inetSockAddr.getAddress(); 403 return String.format( 404 (localAddr instanceof Inet6Address ? "[%s]:%d" : "%s:%d"), 405 localAddr.getHostAddress(), inetSockAddr.getPort()); 406 } 407 408 @Override 409 public void close() { 410 IoUtils.closeQuietly(mFileDescriptor); 411 } 412 } 413 414 415 private class IcmpCheck extends SimpleSocketCheck implements Runnable { 416 private static final int TIMEOUT_SEND = 100; 417 private static final int TIMEOUT_RECV = 300; 418 private static final int ICMPV4_ECHO_REQUEST = 8; 419 private static final int ICMPV6_ECHO_REQUEST = 128; 420 private static final int PACKET_BUFSIZE = 512; 421 private final int mProtocol; 422 private final int mIcmpType; 423 424 public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) { 425 super(source, target, measurement); 426 427 if (mAddressFamily == AF_INET6) { 428 mProtocol = IPPROTO_ICMPV6; 429 mIcmpType = ICMPV6_ECHO_REQUEST; 430 mMeasurement.description = "ICMPv6"; 431 } else { 432 mProtocol = IPPROTO_ICMP; 433 mIcmpType = ICMPV4_ECHO_REQUEST; 434 mMeasurement.description = "ICMPv4"; 435 } 436 437 mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}"; 438 } 439 440 public IcmpCheck(InetAddress target, Measurement measurement) { 441 this(null, target, measurement); 442 } 443 444 @Override 445 public void run() { 446 // Check if this measurement has already failed during setup. 447 if (mMeasurement.finishTime > 0) { 448 // If the measurement failed during construction it didn't 449 // decrement the countdown latch; do so here. 450 mCountDownLatch.countDown(); 451 return; 452 } 453 454 try { 455 setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0); 456 } catch (ErrnoException | IOException e) { 457 mMeasurement.recordFailure(e.toString()); 458 return; 459 } 460 mMeasurement.description += " src{" + getSocketAddressString() + "}"; 461 462 // Build a trivial ICMP packet. 463 final byte[] icmpPacket = { 464 (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0 // ICMP header 465 }; 466 467 int count = 0; 468 mMeasurement.startTime = now(); 469 while (now() < mDeadlineTime - (TIMEOUT_SEND + TIMEOUT_RECV)) { 470 count++; 471 icmpPacket[icmpPacket.length - 1] = (byte) count; 472 try { 473 Os.write(mFileDescriptor, icmpPacket, 0, icmpPacket.length); 474 } catch (ErrnoException | InterruptedIOException e) { 475 mMeasurement.recordFailure(e.toString()); 476 break; 477 } 478 479 try { 480 ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE); 481 Os.read(mFileDescriptor, reply); 482 // TODO: send a few pings back to back to guesstimate packet loss. 483 mMeasurement.recordSuccess("1/" + count); 484 break; 485 } catch (ErrnoException | InterruptedIOException e) { 486 continue; 487 } 488 } 489 if (mMeasurement.finishTime == 0) { 490 mMeasurement.recordFailure("0/" + count); 491 } 492 493 close(); 494 } 495 } 496 497 498 private class DnsUdpCheck extends SimpleSocketCheck implements Runnable { 499 private static final int TIMEOUT_SEND = 100; 500 private static final int TIMEOUT_RECV = 500; 501 private static final int DNS_SERVER_PORT = 53; 502 private static final int RR_TYPE_A = 1; 503 private static final int RR_TYPE_AAAA = 28; 504 private static final int PACKET_BUFSIZE = 512; 505 506 private final Random mRandom = new Random(); 507 508 // Should be static, but the compiler mocks our puny, human attempts at reason. 509 private String responseCodeStr(int rcode) { 510 try { 511 return DnsResponseCode.values()[rcode].toString(); 512 } catch (IndexOutOfBoundsException e) { 513 return String.valueOf(rcode); 514 } 515 } 516 517 private final int mQueryType; 518 519 public DnsUdpCheck(InetAddress target, Measurement measurement) { 520 super(target, measurement); 521 522 // TODO: Ideally, query the target for both types regardless of address family. 523 if (mAddressFamily == AF_INET6) { 524 mQueryType = RR_TYPE_AAAA; 525 } else { 526 mQueryType = RR_TYPE_A; 527 } 528 529 mMeasurement.description = "DNS UDP dst{" + mTarget.getHostAddress() + "}"; 530 } 531 532 @Override 533 public void run() { 534 // Check if this measurement has already failed during setup. 535 if (mMeasurement.finishTime > 0) { 536 // If the measurement failed during construction it didn't 537 // decrement the countdown latch; do so here. 538 mCountDownLatch.countDown(); 539 return; 540 } 541 542 try { 543 setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV, DNS_SERVER_PORT); 544 } catch (ErrnoException | IOException e) { 545 mMeasurement.recordFailure(e.toString()); 546 return; 547 } 548 mMeasurement.description += " src{" + getSocketAddressString() + "}"; 549 550 // This needs to be fixed length so it can be dropped into the pre-canned packet. 551 final String sixRandomDigits = String.valueOf(mRandom.nextInt(900000) + 100000); 552 mMeasurement.description += " qtype{" + mQueryType + "}" 553 + " qname{" + sixRandomDigits + "-android-ds.metric.gstatic.com}"; 554 555 // Build a trivial DNS packet. 556 final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits); 557 558 int count = 0; 559 mMeasurement.startTime = now(); 560 while (now() < mDeadlineTime - (TIMEOUT_RECV + TIMEOUT_RECV)) { 561 count++; 562 try { 563 Os.write(mFileDescriptor, dnsPacket, 0, dnsPacket.length); 564 } catch (ErrnoException | InterruptedIOException e) { 565 mMeasurement.recordFailure(e.toString()); 566 break; 567 } 568 569 try { 570 ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE); 571 Os.read(mFileDescriptor, reply); 572 // TODO: more correct and detailed evaluation of the response, 573 // possibly adding the returned IP address(es) to the output. 574 final String rcodeStr = (reply.limit() > 3) 575 ? " " + responseCodeStr((int) (reply.get(3)) & 0x0f) 576 : ""; 577 mMeasurement.recordSuccess("1/" + count + rcodeStr); 578 break; 579 } catch (ErrnoException | InterruptedIOException e) { 580 continue; 581 } 582 } 583 if (mMeasurement.finishTime == 0) { 584 mMeasurement.recordFailure("0/" + count); 585 } 586 587 close(); 588 } 589 590 private byte[] getDnsQueryPacket(String sixRandomDigits) { 591 byte[] rnd = sixRandomDigits.getBytes(StandardCharsets.US_ASCII); 592 return new byte[] { 593 (byte) mRandom.nextInt(), (byte) mRandom.nextInt(), // [0-1] query ID 594 1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD). 595 0, 1, // [4-5] QDCOUNT (number of queries) 596 0, 0, // [6-7] ANCOUNT (number of answers) 597 0, 0, // [8-9] NSCOUNT (number of name server records) 598 0, 0, // [10-11] ARCOUNT (number of additional records) 599 17, rnd[0], rnd[1], rnd[2], rnd[3], rnd[4], rnd[5], 600 '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 's', 601 6, 'm', 'e', 't', 'r', 'i', 'c', 602 7, 'g', 's', 't', 'a', 't', 'i', 'c', 603 3, 'c', 'o', 'm', 604 0, // null terminator of FQDN (root TLD) 605 0, (byte) mQueryType, // QTYPE 606 0, 1 // QCLASS, set to 1 = IN (Internet) 607 }; 608 } 609 } 610} 611