Home | History | Annotate | Download | only in connectivity
      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.os.SystemClock;
     28 import android.system.ErrnoException;
     29 import android.system.Os;
     30 import android.system.StructTimeval;
     31 import android.text.TextUtils;
     32 import android.util.Pair;
     33 
     34 import com.android.internal.util.IndentingPrintWriter;
     35 
     36 import java.io.Closeable;
     37 import java.io.FileDescriptor;
     38 import java.io.InterruptedIOException;
     39 import java.io.IOException;
     40 import java.net.Inet4Address;
     41 import java.net.Inet6Address;
     42 import java.net.InetAddress;
     43 import java.net.InetSocketAddress;
     44 import java.net.NetworkInterface;
     45 import java.net.SocketAddress;
     46 import java.net.SocketException;
     47 import java.net.UnknownHostException;
     48 import java.nio.ByteBuffer;
     49 import java.nio.charset.StandardCharsets;
     50 import java.util.concurrent.CountDownLatch;
     51 import java.util.concurrent.TimeUnit;
     52 import java.util.Arrays;
     53 import java.util.ArrayList;
     54 import java.util.HashMap;
     55 import java.util.List;
     56 import java.util.Map;
     57 import java.util.Random;
     58 
     59 import 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  */
     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.
     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 
    126         public boolean checkSucceeded() { return succeeded; }
    127 
    128         void recordSuccess(String msg) {
    129             maybeFixupTimes();
    130             succeeded = true;
    131             result = SUCCEEDED + ": " + msg;
    132             if (mCountDownLatch != null) {
    133                 mCountDownLatch.countDown();
    134             }
    135         }
    136 
    137         void recordFailure(String msg) {
    138             maybeFixupTimes();
    139             succeeded = false;
    140             result = FAILED + ": " + msg;
    141             if (mCountDownLatch != null) {
    142                 mCountDownLatch.countDown();
    143             }
    144         }
    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<>();
    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 
    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 
    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 
    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 
    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 
    255     private int totalMeasurementCount() {
    256         return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size();
    257     }
    258 
    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 
    271     public void waitForMeasurements() {
    272         try {
    273             mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
    274         } catch (InterruptedException ignored) {}
    275     }
    276 
    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 
    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 
    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 
    378         protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
    379             this(null, target, measurement);
    380         }
    381 
    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(TrafficStats.TAG_SYSTEM_PROBE);
    386             try {
    387                 mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
    388             } finally {
    389                 TrafficStats.setThreadStatsTag(oldTag);
    390             }
    391             // 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);
    398             if (mSource != null) {
    399                 Os.bind(mFileDescriptor, mSource, 0);
    400             }
    401             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 
    430         public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) {
    431             super(source, target, measurement);
    432 
    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 
    446         public IcmpCheck(InetAddress target, Measurement measurement) {
    447             this(null, target, measurement);
    448         }
    449 
    450         @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.
    557             final String sixRandomDigits = String.valueOf(mRandom.nextInt(900000) + 100000);
    558             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 }
    617