• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.SystemClock;
27 import android.system.ErrnoException;
28 import android.system.Os;
29 import android.system.StructTimeval;
30 import android.text.TextUtils;
31 import android.util.Pair;
32 
33 import com.android.internal.util.IndentingPrintWriter;
34 
35 import java.io.Closeable;
36 import java.io.FileDescriptor;
37 import java.io.InterruptedIOException;
38 import java.io.IOException;
39 import java.net.Inet4Address;
40 import java.net.Inet6Address;
41 import java.net.InetAddress;
42 import java.net.InetSocketAddress;
43 import java.net.NetworkInterface;
44 import java.net.SocketAddress;
45 import java.net.SocketException;
46 import java.net.UnknownHostException;
47 import java.nio.ByteBuffer;
48 import java.nio.charset.StandardCharsets;
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.TimeUnit;
51 import java.util.Arrays;
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Random;
57 
58 import 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  */
84 public 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.
now()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 
checkSucceeded()125         public boolean checkSucceeded() { return succeeded; }
126 
recordSuccess(String msg)127         void recordSuccess(String msg) {
128             maybeFixupTimes();
129             succeeded = true;
130             result = SUCCEEDED + ": " + msg;
131             if (mCountDownLatch != null) {
132                 mCountDownLatch.countDown();
133             }
134         }
135 
recordFailure(String msg)136         void recordFailure(String msg) {
137             maybeFixupTimes();
138             succeeded = false;
139             result = FAILED + ": " + msg;
140             if (mCountDownLatch != null) {
141                 mCountDownLatch.countDown();
142             }
143         }
144 
maybeFixupTimes()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
toString()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 
NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs)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 
getInterfaceIndex(String ifname)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 
prepareIcmpMeasurement(InetAddress target)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 
prepareExplicitSourceIcmpMeasurements(InetAddress target)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 
prepareDnsMeasurement(InetAddress target)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 
totalMeasurementCount()254     private int totalMeasurementCount() {
255         return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size();
256     }
257 
startMeasurements()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 
waitForMeasurements()270     public void waitForMeasurements() {
271         try {
272             mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
273         } catch (InterruptedException ignored) {}
274     }
275 
getMeasurements()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 
dump(IndentingPrintWriter pw)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 
SimpleSocketCheck( InetAddress source, InetAddress target, Measurement measurement)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 
SimpleSocketCheck(InetAddress target, Measurement measurement)377         protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
378             this(null, target, measurement);
379         }
380 
setupSocket( int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)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 
getSocketAddressString()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
close()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 
IcmpCheck(InetAddress source, InetAddress target, Measurement measurement)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 
IcmpCheck(InetAddress target, Measurement measurement)440         public IcmpCheck(InetAddress target, Measurement measurement) {
441             this(null, target, measurement);
442         }
443 
444         @Override
run()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.
responseCodeStr(int rcode)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 
DnsUdpCheck(InetAddress target, Measurement measurement)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
run()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 
getDnsQueryPacket(String sixRandomDigits)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