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