initial revision of NetworkDiagnostics
Bug: 21449922 Change-Id: I6a66cfa98601d6ccfe38ff840b0dd49ed1538a41
This commit is contained in:
@@ -112,6 +112,7 @@ import com.android.internal.util.IndentingPrintWriter;
|
|||||||
import com.android.internal.util.XmlUtils;
|
import com.android.internal.util.XmlUtils;
|
||||||
import com.android.server.am.BatteryStatsService;
|
import com.android.server.am.BatteryStatsService;
|
||||||
import com.android.server.connectivity.DataConnectionStats;
|
import com.android.server.connectivity.DataConnectionStats;
|
||||||
|
import com.android.server.connectivity.NetworkDiagnostics;
|
||||||
import com.android.server.connectivity.Nat464Xlat;
|
import com.android.server.connectivity.Nat464Xlat;
|
||||||
import com.android.server.connectivity.NetworkAgentInfo;
|
import com.android.server.connectivity.NetworkAgentInfo;
|
||||||
import com.android.server.connectivity.NetworkMonitor;
|
import com.android.server.connectivity.NetworkMonitor;
|
||||||
@@ -1749,6 +1750,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldPerformDiagnostics(String[] args) {
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg.equals("--diag")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||||
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
||||||
@@ -1761,6 +1771,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
|
||||||
|
if (shouldPerformDiagnostics(args)) {
|
||||||
|
final long DIAG_TIME_MS = 5000;
|
||||||
|
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
|
||||||
|
// Start gathering diagnostic information.
|
||||||
|
netDiags.add(new NetworkDiagnostics(
|
||||||
|
nai.network,
|
||||||
|
new LinkProperties(nai.linkProperties),
|
||||||
|
DIAG_TIME_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (NetworkDiagnostics netDiag : netDiags) {
|
||||||
|
pw.println();
|
||||||
|
netDiag.waitForMeasurements();
|
||||||
|
netDiag.dump(pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pw.print("NetworkFactories for:");
|
pw.print("NetworkFactories for:");
|
||||||
for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
|
for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
|
||||||
pw.print(" " + nfi.name);
|
pw.print(" " + nfi.name);
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.connectivity;
|
||||||
|
|
||||||
|
import static android.system.OsConstants.*;
|
||||||
|
|
||||||
|
import android.net.LinkProperties;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.RouteInfo;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.system.ErrnoException;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.system.StructTimeval;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NetworkDiagnostics
|
||||||
|
*
|
||||||
|
* A simple class to diagnose network connectivity fundamentals. Current
|
||||||
|
* checks performed are:
|
||||||
|
* - ICMPv4/v6 echo requests for all routers
|
||||||
|
* - ICMPv4/v6 echo requests for all DNS servers
|
||||||
|
* - DNS UDP queries to all DNS servers
|
||||||
|
*
|
||||||
|
* Currently unimplemented checks include:
|
||||||
|
* - report ARP/ND data about on-link neighbors
|
||||||
|
* - DNS TCP queries to all DNS servers
|
||||||
|
* - HTTP DIRECT and PROXY checks
|
||||||
|
* - port 443 blocking/TLS intercept checks
|
||||||
|
* - QUIC reachability checks
|
||||||
|
* - MTU checks
|
||||||
|
*
|
||||||
|
* The supplied timeout bounds the entire diagnostic process. Each specific
|
||||||
|
* check class must implement this upper bound on measurements in whichever
|
||||||
|
* manner is most appropriate and effective.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class NetworkDiagnostics {
|
||||||
|
private static final String TAG = "NetworkDiagnostics";
|
||||||
|
|
||||||
|
// For brevity elsewhere.
|
||||||
|
private static final long now() {
|
||||||
|
return SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values from RFC 1035 section 4.1.1, names from <arpa/nameser.h>.
|
||||||
|
// Should be a member of DnsUdpCheck, but "compiler says no".
|
||||||
|
public static enum DnsResponseCode { NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED };
|
||||||
|
|
||||||
|
private final Network mNetwork;
|
||||||
|
private final LinkProperties mLinkProperties;
|
||||||
|
private final Integer mInterfaceIndex;
|
||||||
|
|
||||||
|
private final long mTimeoutMs;
|
||||||
|
private final long mStartTime;
|
||||||
|
private final long mDeadlineTime;
|
||||||
|
|
||||||
|
// A counter, initialized to the total number of measurements,
|
||||||
|
// so callers can wait for completion.
|
||||||
|
private final CountDownLatch mCountDownLatch;
|
||||||
|
|
||||||
|
private class Measurement {
|
||||||
|
private static final String SUCCEEDED = "SUCCEEDED";
|
||||||
|
private static final String FAILED = "FAILED";
|
||||||
|
|
||||||
|
// TODO: Refactor to make these private for better encapsulation.
|
||||||
|
public String description = "";
|
||||||
|
public long startTime;
|
||||||
|
public long finishTime;
|
||||||
|
public String result = "";
|
||||||
|
public Thread thread;
|
||||||
|
|
||||||
|
public void recordSuccess(String msg) {
|
||||||
|
maybeFixupTimes();
|
||||||
|
if (mCountDownLatch != null) {
|
||||||
|
mCountDownLatch.countDown();
|
||||||
|
}
|
||||||
|
result = SUCCEEDED + ": " + msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordFailure(String msg) {
|
||||||
|
maybeFixupTimes();
|
||||||
|
if (mCountDownLatch != null) {
|
||||||
|
mCountDownLatch.countDown();
|
||||||
|
}
|
||||||
|
result = FAILED + ": " + msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeFixupTimes() {
|
||||||
|
// Allows the caller to just set success/failure and not worry
|
||||||
|
// about also setting the correct finishing time.
|
||||||
|
if (finishTime == 0) { finishTime = now(); }
|
||||||
|
|
||||||
|
// In cases where, for example, a failure has occurred before the
|
||||||
|
// measurement even began, fixup the start time to reflect as much.
|
||||||
|
if (startTime == 0) { startTime = finishTime; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description + ": " + result + " (" + (finishTime - startTime) + "ms)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
|
||||||
|
private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
|
||||||
|
private final String mDescription;
|
||||||
|
|
||||||
|
|
||||||
|
public NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs) {
|
||||||
|
mNetwork = network;
|
||||||
|
mLinkProperties = lp;
|
||||||
|
mInterfaceIndex = getInterfaceIndex(mLinkProperties.getInterfaceName());
|
||||||
|
mTimeoutMs = timeoutMs;
|
||||||
|
mStartTime = now();
|
||||||
|
mDeadlineTime = mStartTime + mTimeoutMs;
|
||||||
|
|
||||||
|
for (RouteInfo route : mLinkProperties.getRoutes()) {
|
||||||
|
if (route.hasGateway()) {
|
||||||
|
prepareIcmpMeasurement(route.getGateway());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
|
||||||
|
prepareIcmpMeasurement(nameserver);
|
||||||
|
prepareDnsMeasurement(nameserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
mCountDownLatch = new CountDownLatch(totalMeasurementCount());
|
||||||
|
|
||||||
|
startMeasurements();
|
||||||
|
|
||||||
|
mDescription = "ifaces{" + TextUtils.join(",", mLinkProperties.getAllInterfaceNames()) + "}"
|
||||||
|
+ " index{" + mInterfaceIndex + "}"
|
||||||
|
+ " network{" + mNetwork + "}"
|
||||||
|
+ " nethandle{" + mNetwork.getNetworkHandle() + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer getInterfaceIndex(String ifname) {
|
||||||
|
try {
|
||||||
|
NetworkInterface ni = NetworkInterface.getByName(ifname);
|
||||||
|
return ni.getIndex();
|
||||||
|
} catch (NullPointerException | SocketException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareIcmpMeasurement(InetAddress target) {
|
||||||
|
if (!mIcmpChecks.containsKey(target)) {
|
||||||
|
Measurement measurement = new Measurement();
|
||||||
|
measurement.thread = new Thread(new IcmpCheck(target, measurement));
|
||||||
|
mIcmpChecks.put(target, measurement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareDnsMeasurement(InetAddress target) {
|
||||||
|
if (!mDnsUdpChecks.containsKey(target)) {
|
||||||
|
Measurement measurement = new Measurement();
|
||||||
|
measurement.thread = new Thread(new DnsUdpCheck(target, measurement));
|
||||||
|
mDnsUdpChecks.put(target, measurement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int totalMeasurementCount() {
|
||||||
|
return mIcmpChecks.size() + mDnsUdpChecks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startMeasurements() {
|
||||||
|
for (Measurement measurement : mIcmpChecks.values()) {
|
||||||
|
measurement.thread.start();
|
||||||
|
}
|
||||||
|
for (Measurement measurement : mDnsUdpChecks.values()) {
|
||||||
|
measurement.thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitForMeasurements() {
|
||||||
|
try {
|
||||||
|
mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dump(IndentingPrintWriter pw) {
|
||||||
|
pw.println(TAG + ":" + mDescription);
|
||||||
|
final long unfinished = mCountDownLatch.getCount();
|
||||||
|
if (unfinished > 0) {
|
||||||
|
// This can't happen unless a caller forgets to call waitForMeasurements()
|
||||||
|
// or a measurement isn't implemented to correctly honor the timeout.
|
||||||
|
pw.println("WARNING: countdown wait incomplete: "
|
||||||
|
+ unfinished + " unfinished measurements");
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
|
||||||
|
if (entry.getKey() instanceof Inet4Address) {
|
||||||
|
pw.println(entry.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
|
||||||
|
if (entry.getKey() instanceof Inet6Address) {
|
||||||
|
pw.println(entry.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
|
||||||
|
if (entry.getKey() instanceof Inet4Address) {
|
||||||
|
pw.println(entry.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
|
||||||
|
if (entry.getKey() instanceof Inet6Address) {
|
||||||
|
pw.println(entry.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class SimpleSocketCheck implements Closeable {
|
||||||
|
protected final InetAddress mTarget;
|
||||||
|
protected final int mAddressFamily;
|
||||||
|
protected final Measurement mMeasurement;
|
||||||
|
protected FileDescriptor mFileDescriptor;
|
||||||
|
protected SocketAddress mSocketAddress;
|
||||||
|
|
||||||
|
protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
|
||||||
|
mMeasurement = measurement;
|
||||||
|
|
||||||
|
if (target instanceof Inet6Address) {
|
||||||
|
Inet6Address targetWithScopeId = null;
|
||||||
|
if (target.isLinkLocalAddress() && mInterfaceIndex != null) {
|
||||||
|
try {
|
||||||
|
targetWithScopeId = Inet6Address.getByAddress(
|
||||||
|
null, target.getAddress(), mInterfaceIndex);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
mMeasurement.recordFailure(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTarget = (targetWithScopeId != null) ? targetWithScopeId : target;
|
||||||
|
mAddressFamily = AF_INET6;
|
||||||
|
} else {
|
||||||
|
mTarget = target;
|
||||||
|
mAddressFamily = AF_INET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupSocket(
|
||||||
|
int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
|
||||||
|
throws ErrnoException, IOException {
|
||||||
|
mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
|
||||||
|
// Setting SNDTIMEO is purely for defensive purposes.
|
||||||
|
Os.setsockoptTimeval(mFileDescriptor,
|
||||||
|
SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
|
||||||
|
Os.setsockoptTimeval(mFileDescriptor,
|
||||||
|
SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout));
|
||||||
|
// TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability.
|
||||||
|
mNetwork.bindSocket(mFileDescriptor);
|
||||||
|
Os.connect(mFileDescriptor, mTarget, dstPort);
|
||||||
|
mSocketAddress = Os.getsockname(mFileDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getSocketAddressString() {
|
||||||
|
// The default toString() implementation is not the prettiest.
|
||||||
|
InetSocketAddress inetSockAddr = (InetSocketAddress) mSocketAddress;
|
||||||
|
InetAddress localAddr = inetSockAddr.getAddress();
|
||||||
|
return String.format(
|
||||||
|
(localAddr instanceof Inet6Address ? "[%s]:%d" : "%s:%d"),
|
||||||
|
localAddr.getHostAddress(), inetSockAddr.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
IoUtils.closeQuietly(mFileDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class IcmpCheck extends SimpleSocketCheck implements Runnable {
|
||||||
|
private static final int TIMEOUT_SEND = 100;
|
||||||
|
private static final int TIMEOUT_RECV = 300;
|
||||||
|
private static final int ICMPV4_ECHO_REQUEST = 8;
|
||||||
|
private static final int ICMPV6_ECHO_REQUEST = 128;
|
||||||
|
private static final int PACKET_BUFSIZE = 512;
|
||||||
|
private final int mProtocol;
|
||||||
|
private final int mIcmpType;
|
||||||
|
|
||||||
|
public IcmpCheck(InetAddress target, Measurement measurement) {
|
||||||
|
super(target, measurement);
|
||||||
|
|
||||||
|
if (mAddressFamily == AF_INET6) {
|
||||||
|
mProtocol = IPPROTO_ICMPV6;
|
||||||
|
mIcmpType = ICMPV6_ECHO_REQUEST;
|
||||||
|
mMeasurement.description = "ICMPv6";
|
||||||
|
} else {
|
||||||
|
mProtocol = IPPROTO_ICMP;
|
||||||
|
mIcmpType = ICMPV4_ECHO_REQUEST;
|
||||||
|
mMeasurement.description = "ICMPv4";
|
||||||
|
}
|
||||||
|
|
||||||
|
mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Check if this measurement has already failed during setup.
|
||||||
|
if (mMeasurement.finishTime > 0) {
|
||||||
|
// If the measurement failed during construction it didn't
|
||||||
|
// decrement the countdown latch; do so here.
|
||||||
|
mCountDownLatch.countDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
|
||||||
|
} catch (ErrnoException | IOException e) {
|
||||||
|
mMeasurement.recordFailure(e.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mMeasurement.description += " src{" + getSocketAddressString() + "}";
|
||||||
|
|
||||||
|
// Build a trivial ICMP packet.
|
||||||
|
final byte[] icmpPacket = {
|
||||||
|
(byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0 // ICMP header
|
||||||
|
};
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
mMeasurement.startTime = now();
|
||||||
|
while (now() < mDeadlineTime - (TIMEOUT_SEND + TIMEOUT_RECV)) {
|
||||||
|
count++;
|
||||||
|
icmpPacket[icmpPacket.length - 1] = (byte) count;
|
||||||
|
try {
|
||||||
|
Os.write(mFileDescriptor, icmpPacket, 0, icmpPacket.length);
|
||||||
|
} catch (ErrnoException | InterruptedIOException e) {
|
||||||
|
mMeasurement.recordFailure(e.toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
|
||||||
|
Os.read(mFileDescriptor, reply);
|
||||||
|
// TODO: send a few pings back to back to guesstimate packet loss.
|
||||||
|
mMeasurement.recordSuccess("1/" + count);
|
||||||
|
break;
|
||||||
|
} catch (ErrnoException | InterruptedIOException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mMeasurement.finishTime == 0) {
|
||||||
|
mMeasurement.recordFailure("0/" + count);
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class DnsUdpCheck extends SimpleSocketCheck implements Runnable {
|
||||||
|
private static final int TIMEOUT_SEND = 100;
|
||||||
|
private static final int TIMEOUT_RECV = 500;
|
||||||
|
private static final int DNS_SERVER_PORT = 53;
|
||||||
|
private static final int RR_TYPE_A = 1;
|
||||||
|
private static final int RR_TYPE_AAAA = 28;
|
||||||
|
private static final int PACKET_BUFSIZE = 512;
|
||||||
|
|
||||||
|
private final Random mRandom = new Random();
|
||||||
|
|
||||||
|
// Should be static, but the compiler mocks our puny, human attempts at reason.
|
||||||
|
private String responseCodeStr(int rcode) {
|
||||||
|
try {
|
||||||
|
return DnsResponseCode.values()[rcode].toString();
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
return String.valueOf(rcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int mQueryType;
|
||||||
|
|
||||||
|
public DnsUdpCheck(InetAddress target, Measurement measurement) {
|
||||||
|
super(target, measurement);
|
||||||
|
|
||||||
|
// TODO: Ideally, query the target for both types regardless of address family.
|
||||||
|
if (mAddressFamily == AF_INET6) {
|
||||||
|
mQueryType = RR_TYPE_AAAA;
|
||||||
|
} else {
|
||||||
|
mQueryType = RR_TYPE_A;
|
||||||
|
}
|
||||||
|
|
||||||
|
mMeasurement.description = "DNS UDP dst{" + mTarget.getHostAddress() + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Check if this measurement has already failed during setup.
|
||||||
|
if (mMeasurement.finishTime > 0) {
|
||||||
|
// If the measurement failed during construction it didn't
|
||||||
|
// decrement the countdown latch; do so here.
|
||||||
|
mCountDownLatch.countDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV, DNS_SERVER_PORT);
|
||||||
|
} catch (ErrnoException | IOException e) {
|
||||||
|
mMeasurement.recordFailure(e.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mMeasurement.description += " src{" + getSocketAddressString() + "}";
|
||||||
|
|
||||||
|
// This needs to be fixed length so it can be dropped into the pre-canned packet.
|
||||||
|
final String sixRandomDigits =
|
||||||
|
Integer.valueOf(mRandom.nextInt(900000) + 100000).toString();
|
||||||
|
mMeasurement.description += " qtype{" + mQueryType + "}"
|
||||||
|
+ " qname{" + sixRandomDigits + "-android-ds.metric.gstatic.com}";
|
||||||
|
|
||||||
|
// Build a trivial DNS packet.
|
||||||
|
final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
mMeasurement.startTime = now();
|
||||||
|
while (now() < mDeadlineTime - (TIMEOUT_RECV + TIMEOUT_RECV)) {
|
||||||
|
count++;
|
||||||
|
try {
|
||||||
|
Os.write(mFileDescriptor, dnsPacket, 0, dnsPacket.length);
|
||||||
|
} catch (ErrnoException | InterruptedIOException e) {
|
||||||
|
mMeasurement.recordFailure(e.toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
|
||||||
|
Os.read(mFileDescriptor, reply);
|
||||||
|
// TODO: more correct and detailed evaluation of the response,
|
||||||
|
// possibly adding the returned IP address(es) to the output.
|
||||||
|
final String rcodeStr = (reply.limit() > 3)
|
||||||
|
? " " + responseCodeStr((int) (reply.get(3)) & 0x0f)
|
||||||
|
: "";
|
||||||
|
mMeasurement.recordSuccess("1/" + count + rcodeStr);
|
||||||
|
break;
|
||||||
|
} catch (ErrnoException | InterruptedIOException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mMeasurement.finishTime == 0) {
|
||||||
|
mMeasurement.recordFailure("0/" + count);
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getDnsQueryPacket(String sixRandomDigits) {
|
||||||
|
byte[] rnd = sixRandomDigits.getBytes(StandardCharsets.US_ASCII);
|
||||||
|
return new byte[] {
|
||||||
|
(byte) mRandom.nextInt(), (byte) mRandom.nextInt(), // [0-1] query ID
|
||||||
|
1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD).
|
||||||
|
0, 1, // [4-5] QDCOUNT (number of queries)
|
||||||
|
0, 0, // [6-7] ANCOUNT (number of answers)
|
||||||
|
0, 0, // [8-9] NSCOUNT (number of name server records)
|
||||||
|
0, 0, // [10-11] ARCOUNT (number of additional records)
|
||||||
|
17, rnd[0], rnd[1], rnd[2], rnd[3], rnd[4], rnd[5],
|
||||||
|
'-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 's',
|
||||||
|
6, 'm', 'e', 't', 'r', 'i', 'c',
|
||||||
|
7, 'g', 's', 't', 'a', 't', 'i', 'c',
|
||||||
|
3, 'c', 'o', 'm',
|
||||||
|
0, // null terminator of FQDN (root TLD)
|
||||||
|
0, (byte) mQueryType, // QTYPE
|
||||||
|
0, 1 // QCLASS, set to 1 = IN (Internet)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user