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 
37 import java.io.Closeable;
38 import java.io.FileDescriptor;
39 import java.io.InterruptedIOException;
40 import java.io.IOException;
41 import java.net.Inet4Address;
42 import java.net.Inet6Address;
43 import java.net.InetAddress;
44 import java.net.InetSocketAddress;
45 import java.net.NetworkInterface;
46 import java.net.SocketAddress;
47 import java.net.SocketException;
48 import java.net.UnknownHostException;
49 import java.nio.ByteBuffer;
50 import java.nio.charset.StandardCharsets;
51 import java.util.concurrent.CountDownLatch;
52 import java.util.concurrent.TimeUnit;
53 import java.util.Arrays;
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 
60 import libcore.io.IoUtils;
61 
62 
63 /**
64  * NetworkDiagnostics
65  *
66  * A simple class to diagnose network connectivity fundamentals.  Current
67  * checks performed are:
68  *     - ICMPv4/v6 echo requests for all routers
69  *     - ICMPv4/v6 echo requests for all DNS servers
70  *     - DNS UDP queries to all DNS servers
71  *
72  * Currently unimplemented checks include:
73  *     - report ARP/ND data about on-link neighbors
74  *     - DNS TCP queries to all DNS servers
75  *     - HTTP DIRECT and PROXY checks
76  *     - port 443 blocking/TLS intercept checks
77  *     - QUIC reachability checks
78  *     - MTU checks
79  *
80  * The supplied timeout bounds the entire diagnostic process.  Each specific
81  * check class must implement this upper bound on measurements in whichever
82  * manner is most appropriate and effective.
83  *
84  * @hide
85  */
86 public class NetworkDiagnostics {
87     private static final String TAG = "NetworkDiagnostics";
88 
89     private static final InetAddress TEST_DNS4 = NetworkUtils.numericToInetAddress("8.8.8.8");
90     private static final InetAddress TEST_DNS6 = NetworkUtils.numericToInetAddress(
91             "2001:4860:4860::8888");
92 
93     // For brevity elsewhere.
now()94     private static final long now() {
95         return SystemClock.elapsedRealtime();
96     }
97 
98     // Values from RFC 1035 section 4.1.1, names from <arpa/nameser.h>.
99     // Should be a member of DnsUdpCheck, but "compiler says no".
100     public static enum DnsResponseCode { NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED };
101 
102     private final Network mNetwork;
103     private final LinkProperties mLinkProperties;
104     private final Integer mInterfaceIndex;
105 
106     private final long mTimeoutMs;
107     private final long mStartTime;
108     private final long mDeadlineTime;
109 
110     // A counter, initialized to the total number of measurements,
111     // so callers can wait for completion.
112     private final CountDownLatch mCountDownLatch;
113 
114     public class Measurement {
115         private static final String SUCCEEDED = "SUCCEEDED";
116         private static final String FAILED = "FAILED";
117 
118         private boolean succeeded;
119 
120         // Package private.  TODO: investigate better encapsulation.
121         String description = "";
122         long startTime;
123         long finishTime;
124         String result = "";
125         Thread thread;
126 
checkSucceeded()127         public boolean checkSucceeded() { return succeeded; }
128 
recordSuccess(String msg)129         void recordSuccess(String msg) {
130             maybeFixupTimes();
131             succeeded = true;
132             result = SUCCEEDED + ": " + msg;
133             if (mCountDownLatch != null) {
134                 mCountDownLatch.countDown();
135             }
136         }
137 
recordFailure(String msg)138         void recordFailure(String msg) {
139             maybeFixupTimes();
140             succeeded = false;
141             result = FAILED + ": " + msg;
142             if (mCountDownLatch != null) {
143                 mCountDownLatch.countDown();
144             }
145         }
146 
maybeFixupTimes()147         private void maybeFixupTimes() {
148             // Allows the caller to just set success/failure and not worry
149             // about also setting the correct finishing time.
150             if (finishTime == 0) { finishTime = now(); }
151 
152             // In cases where, for example, a failure has occurred before the
153             // measurement even began, fixup the start time to reflect as much.
154             if (startTime == 0) { startTime = finishTime; }
155         }
156 
157         @Override
toString()158         public String toString() {
159             return description + ": " + result + " (" + (finishTime - startTime) + "ms)";
160         }
161     }
162 
163     private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
164     private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks =
165             new HashMap<>();
166     private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
167     private final String mDescription;
168 
169 
NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs)170     public NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs) {
171         mNetwork = network;
172         mLinkProperties = lp;
173         mInterfaceIndex = getInterfaceIndex(mLinkProperties.getInterfaceName());
174         mTimeoutMs = timeoutMs;
175         mStartTime = now();
176         mDeadlineTime = mStartTime + mTimeoutMs;
177 
178         // Hardcode measurements to TEST_DNS4 and TEST_DNS6 in order to test off-link connectivity.
179         // We are free to modify mLinkProperties with impunity because ConnectivityService passes us
180         // a copy and not the original object. It's easier to do it this way because we don't need
181         // to check whether the LinkProperties already contains these DNS servers because
182         // LinkProperties#addDnsServer checks for duplicates.
183         if (mLinkProperties.isReachable(TEST_DNS4)) {
184             mLinkProperties.addDnsServer(TEST_DNS4);
185         }
186         // TODO: we could use mLinkProperties.isReachable(TEST_DNS6) here, because we won't set any
187         // DNS servers for which isReachable() is false, but since this is diagnostic code, be extra
188         // careful.
189         if (mLinkProperties.hasGlobalIPv6Address() || mLinkProperties.hasIPv6DefaultRoute()) {
190             mLinkProperties.addDnsServer(TEST_DNS6);
191         }
192 
193         for (RouteInfo route : mLinkProperties.getRoutes()) {
194             if (route.hasGateway()) {
195                 InetAddress gateway = route.getGateway();
196                 prepareIcmpMeasurement(gateway);
197                 if (route.isIPv6Default()) {
198                     prepareExplicitSourceIcmpMeasurements(gateway);
199                 }
200             }
201         }
202         for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
203                 prepareIcmpMeasurement(nameserver);
204                 prepareDnsMeasurement(nameserver);
205         }
206 
207         mCountDownLatch = new CountDownLatch(totalMeasurementCount());
208 
209         startMeasurements();
210 
211         mDescription = "ifaces{" + TextUtils.join(",", mLinkProperties.getAllInterfaceNames()) + "}"
212                 + " index{" + mInterfaceIndex + "}"
213                 + " network{" + mNetwork + "}"
214                 + " nethandle{" + mNetwork.getNetworkHandle() + "}";
215     }
216 
getInterfaceIndex(String ifname)217     private static Integer getInterfaceIndex(String ifname) {
218         try {
219             NetworkInterface ni = NetworkInterface.getByName(ifname);
220             return ni.getIndex();
221         } catch (NullPointerException | SocketException e) {
222             return null;
223         }
224     }
225 
prepareIcmpMeasurement(InetAddress target)226     private void prepareIcmpMeasurement(InetAddress target) {
227         if (!mIcmpChecks.containsKey(target)) {
228             Measurement measurement = new Measurement();
229             measurement.thread = new Thread(new IcmpCheck(target, measurement));
230             mIcmpChecks.put(target, measurement);
231         }
232     }
233 
prepareExplicitSourceIcmpMeasurements(InetAddress target)234     private void prepareExplicitSourceIcmpMeasurements(InetAddress target) {
235         for (LinkAddress l : mLinkProperties.getLinkAddresses()) {
236             InetAddress source = l.getAddress();
237             if (source instanceof Inet6Address && l.isGlobalPreferred()) {
238                 Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target);
239                 if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) {
240                     Measurement measurement = new Measurement();
241                     measurement.thread = new Thread(new IcmpCheck(source, target, measurement));
242                     mExplicitSourceIcmpChecks.put(srcTarget, measurement);
243                 }
244             }
245         }
246     }
247 
prepareDnsMeasurement(InetAddress target)248     private void prepareDnsMeasurement(InetAddress target) {
249         if (!mDnsUdpChecks.containsKey(target)) {
250             Measurement measurement = new Measurement();
251             measurement.thread = new Thread(new DnsUdpCheck(target, measurement));
252             mDnsUdpChecks.put(target, measurement);
253         }
254     }
255 
totalMeasurementCount()256     private int totalMeasurementCount() {
257         return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size();
258     }
259 
startMeasurements()260     private void startMeasurements() {
261         for (Measurement measurement : mIcmpChecks.values()) {
262             measurement.thread.start();
263         }
264         for (Measurement measurement : mExplicitSourceIcmpChecks.values()) {
265             measurement.thread.start();
266         }
267         for (Measurement measurement : mDnsUdpChecks.values()) {
268             measurement.thread.start();
269         }
270     }
271 
waitForMeasurements()272     public void waitForMeasurements() {
273         try {
274             mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
275         } catch (InterruptedException ignored) {}
276     }
277 
getMeasurements()278     public List<Measurement> getMeasurements() {
279         // TODO: Consider moving waitForMeasurements() in here to minimize the
280         // chance of caller errors.
281 
282         ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount());
283 
284         // Sort measurements IPv4 first.
285         for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
286             if (entry.getKey() instanceof Inet4Address) {
287                 measurements.add(entry.getValue());
288             }
289         }
290         for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
291                 mExplicitSourceIcmpChecks.entrySet()) {
292             if (entry.getKey().first instanceof Inet4Address) {
293                 measurements.add(entry.getValue());
294             }
295         }
296         for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
297             if (entry.getKey() instanceof Inet4Address) {
298                 measurements.add(entry.getValue());
299             }
300         }
301 
302         // IPv6 measurements second.
303         for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
304             if (entry.getKey() instanceof Inet6Address) {
305                 measurements.add(entry.getValue());
306             }
307         }
308         for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
309                 mExplicitSourceIcmpChecks.entrySet()) {
310             if (entry.getKey().first instanceof Inet6Address) {
311                 measurements.add(entry.getValue());
312             }
313         }
314         for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
315             if (entry.getKey() instanceof Inet6Address) {
316                 measurements.add(entry.getValue());
317             }
318         }
319 
320         return measurements;
321     }
322 
dump(IndentingPrintWriter pw)323     public void dump(IndentingPrintWriter pw) {
324         pw.println(TAG + ":" + mDescription);
325         final long unfinished = mCountDownLatch.getCount();
326         if (unfinished > 0) {
327             // This can't happen unless a caller forgets to call waitForMeasurements()
328             // or a measurement isn't implemented to correctly honor the timeout.
329             pw.println("WARNING: countdown wait incomplete: "
330                     + unfinished + " unfinished measurements");
331         }
332 
333         pw.increaseIndent();
334 
335         String prefix;
336         for (Measurement m : getMeasurements()) {
337             prefix = m.checkSucceeded() ? "." : "F";
338             pw.println(prefix + "  " + m.toString());
339         }
340 
341         pw.decreaseIndent();
342     }
343 
344 
345     private class SimpleSocketCheck implements Closeable {
346         protected final InetAddress mSource;  // Usually null.
347         protected final InetAddress mTarget;
348         protected final int mAddressFamily;
349         protected final Measurement mMeasurement;
350         protected FileDescriptor mFileDescriptor;
351         protected SocketAddress mSocketAddress;
352 
SimpleSocketCheck( InetAddress source, InetAddress target, Measurement measurement)353         protected SimpleSocketCheck(
354                 InetAddress source, InetAddress target, Measurement measurement) {
355             mMeasurement = measurement;
356 
357             if (target instanceof Inet6Address) {
358                 Inet6Address targetWithScopeId = null;
359                 if (target.isLinkLocalAddress() && mInterfaceIndex != null) {
360                     try {
361                         targetWithScopeId = Inet6Address.getByAddress(
362                                 null, target.getAddress(), mInterfaceIndex);
363                     } catch (UnknownHostException e) {
364                         mMeasurement.recordFailure(e.toString());
365                     }
366                 }
367                 mTarget = (targetWithScopeId != null) ? targetWithScopeId : target;
368                 mAddressFamily = AF_INET6;
369             } else {
370                 mTarget = target;
371                 mAddressFamily = AF_INET;
372             }
373 
374             // We don't need to check the scope ID here because we currently only do explicit-source
375             // measurements from global IPv6 addresses.
376             mSource = source;
377         }
378 
SimpleSocketCheck(InetAddress target, Measurement measurement)379         protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
380             this(null, target, measurement);
381         }
382 
setupSocket( int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)383         protected void setupSocket(
384                 int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
385                 throws ErrnoException, IOException {
386             final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.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