1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.connectivity;
18 
19 import static android.system.OsConstants.*;
20 
21 import android.net.LinkAddress;
22 import android.net.LinkProperties;
23 import android.net.Network;
24 import android.net.NetworkUtils;
25 import android.net.RouteInfo;
26 import android.net.TrafficStats;
27 import android.net.util.NetworkConstants;
28 import android.os.SystemClock;
29 import android.system.ErrnoException;
30 import android.system.Os;
31 import android.system.StructTimeval;
32 import android.text.TextUtils;
33 import android.util.Pair;
34 
35 import com.android.internal.util.IndentingPrintWriter;
36 import com.android.internal.util.TrafficStatsConstants;
37 
38 import libcore.io.IoUtils;
39 
40 import java.io.Closeable;
41 import java.io.FileDescriptor;
42 import java.io.IOException;
43 import java.io.InterruptedIOException;
44 import java.net.Inet4Address;
45 import java.net.Inet6Address;
46 import java.net.InetAddress;
47 import java.net.InetSocketAddress;
48 import java.net.NetworkInterface;
49 import java.net.SocketAddress;
50 import java.net.SocketException;
51 import java.net.UnknownHostException;
52 import java.nio.ByteBuffer;
53 import java.nio.charset.StandardCharsets;
54 import java.util.ArrayList;
55 import java.util.HashMap;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Random;
59 import java.util.concurrent.CountDownLatch;
60 import java.util.concurrent.TimeUnit;
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  */
85 public class NetworkDiagnostics {
86     private static final String TAG = "NetworkDiagnostics";
87 
88     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 
92     // For brevity elsewhere.
now()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 
113     public class Measurement {
114         private static final String SUCCEEDED = "SUCCEEDED";
115         private static final String FAILED = "FAILED";
116 
117         private boolean succeeded;
118 
119         // Package private.  TODO: investigate better encapsulation.
120         String description = "";
121         long startTime;
122         long finishTime;
123         String result = "";
124         Thread thread;
125 
checkSucceeded()126         public boolean checkSucceeded() { return succeeded; }
127 
recordSuccess(String msg)128         void recordSuccess(String msg) {
129             maybeFixupTimes();
130             succeeded = true;
131             result = SUCCEEDED + ": " + msg;
132             if (mCountDownLatch != null) {
133                 mCountDownLatch.countDown();
134             }
135         }
136 
recordFailure(String msg)137         void recordFailure(String msg) {
138             maybeFixupTimes();
139             succeeded = false;
140             result = FAILED + ": " + msg;
141             if (mCountDownLatch != null) {
142                 mCountDownLatch.countDown();
143             }
144         }
145 
maybeFixupTimes()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
toString()157         public String toString() {
158             return description + ": " + result + " (" + (finishTime - startTime) + "ms)";
159         }
160     }
161 
162     private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
163     private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks =
164             new HashMap<>();
165     private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
166     private final String mDescription;
167 
168 
NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs)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 
177         // 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 
192         for (RouteInfo route : mLinkProperties.getRoutes()) {
193             if (route.hasGateway()) {
194                 InetAddress gateway = route.getGateway();
195                 prepareIcmpMeasurement(gateway);
196                 if (route.isIPv6Default()) {
197                     prepareExplicitSourceIcmpMeasurements(gateway);
198                 }
199             }
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 
getInterfaceIndex(String ifname)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 
prepareIcmpMeasurement(InetAddress target)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 
prepareExplicitSourceIcmpMeasurements(InetAddress target)233     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 
prepareDnsMeasurement(InetAddress target)247     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 
totalMeasurementCount()255     private int totalMeasurementCount() {
256         return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size();
257     }
258 
startMeasurements()259     private void startMeasurements() {
260         for (Measurement measurement : mIcmpChecks.values()) {
261             measurement.thread.start();
262         }
263         for (Measurement measurement : mExplicitSourceIcmpChecks.values()) {
264             measurement.thread.start();
265         }
266         for (Measurement measurement : mDnsUdpChecks.values()) {
267             measurement.thread.start();
268         }
269     }
270 
waitForMeasurements()271     public void waitForMeasurements() {
272         try {
273             mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
274         } catch (InterruptedException ignored) {}
275     }
276 
getMeasurements()277     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 
dump(IndentingPrintWriter pw)322     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();
333 
334         String prefix;
335         for (Measurement m : getMeasurements()) {
336             prefix = m.checkSucceeded() ? "." : "F";
337             pw.println(prefix + "  " + m.toString());
338         }
339 
340         pw.decreaseIndent();
341     }
342 
343 
344     private class SimpleSocketCheck implements Closeable {
345         protected final InetAddress mSource;  // Usually null.
346         protected final InetAddress mTarget;
347         protected final int mAddressFamily;
348         protected final Measurement mMeasurement;
349         protected FileDescriptor mFileDescriptor;
350         protected SocketAddress mSocketAddress;
351 
SimpleSocketCheck( InetAddress source, InetAddress target, Measurement measurement)352         protected SimpleSocketCheck(
353                 InetAddress source, InetAddress target, Measurement measurement) {
354             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             }
372 
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 
SimpleSocketCheck(InetAddress target, Measurement measurement)378         protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
379             this(null, target, measurement);
380         }
381 
setupSocket( int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)382         protected void setupSocket(
383                 int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
384                 throws ErrnoException, IOException {
385             final int oldTag = TrafficStats.getAndSetThreadStatsTag(
386                     TrafficStatsConstants.TAG_SYSTEM_PROBE);
387             try {
388                 mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
389             } finally {
390                 TrafficStats.setThreadStatsTag(oldTag);
391             }
392             // Setting SNDTIMEO is purely for defensive purposes.
393             Os.setsockoptTimeval(mFileDescriptor,
394                     SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
395             Os.setsockoptTimeval(mFileDescriptor,
396                     SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout));
397             // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability.
398             mNetwork.bindSocket(mFileDescriptor);
399             if (mSource != null) {
400                 Os.bind(mFileDescriptor, mSource, 0);
401             }
402             Os.connect(mFileDescriptor, mTarget, dstPort);
403             mSocketAddress = Os.getsockname(mFileDescriptor);
404         }
405 
getSocketAddressString()406         protected String getSocketAddressString() {
407             // The default toString() implementation is not the prettiest.
408             InetSocketAddress inetSockAddr = (InetSocketAddress) mSocketAddress;
409             InetAddress localAddr = inetSockAddr.getAddress();
410             return String.format(
411                     (localAddr instanceof Inet6Address ? "[%s]:%d" : "%s:%d"),
412                     localAddr.getHostAddress(), inetSockAddr.getPort());
413         }
414 
415         @Override
close()416         public void close() {
417             IoUtils.closeQuietly(mFileDescriptor);
418         }
419     }
420 
421 
422     private class IcmpCheck extends SimpleSocketCheck implements Runnable {
423         private static final int TIMEOUT_SEND = 100;
424         private static final int TIMEOUT_RECV = 300;
425         private static final int PACKET_BUFSIZE = 512;
426         private final int mProtocol;
427         private final int mIcmpType;
428 
IcmpCheck(InetAddress source, InetAddress target, Measurement measurement)429         public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) {
430             super(source, target, measurement);
431 
432             if (mAddressFamily == AF_INET6) {
433                 mProtocol = IPPROTO_ICMPV6;
434                 mIcmpType = NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
435                 mMeasurement.description = "ICMPv6";
436             } else {
437                 mProtocol = IPPROTO_ICMP;
438                 mIcmpType = NetworkConstants.ICMPV4_ECHO_REQUEST_TYPE;
439                 mMeasurement.description = "ICMPv4";
440             }
441 
442             mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
443         }
444 
IcmpCheck(InetAddress target, Measurement measurement)445         public IcmpCheck(InetAddress target, Measurement measurement) {
446             this(null, target, measurement);
447         }
448 
449         @Override
run()450         public void run() {
451             // Check if this measurement has already failed during setup.
452             if (mMeasurement.finishTime > 0) {
453                 // If the measurement failed during construction it didn't
454                 // decrement the countdown latch; do so here.
455                 mCountDownLatch.countDown();
456                 return;
457             }
458 
459             try {
460                 setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
461             } catch (ErrnoException | IOException e) {
462                 mMeasurement.recordFailure(e.toString());
463                 return;
464             }
465             mMeasurement.description += " src{" + getSocketAddressString() + "}";
466 
467             // Build a trivial ICMP packet.
468             final byte[] icmpPacket = {
469                     (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0  // ICMP header
470             };
471 
472             int count = 0;
473             mMeasurement.startTime = now();
474             while (now() < mDeadlineTime - (TIMEOUT_SEND + TIMEOUT_RECV)) {
475                 count++;
476                 icmpPacket[icmpPacket.length - 1] = (byte) count;
477                 try {
478                     Os.write(mFileDescriptor, icmpPacket, 0, icmpPacket.length);
479                 } catch (ErrnoException | InterruptedIOException e) {
480                     mMeasurement.recordFailure(e.toString());
481                     break;
482                 }
483 
484                 try {
485                     ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
486                     Os.read(mFileDescriptor, reply);
487                     // TODO: send a few pings back to back to guesstimate packet loss.
488                     mMeasurement.recordSuccess("1/" + count);
489                     break;
490                 } catch (ErrnoException | InterruptedIOException e) {
491                     continue;
492                 }
493             }
494             if (mMeasurement.finishTime == 0) {
495                 mMeasurement.recordFailure("0/" + count);
496             }
497 
498             close();
499         }
500     }
501 
502 
503     private class DnsUdpCheck extends SimpleSocketCheck implements Runnable {
504         private static final int TIMEOUT_SEND = 100;
505         private static final int TIMEOUT_RECV = 500;
506         private static final int RR_TYPE_A = 1;
507         private static final int RR_TYPE_AAAA = 28;
508         private static final int PACKET_BUFSIZE = 512;
509 
510         private final Random mRandom = new Random();
511 
512         // Should be static, but the compiler mocks our puny, human attempts at reason.
responseCodeStr(int rcode)513         private String responseCodeStr(int rcode) {
514             try {
515                 return DnsResponseCode.values()[rcode].toString();
516             } catch (IndexOutOfBoundsException e) {
517                 return String.valueOf(rcode);
518             }
519         }
520 
521         private final int mQueryType;
522 
DnsUdpCheck(InetAddress target, Measurement measurement)523         public DnsUdpCheck(InetAddress target, Measurement measurement) {
524             super(target, measurement);
525 
526             // TODO: Ideally, query the target for both types regardless of address family.
527             if (mAddressFamily == AF_INET6) {
528                 mQueryType = RR_TYPE_AAAA;
529             } else {
530                 mQueryType = RR_TYPE_A;
531             }
532 
533             mMeasurement.description = "DNS UDP dst{" + mTarget.getHostAddress() + "}";
534         }
535 
536         @Override
run()537         public void run() {
538             // Check if this measurement has already failed during setup.
539             if (mMeasurement.finishTime > 0) {
540                 // If the measurement failed during construction it didn't
541                 // decrement the countdown latch; do so here.
542                 mCountDownLatch.countDown();
543                 return;
544             }
545 
546             try {
547                 setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV,
548                         NetworkConstants.DNS_SERVER_PORT);
549             } catch (ErrnoException | IOException e) {
550                 mMeasurement.recordFailure(e.toString());
551                 return;
552             }
553             mMeasurement.description += " src{" + getSocketAddressString() + "}";
554 
555             // This needs to be fixed length so it can be dropped into the pre-canned packet.
556             final String sixRandomDigits = String.valueOf(mRandom.nextInt(900000) + 100000);
557             mMeasurement.description += " qtype{" + mQueryType + "}"
558                     + " qname{" + sixRandomDigits + "-android-ds.metric.gstatic.com}";
559 
560             // Build a trivial DNS packet.
561             final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
562 
563             int count = 0;
564             mMeasurement.startTime = now();
565             while (now() < mDeadlineTime - (TIMEOUT_RECV + TIMEOUT_RECV)) {
566                 count++;
567                 try {
568                     Os.write(mFileDescriptor, dnsPacket, 0, dnsPacket.length);
569                 } catch (ErrnoException | InterruptedIOException e) {
570                     mMeasurement.recordFailure(e.toString());
571                     break;
572                 }
573 
574                 try {
575                     ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
576                     Os.read(mFileDescriptor, reply);
577                     // TODO: more correct and detailed evaluation of the response,
578                     // possibly adding the returned IP address(es) to the output.
579                     final String rcodeStr = (reply.limit() > 3)
580                             ? " " + responseCodeStr((int) (reply.get(3)) & 0x0f)
581                             : "";
582                     mMeasurement.recordSuccess("1/" + count + rcodeStr);
583                     break;
584                 } catch (ErrnoException | InterruptedIOException e) {
585                     continue;
586                 }
587             }
588             if (mMeasurement.finishTime == 0) {
589                 mMeasurement.recordFailure("0/" + count);
590             }
591 
592             close();
593         }
594 
getDnsQueryPacket(String sixRandomDigits)595         private byte[] getDnsQueryPacket(String sixRandomDigits) {
596             byte[] rnd = sixRandomDigits.getBytes(StandardCharsets.US_ASCII);
597             return new byte[] {
598                 (byte) mRandom.nextInt(), (byte) mRandom.nextInt(),  // [0-1]   query ID
599                 1, 0,  // [2-3]   flags; byte[2] = 1 for recursion desired (RD).
600                 0, 1,  // [4-5]   QDCOUNT (number of queries)
601                 0, 0,  // [6-7]   ANCOUNT (number of answers)
602                 0, 0,  // [8-9]   NSCOUNT (number of name server records)
603                 0, 0,  // [10-11] ARCOUNT (number of additional records)
604                 17, rnd[0], rnd[1], rnd[2], rnd[3], rnd[4], rnd[5],
605                         '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 's',
606                 6, 'm', 'e', 't', 'r', 'i', 'c',
607                 7, 'g', 's', 't', 'a', 't', 'i', 'c',
608                 3, 'c', 'o', 'm',
609                 0,  // null terminator of FQDN (root TLD)
610                 0, (byte) mQueryType,  // QTYPE
611                 0, 1  // QCLASS, set to 1 = IN (Internet)
612             };
613         }
614     }
615 }
616