Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 21 | import android.net.LinkAddress; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 22 | import android.net.LinkProperties; |
| 23 | import android.net.Network; |
Lorenzo Colitti | 87cfc70 | 2015-07-27 16:35:33 +0900 | [diff] [blame] | 24 | import android.net.NetworkUtils; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 25 | import android.net.RouteInfo; |
Jeff Sharkey | 619a511 | 2017-01-19 11:55:54 -0700 | [diff] [blame] | 26 | import android.net.TrafficStats; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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; |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 32 | import android.util.Pair; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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; |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 53 | import java.util.ArrayList; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 54 | import java.util.HashMap; |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 55 | import java.util.List; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Lorenzo Colitti | 87cfc70 | 2015-07-27 16:35:33 +0900 | [diff] [blame] | 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 | |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 113 | public class Measurement { |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 114 | private static final String SUCCEEDED = "SUCCEEDED"; |
| 115 | private static final String FAILED = "FAILED"; |
| 116 | |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 117 | private boolean succeeded; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 118 | |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 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) { |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 129 | maybeFixupTimes(); |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 130 | succeeded = true; |
Erik Kline | ed1b459 | 2015-06-24 20:02:20 +0900 | [diff] [blame] | 131 | result = SUCCEEDED + ": " + msg; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 132 | if (mCountDownLatch != null) { |
| 133 | mCountDownLatch.countDown(); |
| 134 | } |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 135 | } |
| 136 | |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 137 | void recordFailure(String msg) { |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 138 | maybeFixupTimes(); |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 139 | succeeded = false; |
Erik Kline | ed1b459 | 2015-06-24 20:02:20 +0900 | [diff] [blame] | 140 | result = FAILED + ": " + msg; |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 141 | if (mCountDownLatch != null) { |
| 142 | mCountDownLatch.countDown(); |
| 143 | } |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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<>(); |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 163 | private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks = |
| 164 | new HashMap<>(); |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Lorenzo Colitti | 87cfc70 | 2015-07-27 16:35:33 +0900 | [diff] [blame] | 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 | |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 192 | for (RouteInfo route : mLinkProperties.getRoutes()) { |
| 193 | if (route.hasGateway()) { |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 194 | InetAddress gateway = route.getGateway(); |
| 195 | prepareIcmpMeasurement(gateway); |
| 196 | if (route.isIPv6Default()) { |
| 197 | prepareExplicitSourceIcmpMeasurements(gateway); |
| 198 | } |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 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 | |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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() { |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 256 | return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size(); |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 257 | } |
| 258 | |
| 259 | private void startMeasurements() { |
| 260 | for (Measurement measurement : mIcmpChecks.values()) { |
| 261 | measurement.thread.start(); |
| 262 | } |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 263 | for (Measurement measurement : mExplicitSourceIcmpChecks.values()) { |
| 264 | measurement.thread.start(); |
| 265 | } |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 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 | |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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(); |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 333 | |
| 334 | String prefix; |
| 335 | for (Measurement m : getMeasurements()) { |
| 336 | prefix = m.checkSucceeded() ? "." : "F"; |
| 337 | pw.println(prefix + " " + m.toString()); |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 338 | } |
Erik Kline | 106cdf6 | 2016-02-03 14:12:23 +0900 | [diff] [blame] | 339 | |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 340 | pw.decreaseIndent(); |
| 341 | } |
| 342 | |
| 343 | |
| 344 | private class SimpleSocketCheck implements Closeable { |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 345 | protected final InetAddress mSource; // Usually null. |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 346 | protected final InetAddress mTarget; |
| 347 | protected final int mAddressFamily; |
| 348 | protected final Measurement mMeasurement; |
| 349 | protected FileDescriptor mFileDescriptor; |
| 350 | protected SocketAddress mSocketAddress; |
| 351 | |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 352 | protected SimpleSocketCheck( |
| 353 | InetAddress source, InetAddress target, Measurement measurement) { |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | } |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 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); |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 380 | } |
| 381 | |
| 382 | protected void setupSocket( |
| 383 | int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort) |
| 384 | throws ErrnoException, IOException { |
Jeff Sharkey | 619a511 | 2017-01-19 11:55:54 -0700 | [diff] [blame] | 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 | } |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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); |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 398 | if (mSource != null) { |
| 399 | Os.bind(mFileDescriptor, mSource, 0); |
| 400 | } |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 430 | public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) { |
| 431 | super(source, target, measurement); |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | |
Lorenzo Colitti | 29f6666 | 2015-09-09 17:37:12 +0900 | [diff] [blame] | 446 | public IcmpCheck(InetAddress target, Measurement measurement) { |
| 447 | this(null, target, measurement); |
| 448 | } |
| 449 | |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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. |
Narayan Kamath | a09b4d2 | 2016-04-15 18:32:45 +0100 | [diff] [blame] | 557 | final String sixRandomDigits = String.valueOf(mRandom.nextInt(900000) + 100000); |
Erik Kline | 379747a | 2015-06-05 17:47:34 +0900 | [diff] [blame] | 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 | } |