• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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