1 // Copyright 2012 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import android.Manifest; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.content.IntentFilter; 11 import android.content.pm.PackageManager; 12 import android.net.ConnectivityManager; 13 import android.net.LinkProperties; 14 import android.net.Network; 15 import android.net.NetworkCapabilities; 16 import android.net.NetworkInfo; 17 import android.net.TrafficStats; 18 import android.net.TransportInfo; 19 import android.net.wifi.WifiInfo; 20 import android.net.wifi.WifiManager; 21 import android.os.Build; 22 import android.os.Build.VERSION_CODES; 23 import android.os.ParcelFileDescriptor; 24 import android.os.Process; 25 import android.telephony.TelephonyManager; 26 import android.util.Log; 27 28 import androidx.annotation.RequiresApi; 29 30 import org.jni_zero.CalledByNative; 31 import org.jni_zero.CalledByNativeForTesting; 32 import org.jni_zero.CalledByNativeUnchecked; 33 34 import org.chromium.base.ApiCompatibilityUtils; 35 import org.chromium.base.ContextUtils; 36 import org.chromium.base.ResettersForTesting; 37 import org.chromium.base.compat.ApiHelperForM; 38 import org.chromium.base.compat.ApiHelperForN; 39 import org.chromium.base.compat.ApiHelperForP; 40 import org.chromium.base.compat.ApiHelperForQ; 41 42 import java.io.FileDescriptor; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.OutputStream; 46 import java.lang.reflect.InvocationTargetException; 47 import java.lang.reflect.Method; 48 import java.net.InetAddress; 49 import java.net.NetworkInterface; 50 import java.net.Socket; 51 import java.net.SocketAddress; 52 import java.net.SocketException; 53 import java.net.SocketImpl; 54 import java.net.URLConnection; 55 import java.security.KeyStoreException; 56 import java.security.NoSuchAlgorithmException; 57 import java.security.cert.CertificateException; 58 import java.util.Enumeration; 59 import java.util.List; 60 61 /** This class implements net utilities required by the net component. */ 62 class AndroidNetworkLibrary { 63 private static final String TAG = "AndroidNetworkLibrary"; 64 65 // Cached value indicating if app has ACCESS_NETWORK_STATE permission. 66 private static Boolean sHaveAccessNetworkState; 67 // Cached value indicating if app has ACCESS_WIFI_STATE permission. 68 private static Boolean sHaveAccessWifiState; 69 70 /** 71 * @return the mime type (if any) that is associated with the file 72 * extension. Returns null if no corresponding mime type exists. 73 */ 74 @CalledByNative getMimeTypeFromExtension(String extension)75 public static String getMimeTypeFromExtension(String extension) { 76 return URLConnection.guessContentTypeFromName("foo." + extension); 77 } 78 79 /** 80 * @return true if it can determine that only loopback addresses are 81 * configured. i.e. if only 127.0.0.1 and ::1 are routable. Also 82 * returns false if it cannot determine this. 83 */ 84 @CalledByNative haveOnlyLoopbackAddresses()85 public static boolean haveOnlyLoopbackAddresses() { 86 Enumeration<NetworkInterface> list = null; 87 try { 88 list = NetworkInterface.getNetworkInterfaces(); 89 if (list == null) return false; 90 } catch (Exception e) { 91 Log.w(TAG, "could not get network interfaces: " + e); 92 return false; 93 } 94 95 while (list.hasMoreElements()) { 96 NetworkInterface netIf = list.nextElement(); 97 try { 98 if (netIf.isUp() && !netIf.isLoopback()) return false; 99 } catch (SocketException e) { 100 continue; 101 } 102 } 103 return true; 104 } 105 106 /** 107 * Validate the server's certificate chain is trusted. Note that the caller 108 * must still verify the name matches that of the leaf certificate. 109 * 110 * @param certChain The ASN.1 DER encoded bytes for certificates. 111 * @param authType The key exchange algorithm name (e.g. RSA). 112 * @param host The hostname of the server. 113 * @return Android certificate verification result code. 114 */ 115 @CalledByNative verifyServerCertificates( byte[][] certChain, String authType, String host)116 public static AndroidCertVerifyResult verifyServerCertificates( 117 byte[][] certChain, String authType, String host) { 118 try { 119 return X509Util.verifyServerCertificates(certChain, authType, host); 120 } catch (KeyStoreException e) { 121 return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED); 122 } catch (NoSuchAlgorithmException e) { 123 return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED); 124 } catch (IllegalArgumentException e) { 125 return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED); 126 } 127 } 128 129 /** 130 * Get the list of user-added roots. 131 * 132 * @return DER-encoded list of user-added roots. 133 */ 134 @CalledByNative getUserAddedRoots()135 public static byte[][] getUserAddedRoots() { 136 return X509Util.getUserAddedRoots(); 137 } 138 139 /** 140 * Adds a test root certificate to the local trust store. 141 * @param rootCert DER encoded bytes of the certificate. 142 */ 143 @CalledByNativeUnchecked addTestRootCertificate(byte[] rootCert)144 public static void addTestRootCertificate(byte[] rootCert) 145 throws CertificateException, KeyStoreException, NoSuchAlgorithmException { 146 X509Util.addTestRootCertificate(rootCert); 147 } 148 149 /** 150 * Removes all test root certificates added by |addTestRootCertificate| calls from the local 151 * trust store. 152 */ 153 @CalledByNativeUnchecked clearTestRootCertificates()154 public static void clearTestRootCertificates() 155 throws NoSuchAlgorithmException, CertificateException, KeyStoreException { 156 X509Util.clearTestRootCertificates(); 157 } 158 159 /** 160 * Returns the MCC+MNC (mobile country code + mobile network code) as 161 * the numeric name of the current registered operator. This function 162 * potentially blocks the thread, so use with care. 163 */ 164 @CalledByNative getNetworkOperator()165 private static String getNetworkOperator() { 166 TelephonyManager telephonyManager = 167 (TelephonyManager) 168 ContextUtils.getApplicationContext() 169 .getSystemService(Context.TELEPHONY_SERVICE); 170 if (telephonyManager == null) return ""; 171 return telephonyManager.getNetworkOperator(); 172 } 173 174 /** 175 * Indicates whether the device is roaming on the currently active network. When true, it 176 * suggests that use of data may incur extra costs. 177 */ 178 @CalledByNative getIsRoaming()179 private static boolean getIsRoaming() { 180 ConnectivityManager connectivityManager = 181 (ConnectivityManager) 182 ContextUtils.getApplicationContext() 183 .getSystemService(Context.CONNECTIVITY_SERVICE); 184 NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 185 if (networkInfo == null) return false; // No active network. 186 return networkInfo.isRoaming(); 187 } 188 189 /** 190 * Returns true if the system's captive portal probe was blocked for the current default data 191 * network. The method will return false if the captive portal probe was not blocked, the login 192 * process to the captive portal has been successfully completed, or if the captive portal 193 * status can't be determined. Requires ACCESS_NETWORK_STATE permission. Only available on 194 * Android Marshmallow and later versions. Returns false on earlier versions. 195 */ 196 @RequiresApi(Build.VERSION_CODES.M) 197 @CalledByNative getIsCaptivePortal()198 private static boolean getIsCaptivePortal() { 199 // NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL is only available on Marshmallow and 200 // later versions. 201 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false; 202 ConnectivityManager connectivityManager = 203 (ConnectivityManager) 204 ContextUtils.getApplicationContext() 205 .getSystemService(Context.CONNECTIVITY_SERVICE); 206 if (connectivityManager == null) return false; 207 208 Network network = ApiHelperForM.getActiveNetwork(connectivityManager); 209 if (network == null) return false; 210 211 NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); 212 return capabilities != null 213 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); 214 } 215 216 /** 217 * Helper function that gets the WifiInfo of the WiFi network. If we have permission to access 218 * to the WiFi state, then we use either {@link NetworkCapabilities} for Android S+ or {@link 219 * WifiManager} for earlier versions. Otherwise, we try to get the WifiInfo via broadcast (Note 220 * that this approach does not work on Android P and above). 221 */ getWifiInfo()222 private static WifiInfo getWifiInfo() { 223 if (haveAccessWifiState()) { 224 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 225 // On Android S+, need to use NetworkCapabilities to get the WifiInfo. 226 ConnectivityManager connectivityManager = 227 (ConnectivityManager) 228 ContextUtils.getApplicationContext() 229 .getSystemService(Context.CONNECTIVITY_SERVICE); 230 Network[] allNetworks = connectivityManager.getAllNetworks(); 231 // TODO(curranmax): This only gets the WifiInfo of the first WiFi network that is 232 // iterated over. On Android S+ there may be up to two WiFi networks. 233 // https://crbug.com/1181393 234 for (Network network : allNetworks) { 235 NetworkCapabilities networkCapabilities = 236 connectivityManager.getNetworkCapabilities(network); 237 if (networkCapabilities != null 238 && networkCapabilities.hasTransport( 239 NetworkCapabilities.TRANSPORT_WIFI)) { 240 TransportInfo transportInfo = 241 ApiHelperForQ.getTransportInfo(networkCapabilities); 242 if (transportInfo != null && transportInfo instanceof WifiInfo) { 243 return (WifiInfo) transportInfo; 244 } 245 } 246 } 247 return null; 248 } else { 249 // Get WifiInfo via WifiManager. This method is deprecated starting with Android S. 250 WifiManager wifiManager = 251 (WifiManager) 252 ContextUtils.getApplicationContext() 253 .getSystemService(Context.WIFI_SERVICE); 254 return wifiManager.getConnectionInfo(); 255 } 256 } else { 257 // If we do not have permission to access the WiFi state, then try to get the WifiInfo 258 // through broadcast. Note that this approach does not work on Android P+. 259 final Intent intent = 260 ContextUtils.registerProtectedBroadcastReceiver( 261 ContextUtils.getApplicationContext(), 262 null, 263 new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); 264 if (intent != null) { 265 return intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); 266 } 267 return null; 268 } 269 } 270 271 /** 272 * Gets the SSID of the currently associated WiFi access point if there is one, and it is 273 * available. SSID may not be available if the app does not have permissions to access it. On 274 * Android M+, the app accessing SSID needs to have ACCESS_COARSE_LOCATION or 275 * ACCESS_FINE_LOCATION. If there is no WiFi access point or its SSID is unavailable, an empty 276 * string is returned. 277 */ 278 @CalledByNative getWifiSSID()279 public static String getWifiSSID() { 280 WifiInfo wifiInfo = getWifiInfo(); 281 282 if (wifiInfo != null) { 283 final String ssid = wifiInfo.getSSID(); 284 // On Android M+, the platform APIs may return "<unknown ssid>" as the SSID if the 285 // app does not have sufficient permissions. In that case, return an empty string. 286 if (ssid != null && !ssid.equals("<unknown ssid>")) { 287 return ssid; 288 } 289 } 290 return ""; 291 } 292 293 @CalledByNativeForTesting setWifiEnabledForTesting(boolean enabled)294 public static void setWifiEnabledForTesting(boolean enabled) { 295 WifiManager wifiManager = 296 (WifiManager) 297 ContextUtils.getApplicationContext().getSystemService(Context.WIFI_SERVICE); 298 var oldValue = wifiManager.isWifiEnabled(); 299 wifiManager.setWifiEnabled(enabled); 300 ResettersForTesting.register(() -> wifiManager.setWifiEnabled(oldValue)); 301 } 302 303 /** 304 * Gets the signal strength from the currently associated WiFi access point if there is one, and 305 * it is available. Signal strength may not be available if the app does not have permissions to 306 * access it. 307 * @return -1 if value is unavailable, otherwise, a value between 0 and {@code countBuckets-1} 308 * (both inclusive). 309 */ 310 @CalledByNative getWifiSignalLevel(int countBuckets)311 public static int getWifiSignalLevel(int countBuckets) { 312 // Some devices unexpectedly have a null context. See https://crbug.com/1019974. 313 if (ContextUtils.getApplicationContext() == null) { 314 return -1; 315 } 316 if (ContextUtils.getApplicationContext().getContentResolver() == null) { 317 return -1; 318 } 319 320 int rssi; 321 // On Android Q and above, the WifiInfo cannot be obtained through broadcast. See 322 // https://crbug.com/1026686. 323 if (haveAccessWifiState()) { 324 WifiInfo wifiInfo = getWifiInfo(); 325 if (wifiInfo == null) { 326 return -1; 327 } 328 rssi = wifiInfo.getRssi(); 329 } else { 330 Intent intent = null; 331 try { 332 intent = 333 ContextUtils.registerProtectedBroadcastReceiver( 334 ContextUtils.getApplicationContext(), 335 null, 336 new IntentFilter(WifiManager.RSSI_CHANGED_ACTION)); 337 } catch (IllegalArgumentException e) { 338 // Some devices unexpectedly throw IllegalArgumentException when registering 339 // the broadcast receiver. See https://crbug.com/984179. 340 return -1; 341 } 342 if (intent == null) { 343 return -1; 344 } 345 rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, Integer.MIN_VALUE); 346 } 347 348 if (rssi == Integer.MIN_VALUE) { 349 return -1; 350 } 351 352 final int signalLevel = WifiManager.calculateSignalLevel(rssi, countBuckets); 353 if (signalLevel < 0 || signalLevel >= countBuckets) { 354 return -1; 355 } 356 357 return signalLevel; 358 } 359 360 public static class NetworkSecurityPolicyProxy { 361 private static NetworkSecurityPolicyProxy sInstance = new NetworkSecurityPolicyProxy(); 362 getInstance()363 public static NetworkSecurityPolicyProxy getInstance() { 364 return sInstance; 365 } 366 setInstanceForTesting( NetworkSecurityPolicyProxy networkSecurityPolicyProxy)367 public static void setInstanceForTesting( 368 NetworkSecurityPolicyProxy networkSecurityPolicyProxy) { 369 var oldValue = sInstance; 370 sInstance = networkSecurityPolicyProxy; 371 ResettersForTesting.register(() -> sInstance = oldValue); 372 } 373 374 @RequiresApi(Build.VERSION_CODES.N) isCleartextTrafficPermitted(String host)375 public boolean isCleartextTrafficPermitted(String host) { 376 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 377 // No per-host configuration before N. 378 return isCleartextTrafficPermitted(); 379 } 380 return ApiHelperForN.isCleartextTrafficPermitted(host); 381 } 382 383 @RequiresApi(Build.VERSION_CODES.M) isCleartextTrafficPermitted()384 public boolean isCleartextTrafficPermitted() { 385 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 386 // Always true before M. 387 return true; 388 } 389 return ApiHelperForM.isCleartextTrafficPermitted(); 390 } 391 } 392 393 /** Returns true if cleartext traffic to |host| is allowed by the current app. */ 394 @CalledByNative isCleartextPermitted(String host)395 private static boolean isCleartextPermitted(String host) { 396 try { 397 return NetworkSecurityPolicyProxy.getInstance().isCleartextTrafficPermitted(host); 398 } catch (IllegalArgumentException e) { 399 return NetworkSecurityPolicyProxy.getInstance().isCleartextTrafficPermitted(); 400 } 401 } 402 haveAccessNetworkState()403 private static boolean haveAccessNetworkState() { 404 // This could be racy if called on multiple threads, but races will 405 // end in the same result so it's not a problem. 406 if (sHaveAccessNetworkState == null) { 407 sHaveAccessNetworkState = 408 Boolean.valueOf( 409 ApiCompatibilityUtils.checkPermission( 410 ContextUtils.getApplicationContext(), 411 Manifest.permission.ACCESS_NETWORK_STATE, 412 Process.myPid(), 413 Process.myUid()) 414 == PackageManager.PERMISSION_GRANTED); 415 } 416 return sHaveAccessNetworkState; 417 } 418 haveAccessWifiState()419 private static boolean haveAccessWifiState() { 420 // This could be racy if called on multiple threads, but races will 421 // end in the same result so it's not a problem. 422 if (sHaveAccessWifiState == null) { 423 sHaveAccessWifiState = 424 Boolean.valueOf( 425 ApiCompatibilityUtils.checkPermission( 426 ContextUtils.getApplicationContext(), 427 Manifest.permission.ACCESS_WIFI_STATE, 428 Process.myPid(), 429 Process.myUid()) 430 == PackageManager.PERMISSION_GRANTED); 431 } 432 return sHaveAccessWifiState; 433 } 434 435 /** 436 * Returns object representing the DNS configuration for the provided 437 * network handle. 438 */ 439 @RequiresApi(Build.VERSION_CODES.P) 440 @CalledByNative getDnsStatusForNetwork(long networkHandle)441 public static DnsStatus getDnsStatusForNetwork(long networkHandle) { 442 // In case the network handle is invalid don't crash, instead return an empty DnsStatus and 443 // let native code handle that. 444 try { 445 Network network = Network.fromNetworkHandle(networkHandle); 446 return getDnsStatus(network); 447 } catch (IllegalArgumentException e) { 448 return null; 449 } 450 } 451 452 /** 453 * Returns object representing the DNS configuration for the current 454 * default network. 455 */ 456 @RequiresApi(Build.VERSION_CODES.M) 457 @CalledByNative getCurrentDnsStatus()458 public static DnsStatus getCurrentDnsStatus() { 459 return getDnsStatus(null); 460 } 461 462 /** 463 * Returns object representing the DNS configuration for the provided 464 * network. If |network| is null, uses the active network. 465 */ 466 @RequiresApi(Build.VERSION_CODES.M) getDnsStatus(Network network)467 public static DnsStatus getDnsStatus(Network network) { 468 if (!haveAccessNetworkState()) { 469 return null; 470 } 471 ConnectivityManager connectivityManager = 472 (ConnectivityManager) 473 ContextUtils.getApplicationContext() 474 .getSystemService(Context.CONNECTIVITY_SERVICE); 475 if (connectivityManager == null) { 476 return null; 477 } 478 if (network == null) { 479 network = ApiHelperForM.getActiveNetwork(connectivityManager); 480 } 481 if (network == null) { 482 return null; 483 } 484 LinkProperties linkProperties; 485 try { 486 linkProperties = connectivityManager.getLinkProperties(network); 487 } catch (RuntimeException e) { 488 return null; 489 } 490 if (linkProperties == null) { 491 return null; 492 } 493 List<InetAddress> dnsServersList = linkProperties.getDnsServers(); 494 String searchDomains = linkProperties.getDomains(); 495 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 496 return new DnsStatus( 497 dnsServersList, 498 ApiHelperForP.isPrivateDnsActive(linkProperties), 499 ApiHelperForP.getPrivateDnsServerName(linkProperties), 500 searchDomains); 501 } else { 502 return new DnsStatus(dnsServersList, false, "", searchDomains); 503 } 504 } 505 506 /** Reports a connectivity issue with the device's current default network. */ 507 @RequiresApi(Build.VERSION_CODES.M) 508 @CalledByNative reportBadDefaultNetwork()509 private static boolean reportBadDefaultNetwork() { 510 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false; 511 ConnectivityManager connectivityManager = 512 (ConnectivityManager) 513 ContextUtils.getApplicationContext() 514 .getSystemService(Context.CONNECTIVITY_SERVICE); 515 if (connectivityManager == null) return false; 516 517 ApiHelperForM.reportNetworkConnectivity(connectivityManager, null, false); 518 return true; 519 } 520 521 /** 522 * Class to wrap FileDescriptor.setInt$() which is hidden and so must be accessed via 523 * reflection. 524 */ 525 private static class SetFileDescriptor { 526 // Reference to FileDescriptor.setInt$(int fd). 527 private static final Method sFileDescriptorSetInt; 528 529 // Get reference to FileDescriptor.setInt$(int fd) via reflection. 530 static { 531 try { 532 sFileDescriptorSetInt = FileDescriptor.class.getMethod("setInt$", Integer.TYPE); 533 } catch (NoSuchMethodException | SecurityException e) { 534 throw new RuntimeException("Unable to get FileDescriptor.setInt$", e); 535 } 536 } 537 538 /** Creates a FileDescriptor and calls FileDescriptor.setInt$(int fd) on it. */ createWithFd(int fd)539 public static FileDescriptor createWithFd(int fd) { 540 try { 541 FileDescriptor fileDescriptor = new FileDescriptor(); 542 sFileDescriptorSetInt.invoke(fileDescriptor, fd); 543 return fileDescriptor; 544 } catch (IllegalAccessException e) { 545 throw new RuntimeException("FileDescriptor.setInt$() failed", e); 546 } catch (InvocationTargetException e) { 547 throw new RuntimeException("FileDescriptor.setInt$() failed", e); 548 } 549 } 550 } 551 552 /** 553 * This class provides an implementation of {@link java.net.Socket} that serves only as a 554 * conduit to pass a file descriptor integer to {@link android.net.TrafficStats#tagSocket} 555 * when called by {@link #tagSocket}. This class does not take ownership of the file descriptor, 556 * so calling {@link #close} will not actually close the file descriptor. 557 */ 558 private static class SocketFd extends Socket { 559 /** 560 * This class provides an implementation of {@link java.net.SocketImpl} that serves only as 561 * a conduit to pass a file descriptor integer to {@link android.net.TrafficStats#tagSocket} 562 * when called by {@link #tagSocket}. This class does not take ownership of the file 563 * descriptor, so calling {@link #close} will not actually close the file descriptor. 564 */ 565 private static class SocketImplFd extends SocketImpl { 566 /** 567 * Create a {@link java.net.SocketImpl} that sets {@code fd} as the underlying file 568 * descriptor. Does not take ownership of the file descriptor, so calling {@link #close} 569 * will not actually close the file descriptor. 570 */ SocketImplFd(FileDescriptor fd)571 SocketImplFd(FileDescriptor fd) { 572 this.fd = fd; 573 } 574 575 @Override accept(SocketImpl s)576 protected void accept(SocketImpl s) { 577 throw new RuntimeException("accept not implemented"); 578 } 579 580 @Override available()581 protected int available() { 582 throw new RuntimeException("accept not implemented"); 583 } 584 585 @Override bind(InetAddress host, int port)586 protected void bind(InetAddress host, int port) { 587 throw new RuntimeException("accept not implemented"); 588 } 589 590 @Override close()591 protected void close() {} 592 593 @Override connect(InetAddress address, int port)594 protected void connect(InetAddress address, int port) { 595 throw new RuntimeException("connect not implemented"); 596 } 597 598 @Override connect(SocketAddress address, int timeout)599 protected void connect(SocketAddress address, int timeout) { 600 throw new RuntimeException("connect not implemented"); 601 } 602 603 @Override connect(String host, int port)604 protected void connect(String host, int port) { 605 throw new RuntimeException("connect not implemented"); 606 } 607 608 @Override create(boolean stream)609 protected void create(boolean stream) {} 610 611 @Override getInputStream()612 protected InputStream getInputStream() { 613 throw new RuntimeException("getInputStream not implemented"); 614 } 615 616 @Override getOutputStream()617 protected OutputStream getOutputStream() { 618 throw new RuntimeException("getOutputStream not implemented"); 619 } 620 621 @Override listen(int backlog)622 protected void listen(int backlog) { 623 throw new RuntimeException("listen not implemented"); 624 } 625 626 @Override sendUrgentData(int data)627 protected void sendUrgentData(int data) { 628 throw new RuntimeException("sendUrgentData not implemented"); 629 } 630 631 @Override getOption(int optID)632 public Object getOption(int optID) { 633 throw new RuntimeException("getOption not implemented"); 634 } 635 636 @Override setOption(int optID, Object value)637 public void setOption(int optID, Object value) { 638 throw new RuntimeException("setOption not implemented"); 639 } 640 } 641 642 /** 643 * Create a {@link java.net.Socket} that sets {@code fd} as the underlying file 644 * descriptor. Does not take ownership of the file descriptor, so calling {@link #close} 645 * will not actually close the file descriptor. 646 */ SocketFd(FileDescriptor fd)647 SocketFd(FileDescriptor fd) throws IOException { 648 super(new SocketImplFd(fd)); 649 } 650 } 651 652 /** 653 * Tag socket referenced by {@code ifd} with {@code tag} for UID {@code uid}. 654 * 655 * Assumes thread UID tag isn't set upon entry, and ensures thread UID tag isn't set upon exit. 656 * Unfortunately there is no TrafficStatis.getThreadStatsUid(). 657 */ 658 @CalledByNative tagSocket(int ifd, int uid, int tag)659 private static void tagSocket(int ifd, int uid, int tag) throws IOException { 660 // Set thread tags. 661 int oldTag = TrafficStats.getThreadStatsTag(); 662 if (tag != oldTag) { 663 TrafficStats.setThreadStatsTag(tag); 664 } 665 if (uid != TrafficStatsUid.UNSET) { 666 ThreadStatsUid.set(uid); 667 } 668 669 // Apply thread tags to socket. 670 671 // First, convert integer file descriptor (ifd) to FileDescriptor. 672 final ParcelFileDescriptor pfd; 673 final FileDescriptor fd; 674 // The only supported way to generate a FileDescriptor from an integer file 675 // descriptor is via ParcelFileDescriptor.adoptFd(). Unfortunately prior to Android 676 // Marshmallow ParcelFileDescriptor.detachFd() didn't actually detach from the 677 // FileDescriptor, so use reflection to set {@code fd} into the FileDescriptor for 678 // versions prior to Marshmallow. Here's the fix that went into Marshmallow: 679 // https://android.googlesource.com/platform/frameworks/base/+/b30ad6f 680 if (Build.VERSION.SDK_INT < VERSION_CODES.M) { 681 pfd = null; 682 fd = SetFileDescriptor.createWithFd(ifd); 683 } else { 684 pfd = ParcelFileDescriptor.adoptFd(ifd); 685 fd = pfd.getFileDescriptor(); 686 } 687 // Second, convert FileDescriptor to Socket. 688 Socket s = new SocketFd(fd); 689 // Third, tag the Socket. 690 TrafficStats.tagSocket(s); 691 s.close(); // No-op but always good to close() Closeables. 692 // Have ParcelFileDescriptor relinquish ownership of the file descriptor. 693 if (pfd != null) { 694 pfd.detachFd(); 695 } 696 697 // Restore prior thread tags. 698 if (tag != oldTag) { 699 TrafficStats.setThreadStatsTag(oldTag); 700 } 701 if (uid != TrafficStatsUid.UNSET) { 702 ThreadStatsUid.clear(); 703 } 704 } 705 } 706