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