• 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 
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