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