• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server;
18 
19 import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
20 import static android.net.BpfNetMapsConstants.COOKIE_TAG_MAP_PATH;
21 import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
22 import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED;
23 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
24 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
25 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
26 import static android.net.BpfNetMapsConstants.IIF_MATCH;
27 import static android.net.BpfNetMapsConstants.INGRESS_DISCARD_MAP_PATH;
28 import static android.net.BpfNetMapsConstants.LOCAL_NET_ACCESS_MAP_PATH;
29 import static android.net.BpfNetMapsConstants.LOCAL_NET_BLOCKED_UID_MAP_PATH;
30 import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
31 import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
32 import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
33 import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
34 import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
35 import static android.net.BpfNetMapsUtils.isFirewallAllowList;
36 import static android.net.BpfNetMapsUtils.matchToString;
37 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
38 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
39 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
40 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
41 import static android.system.OsConstants.EINVAL;
42 import static android.system.OsConstants.ENODEV;
43 import static android.system.OsConstants.ENOENT;
44 import static android.system.OsConstants.EOPNOTSUPP;
45 
46 import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
47 import static com.android.server.connectivity.NetworkPermissions.PERMISSION_NONE;
48 import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_INTERNET;
49 import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UNINSTALLED;
50 import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS;
51 
52 import android.annotation.NonNull;
53 import android.annotation.Nullable;
54 import android.app.StatsManager;
55 import android.content.Context;
56 import android.net.BpfNetMapsUtils;
57 import android.net.INetd;
58 import android.net.UidOwnerValue;
59 import android.os.Build;
60 import android.os.RemoteException;
61 import android.os.ServiceSpecificException;
62 import android.os.UserHandle;
63 import android.system.ErrnoException;
64 import android.system.Os;
65 import android.util.ArraySet;
66 import android.util.IndentingPrintWriter;
67 import android.util.Log;
68 import android.util.Pair;
69 import android.util.StatsEvent;
70 
71 import androidx.annotation.RequiresApi;
72 
73 import com.android.internal.annotations.VisibleForTesting;
74 import com.android.modules.utils.BackgroundThread;
75 import com.android.modules.utils.build.SdkLevel;
76 import com.android.net.module.util.BpfDump;
77 import com.android.net.module.util.BpfMap;
78 import com.android.net.module.util.IBpfMap;
79 import com.android.net.module.util.SingleWriterBpfMap;
80 import com.android.net.module.util.Struct;
81 import com.android.net.module.util.Struct.Bool;
82 import com.android.net.module.util.Struct.S32;
83 import com.android.net.module.util.Struct.U32;
84 import com.android.net.module.util.Struct.U8;
85 import com.android.net.module.util.bpf.CookieTagMapKey;
86 import com.android.net.module.util.bpf.CookieTagMapValue;
87 import com.android.net.module.util.bpf.IngressDiscardKey;
88 import com.android.net.module.util.bpf.IngressDiscardValue;
89 import com.android.net.module.util.bpf.LocalNetAccessKey;
90 import com.android.server.connectivity.InterfaceTracker;
91 
92 import java.io.FileDescriptor;
93 import java.io.IOException;
94 import java.net.InetAddress;
95 import java.util.Arrays;
96 import java.util.List;
97 import java.util.Objects;
98 import java.util.Set;
99 import java.util.StringJoiner;
100 
101 /**
102  * BpfNetMaps is responsible for providing traffic controller relevant functionality.
103  *
104  * {@hide}
105  */
106 public class BpfNetMaps {
107     static {
108         if (SdkLevel.isAtLeastT()) {
109             System.loadLibrary("service-connectivity");
110         }
111     }
112 
113     private static final String TAG = "BpfNetMaps";
114     private final INetd mNetd;
115     private final Dependencies mDeps;
116     // Use legacy netd for releases before T.
117     private static boolean sInitialized = false;
118 
119     // Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
120     // This entry is not accessed by others.
121     // BpfNetMaps acquires this lock while sequence of read, modify, and write.
122     private static final Object sUidRulesConfigBpfMapLock = new Object();
123 
124     // Lock for sConfigurationMap entry for CURRENT_STATS_MAP_CONFIGURATION_KEY.
125     // BpfNetMaps acquires this lock while sequence of read, modify, and write.
126     // BpfNetMaps is an only writer of this entry.
127     private static final Object sCurrentStatsMapConfigLock = new Object();
128 
129     private static final long UID_RULES_DEFAULT_CONFIGURATION = 0;
130     private static final long STATS_SELECT_MAP_A = 0;
131     private static final long STATS_SELECT_MAP_B = 1;
132 
133     private static IBpfMap<S32, U32> sConfigurationMap = null;
134     // BpfMap for UID_OWNER_MAP_PATH. This map is not accessed by others.
135     private static IBpfMap<S32, UidOwnerValue> sUidOwnerMap = null;
136     private static IBpfMap<S32, U8> sUidPermissionMap = null;
137     private static IBpfMap<CookieTagMapKey, CookieTagMapValue> sCookieTagMap = null;
138     // TODO: Add BOOL class and replace U8?
139     private static IBpfMap<S32, U8> sDataSaverEnabledMap = null;
140     private static IBpfMap<IngressDiscardKey, IngressDiscardValue> sIngressDiscardMap = null;
141 
142     private static IBpfMap<LocalNetAccessKey, Bool> sLocalNetAccessMap = null;
143     private static IBpfMap<U32, Bool> sLocalNetBlockedUidMap = null;
144 
145     private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
146             Pair.create(TRAFFIC_PERMISSION_INTERNET, "PERMISSION_INTERNET"),
147             Pair.create(TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
148     );
149     private final InterfaceTracker mInterfaceTracker;
150 
151     /**
152      * Set configurationMap for test.
153      */
154     @VisibleForTesting
setConfigurationMapForTest(IBpfMap<S32, U32> configurationMap)155     public static void setConfigurationMapForTest(IBpfMap<S32, U32> configurationMap) {
156         sConfigurationMap = configurationMap;
157     }
158 
159     /**
160      * Set uidOwnerMap for test.
161      */
162     @VisibleForTesting
setUidOwnerMapForTest(IBpfMap<S32, UidOwnerValue> uidOwnerMap)163     public static void setUidOwnerMapForTest(IBpfMap<S32, UidOwnerValue> uidOwnerMap) {
164         sUidOwnerMap = uidOwnerMap;
165     }
166 
167     /**
168      * Set uidPermissionMap for test.
169      */
170     @VisibleForTesting
setUidPermissionMapForTest(IBpfMap<S32, U8> uidPermissionMap)171     public static void setUidPermissionMapForTest(IBpfMap<S32, U8> uidPermissionMap) {
172         sUidPermissionMap = uidPermissionMap;
173     }
174 
175     /**
176      * Set cookieTagMap for test.
177      */
178     @VisibleForTesting
setCookieTagMapForTest( IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap)179     public static void setCookieTagMapForTest(
180             IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap) {
181         sCookieTagMap = cookieTagMap;
182     }
183 
184     /**
185      * Set dataSaverEnabledMap for test.
186      */
187     @VisibleForTesting
setDataSaverEnabledMapForTest(IBpfMap<S32, U8> dataSaverEnabledMap)188     public static void setDataSaverEnabledMapForTest(IBpfMap<S32, U8> dataSaverEnabledMap) {
189         sDataSaverEnabledMap = dataSaverEnabledMap;
190     }
191 
192     /**
193      * Set ingressDiscardMap for test.
194      */
195     @VisibleForTesting
setIngressDiscardMapForTest( IBpfMap<IngressDiscardKey, IngressDiscardValue> ingressDiscardMap)196     public static void setIngressDiscardMapForTest(
197             IBpfMap<IngressDiscardKey, IngressDiscardValue> ingressDiscardMap) {
198         sIngressDiscardMap = ingressDiscardMap;
199     }
200 
201     /**
202      * Set localNetAccessMap for test.
203      */
204     @VisibleForTesting
setLocalNetAccessMapForTest( IBpfMap<LocalNetAccessKey, Bool> localNetAccessMap)205     public static void setLocalNetAccessMapForTest(
206             IBpfMap<LocalNetAccessKey, Bool> localNetAccessMap) {
207         sLocalNetAccessMap = localNetAccessMap;
208     }
209 
210     /**
211      * Set localNetBlockedUidMap for test.
212      */
213     @VisibleForTesting
setLocalNetBlockedUidMapForTest( IBpfMap<U32, Bool> localNetBlockedUidMap)214     public static void setLocalNetBlockedUidMapForTest(
215             IBpfMap<U32, Bool> localNetBlockedUidMap) {
216         sLocalNetBlockedUidMap = localNetBlockedUidMap;
217     }
218 
219 
220     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getConfigurationMap()221     private static IBpfMap<S32, U32> getConfigurationMap() {
222         try {
223             return SingleWriterBpfMap.getSingleton(
224                     CONFIGURATION_MAP_PATH, S32.class, U32.class);
225         } catch (ErrnoException e) {
226             throw new IllegalStateException("Cannot open netd configuration map", e);
227         }
228     }
229 
230     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidOwnerMap()231     private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
232         try {
233             return SingleWriterBpfMap.getSingleton(
234                     UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
235         } catch (ErrnoException e) {
236             throw new IllegalStateException("Cannot open uid owner map", e);
237         }
238     }
239 
240     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidPermissionMap()241     private static IBpfMap<S32, U8> getUidPermissionMap() {
242         try {
243             return SingleWriterBpfMap.getSingleton(
244                     UID_PERMISSION_MAP_PATH, S32.class, U8.class);
245         } catch (ErrnoException e) {
246             throw new IllegalStateException("Cannot open uid permission map", e);
247         }
248     }
249 
250     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getCookieTagMap()251     private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
252         try {
253             // Cannot use SingleWriterBpfMap because it's written by ClatCoordinator as well.
254             return new BpfMap<>(COOKIE_TAG_MAP_PATH,
255                     CookieTagMapKey.class, CookieTagMapValue.class);
256         } catch (ErrnoException e) {
257             throw new IllegalStateException("Cannot open cookie tag map", e);
258         }
259     }
260 
261     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getDataSaverEnabledMap()262     private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
263         try {
264             return SingleWriterBpfMap.getSingleton(
265                     DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
266         } catch (ErrnoException e) {
267             throw new IllegalStateException("Cannot open data saver enabled map", e);
268         }
269     }
270 
271     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getIngressDiscardMap()272     private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
273         try {
274             return SingleWriterBpfMap.getSingleton(INGRESS_DISCARD_MAP_PATH,
275                     IngressDiscardKey.class, IngressDiscardValue.class);
276         } catch (ErrnoException e) {
277             throw new IllegalStateException("Cannot open ingress discard map", e);
278         }
279     }
280 
281     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
getLocalNetBlockedUidMap()282     private static IBpfMap<U32, Bool> getLocalNetBlockedUidMap() {
283         try {
284             return SingleWriterBpfMap.getSingleton(LOCAL_NET_BLOCKED_UID_MAP_PATH,
285                     U32.class, Bool.class);
286         } catch (ErrnoException e) {
287             throw new IllegalStateException("Cannot open local_net_blocked_uid map", e);
288         }
289     }
290 
291     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
getLocalNetAccessMap()292     private static IBpfMap<LocalNetAccessKey, Bool> getLocalNetAccessMap() {
293         try {
294             return SingleWriterBpfMap.getSingleton(LOCAL_NET_ACCESS_MAP_PATH,
295                     LocalNetAccessKey.class, Bool.class);
296         } catch (ErrnoException e) {
297             throw new IllegalStateException("Cannot open local_net_access map", e);
298         }
299     }
300 
301     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
initBpfMaps()302     private static void initBpfMaps() {
303         if (sConfigurationMap == null) {
304             sConfigurationMap = getConfigurationMap();
305         }
306         try {
307             sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY,
308                     new U32(UID_RULES_DEFAULT_CONFIGURATION));
309         } catch (ErrnoException e) {
310             throw new IllegalStateException("Failed to initialize uid rules configuration", e);
311         }
312         try {
313             sConfigurationMap.updateEntry(CURRENT_STATS_MAP_CONFIGURATION_KEY,
314                     new U32(STATS_SELECT_MAP_A));
315         } catch (ErrnoException e) {
316             throw new IllegalStateException("Failed to initialize current stats configuration", e);
317         }
318 
319         if (sUidOwnerMap == null) {
320             sUidOwnerMap = getUidOwnerMap();
321         }
322         try {
323             sUidOwnerMap.clear();
324         } catch (ErrnoException e) {
325             throw new IllegalStateException("Failed to initialize uid owner map", e);
326         }
327 
328         if (sUidPermissionMap == null) {
329             sUidPermissionMap = getUidPermissionMap();
330         }
331 
332         if (sCookieTagMap == null) {
333             sCookieTagMap = getCookieTagMap();
334         }
335 
336         if (sDataSaverEnabledMap == null) {
337             sDataSaverEnabledMap = getDataSaverEnabledMap();
338         }
339         try {
340             sDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
341         } catch (ErrnoException e) {
342             throw new IllegalStateException("Failed to initialize data saver configuration", e);
343         }
344 
345         if (sIngressDiscardMap == null) {
346             sIngressDiscardMap = getIngressDiscardMap();
347         }
348         try {
349             sIngressDiscardMap.clear();
350         } catch (ErrnoException e) {
351             throw new IllegalStateException("Failed to initialize ingress discard map", e);
352         }
353 
354         if (isAtLeast25Q2()) {
355             if (sLocalNetAccessMap == null) {
356                 sLocalNetAccessMap = getLocalNetAccessMap();
357             }
358             try {
359                 sLocalNetAccessMap.clear();
360             } catch (ErrnoException e) {
361                 throw new IllegalStateException("Failed to initialize local_net_access map", e);
362             }
363 
364             if (sLocalNetBlockedUidMap == null) {
365                 sLocalNetBlockedUidMap = getLocalNetBlockedUidMap();
366             }
367             try {
368                 sLocalNetBlockedUidMap.clear();
369             } catch (ErrnoException e) {
370                 throw new IllegalStateException("Failed to initialize local_net_blocked_uid map",
371                         e);
372             }
373         }
374     }
375 
376     /**
377      * Initializes the class if it is not already initialized. This method will open maps but not
378      * cause any other effects. This method may be called multiple times on any thread.
379      */
380     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
ensureInitialized(final Context context)381     private static synchronized void ensureInitialized(final Context context) {
382         if (sInitialized) return;
383         initBpfMaps();
384         sInitialized = true;
385     }
386 
387     /**
388      * Dependencies of BpfNetMaps, for injection in tests.
389      */
390     @VisibleForTesting
391     public static class Dependencies {
392         /**
393          * Get interface index.
394          */
getIfIndex(final String ifName)395         public int getIfIndex(final String ifName) {
396             return Os.if_nametoindex(ifName);
397         }
398 
399         /**
400          * Get interface name
401          */
getIfName(final int ifIndex)402         public String getIfName(final int ifIndex) {
403             return Os.if_indextoname(ifIndex);
404         }
405 
406         /**
407          * Synchronously call in to kernel to synchronize_rcu()
408          */
409         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
synchronizeKernelRCU()410         public int synchronizeKernelRCU() {
411             try {
412                 BpfMap.synchronizeKernelRCU();
413             } catch (ErrnoException e) {
414                 return -e.errno;
415             }
416             return 0;
417         }
418 
419         /**
420          * Build Stats Event for NETWORK_BPF_MAP_INFO atom
421          */
buildStatsEvent(final int cookieTagMapSize, final int uidOwnerMapSize, final int uidPermissionMapSize)422         public StatsEvent buildStatsEvent(final int cookieTagMapSize, final int uidOwnerMapSize,
423                 final int uidPermissionMapSize) {
424             return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize,
425                     uidOwnerMapSize, uidPermissionMapSize);
426         }
427     }
428 
429     /** Constructor used after T that doesn't need to use netd anymore. */
430     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
BpfNetMaps(final Context context, @NonNull final InterfaceTracker interfaceTracker)431     public BpfNetMaps(final Context context, @NonNull final InterfaceTracker interfaceTracker) {
432         this(context, null, interfaceTracker);
433 
434         if (!SdkLevel.isAtLeastT()) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
435     }
436 
BpfNetMaps(final Context context, final INetd netd, @NonNull final InterfaceTracker interfaceTracker)437     public BpfNetMaps(final Context context, final INetd netd, @NonNull final InterfaceTracker
438             interfaceTracker) {
439         this(context, netd, new Dependencies(), interfaceTracker);
440     }
441 
442     @VisibleForTesting
BpfNetMaps(final Context context, final INetd netd, final Dependencies deps, @NonNull final InterfaceTracker interfaceTracker)443     public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps,
444             @NonNull final  InterfaceTracker interfaceTracker) {
445         Objects.requireNonNull(interfaceTracker);
446         if (SdkLevel.isAtLeastT()) {
447             ensureInitialized(context);
448         }
449         mNetd = netd;
450         mDeps = deps;
451         mInterfaceTracker = interfaceTracker;
452     }
453 
maybeThrow(final int err, final String msg)454     private void maybeThrow(final int err, final String msg) {
455         if (err != 0) {
456             throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
457         }
458     }
459 
throwIfPreT(final String msg)460     private void throwIfPreT(final String msg) {
461         if (!SdkLevel.isAtLeastT()) {
462             throw new UnsupportedOperationException(msg);
463         }
464     }
465 
throwIfPre25Q2(final String msg)466     private void throwIfPre25Q2(final String msg) {
467         if (!isAtLeast25Q2()) {
468             throw new UnsupportedOperationException(msg);
469         }
470     }
471 
472     /*
473      ToDo : Remove this method when SdkLevel.isAtLeastB() is fixed, aosp is at sdk level 36 or use
474      NetworkStackUtils.isAtLeast25Q2 when it is moved to a static lib.
475      */
isAtLeast25Q2()476     public static boolean isAtLeast25Q2() {
477         return SdkLevel.isAtLeastB()  || (SdkLevel.isAtLeastV()
478                 && "Baklava".equals(Build.VERSION.CODENAME));
479     }
480 
removeRule(final int uid, final long match, final String caller)481     private void removeRule(final int uid, final long match, final String caller) {
482         try {
483             synchronized (sUidOwnerMap) {
484                 final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new S32(uid));
485 
486                 if (oldMatch == null) {
487                     throw new ServiceSpecificException(ENOENT,
488                             "sUidOwnerMap does not have entry for uid: " + uid);
489                 }
490 
491                 final UidOwnerValue newMatch = new UidOwnerValue(
492                         (match == IIF_MATCH) ? 0 : oldMatch.iif,
493                         oldMatch.rule & ~match
494                 );
495 
496                 if (newMatch.rule == 0) {
497                     sUidOwnerMap.deleteEntry(new S32(uid));
498                 } else {
499                     sUidOwnerMap.updateEntry(new S32(uid), newMatch);
500                 }
501             }
502         } catch (ErrnoException e) {
503             throw new ServiceSpecificException(e.errno,
504                     caller + " failed to remove rule: " + Os.strerror(e.errno));
505         }
506     }
507 
addRule(final int uid, final long match, final int iif, final String caller)508     private void addRule(final int uid, final long match, final int iif, final String caller) {
509         if (match != IIF_MATCH && iif != 0) {
510             throw new ServiceSpecificException(EINVAL,
511                     "Non-interface match must have zero interface index");
512         }
513 
514         try {
515             synchronized (sUidOwnerMap) {
516                 final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new S32(uid));
517 
518                 final UidOwnerValue newMatch;
519                 if (oldMatch != null) {
520                     newMatch = new UidOwnerValue(
521                             (match == IIF_MATCH) ? iif : oldMatch.iif,
522                             oldMatch.rule | match
523                     );
524                 } else {
525                     newMatch = new UidOwnerValue(
526                             iif,
527                             match
528                     );
529                 }
530                 sUidOwnerMap.updateEntry(new S32(uid), newMatch);
531             }
532         } catch (ErrnoException e) {
533             throw new ServiceSpecificException(e.errno,
534                     caller + " failed to add rule: " + Os.strerror(e.errno));
535         }
536     }
537 
addRule(final int uid, final long match, final String caller)538     private void addRule(final int uid, final long match, final String caller) {
539         addRule(uid, match, 0 /* iif */, caller);
540     }
541 
542     /**
543      * Set target firewall child chain
544      *
545      * @param childChain target chain to enable
546      * @param enable     whether to enable or disable child chain.
547      * @throws UnsupportedOperationException if called on pre-T devices.
548      * @throws ServiceSpecificException in case of failure, with an error code indicating the
549      *                                  cause of the failure.
550      */
551     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setChildChain(final int childChain, final boolean enable)552     public void setChildChain(final int childChain, final boolean enable) {
553         throwIfPreT("setChildChain is not available on pre-T devices");
554 
555         final long match = getMatchByFirewallChain(childChain);
556         try {
557             synchronized (sUidRulesConfigBpfMapLock) {
558                 final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
559                 final long newConfig = enable ? (config.val | match) : (config.val & ~match);
560                 sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(newConfig));
561             }
562         } catch (ErrnoException e) {
563             throw new ServiceSpecificException(e.errno,
564                     "Unable to set child chain: " + Os.strerror(e.errno));
565         }
566     }
567 
568     /**
569      * Get the specified firewall chain's status.
570      *
571      * @param childChain target chain
572      * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
573      * @throws UnsupportedOperationException if called on pre-T devices.
574      * @throws ServiceSpecificException in case of failure, with an error code indicating the
575      *                                  cause of the failure.
576      */
577     @Deprecated
578     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
isChainEnabled(final int childChain)579     public boolean isChainEnabled(final int childChain) {
580         return BpfNetMapsUtils.isChainEnabled(sConfigurationMap, childChain);
581     }
582 
asSet(final int[] uids)583     private Set<Integer> asSet(final int[] uids) {
584         final Set<Integer> uidSet = new ArraySet<>();
585         for (final int uid : uids) {
586             uidSet.add(uid);
587         }
588         return uidSet;
589     }
590 
591     /**
592      * Replaces the contents of the specified UID-based firewall chain.
593      * Enables the chain for specified uids and disables the chain for non-specified uids.
594      *
595      * @param chain       Target chain.
596      * @param uids        The list of UIDs to allow/deny.
597      * @throws UnsupportedOperationException if called on pre-T devices.
598      * @throws IllegalArgumentException if {@code chain} is not a valid chain.
599      */
600     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
replaceUidChain(final int chain, final int[] uids)601     public void replaceUidChain(final int chain, final int[] uids) {
602         throwIfPreT("replaceUidChain is not available on pre-T devices");
603 
604         final long match;
605         try {
606             match = getMatchByFirewallChain(chain);
607         } catch (ServiceSpecificException e) {
608             // Throws IllegalArgumentException to keep the behavior of
609             // ConnectivityManager#replaceFirewallChain API
610             throw new IllegalArgumentException("Invalid firewall chain: " + chain);
611         }
612         final Set<Integer> uidSet = asSet(uids);
613         final Set<Integer> uidSetToRemoveRule = new ArraySet<>();
614         try {
615             synchronized (sUidOwnerMap) {
616                 sUidOwnerMap.forEach((uid, config) -> {
617                     // config could be null if there is a concurrent entry deletion.
618                     // http://b/220084230. But sUidOwnerMap update must be done while holding a
619                     // lock, so this should not happen.
620                     if (config == null) {
621                         Log.wtf(TAG, "sUidOwnerMap entry was deleted while holding a lock");
622                     } else if (!uidSet.contains((int) uid.val) && (config.rule & match) != 0) {
623                         uidSetToRemoveRule.add((int) uid.val);
624                     }
625                 });
626 
627                 for (final int uid : uidSetToRemoveRule) {
628                     removeRule(uid, match, "replaceUidChain");
629                 }
630                 for (final int uid : uids) {
631                     addRule(uid, match, "replaceUidChain");
632                 }
633             }
634         } catch (ErrnoException | ServiceSpecificException e) {
635             Log.e(TAG, "replaceUidChain failed: " + e);
636         }
637     }
638 
639     /**
640      * Set firewall rule for uid
641      *
642      * @param childChain   target chain
643      * @param uid          uid to allow/deny
644      * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
645      * @throws ServiceSpecificException in case of failure, with an error code indicating the
646      *                                  cause of the failure.
647      */
648     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setUidRule(final int childChain, final int uid, final int firewallRule)649     public void setUidRule(final int childChain, final int uid, final int firewallRule) {
650         throwIfPreT("setUidRule is not available on pre-T devices");
651 
652         final long match = getMatchByFirewallChain(childChain);
653         final boolean isAllowList = isFirewallAllowList(childChain);
654         final boolean add = (firewallRule == FIREWALL_RULE_ALLOW && isAllowList)
655                 || (firewallRule == FIREWALL_RULE_DENY && !isAllowList);
656 
657         if (add) {
658             addRule(uid, match, "setUidRule");
659         } else {
660             removeRule(uid, match, "setUidRule");
661         }
662     }
663 
664     /**
665      * Get firewall rule of specified firewall chain on specified uid.
666      *
667      * @param childChain target chain
668      * @param uid        target uid
669      * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
670      * @throws UnsupportedOperationException if called on pre-T devices.
671      * @throws ServiceSpecificException in case of failure, with an error code indicating the
672      *                                  cause of the failure.
673      */
674     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidRule(final int childChain, final int uid)675     public int getUidRule(final int childChain, final int uid) {
676         return BpfNetMapsUtils.getUidRule(sUidOwnerMap, childChain, uid);
677     }
678 
679     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidsMatchEnabled(final int childChain)680     private Set<Integer> getUidsMatchEnabled(final int childChain) throws ErrnoException {
681         final long match = getMatchByFirewallChain(childChain);
682         Set<Integer> uids = new ArraySet<>();
683         synchronized (sUidOwnerMap) {
684             sUidOwnerMap.forEach((uid, val) -> {
685                 if (val == null) {
686                     Log.wtf(TAG, "sUidOwnerMap entry was deleted while holding a lock");
687                 } else {
688                     if ((val.rule & match) != 0) {
689                         uids.add(uid.val);
690                     }
691                 }
692             });
693         }
694         return uids;
695     }
696 
697     /**
698      * Get uids that has FIREWALL_RULE_ALLOW on allowlist chain.
699      * Allowlist means the firewall denies all by default, uids must be explicitly allowed.
700      *
701      * Note that uids that has FIREWALL_RULE_DENY on allowlist chain can not be computed from the
702      * bpf map, since all the uids that does not have explicit FIREWALL_RULE_ALLOW rule in bpf map
703      * are determined to have FIREWALL_RULE_DENY.
704      *
705      * @param childChain target chain
706      * @return Set of uids
707      */
708     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidsWithAllowRuleOnAllowListChain(final int childChain)709     public Set<Integer> getUidsWithAllowRuleOnAllowListChain(final int childChain)
710             throws ErrnoException {
711         if (!isFirewallAllowList(childChain)) {
712             throw new IllegalArgumentException("getUidsWithAllowRuleOnAllowListChain is called with"
713                     + " denylist chain:" + childChain);
714         }
715         // Corresponding match is enabled for uids that has FIREWALL_RULE_ALLOW on allowlist chain.
716         return getUidsMatchEnabled(childChain);
717     }
718 
719     /**
720      * Get uids that has FIREWALL_RULE_DENY on denylist chain.
721      * Denylist means the firewall allows all by default, uids must be explicitly denyed
722      *
723      * Note that uids that has FIREWALL_RULE_ALLOW on denylist chain can not be computed from the
724      * bpf map, since all the uids that does not have explicit FIREWALL_RULE_DENY rule in bpf map
725      * are determined to have the FIREWALL_RULE_ALLOW.
726      *
727      * @param childChain target chain
728      * @return Set of uids
729      */
730     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidsWithDenyRuleOnDenyListChain(final int childChain)731     public Set<Integer> getUidsWithDenyRuleOnDenyListChain(final int childChain)
732             throws ErrnoException {
733         if (isFirewallAllowList(childChain)) {
734             throw new IllegalArgumentException("getUidsWithDenyRuleOnDenyListChain is called with"
735                     + " allowlist chain:" + childChain);
736         }
737         // Corresponding match is enabled for uids that has FIREWALL_RULE_DENY on denylist chain.
738         return getUidsMatchEnabled(childChain);
739     }
740 
741     /**
742      * Add ingress interface filtering rules to a list of UIDs
743      *
744      * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
745      * allowed interface and loopback to be sent to the list of UIDs.
746      *
747      * Calling this method on one or more UIDs with an existing filtering rule but a different
748      * interface name will result in the filtering rule being updated to allow the new interface
749      * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
750      *
751      * @param ifName the name of the interface on which the filtering rules will allow packets to
752      *               be received.
753      * @param uids   an array of UIDs which the filtering rules will be set
754      * @throws RemoteException when netd has crashed.
755      * @throws ServiceSpecificException in case of failure, with an error code indicating the
756      *                                  cause of the failure.
757      */
addUidInterfaceRules(final String ifName, final int[] uids)758     public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
759         if (!SdkLevel.isAtLeastT()) {
760             mNetd.firewallAddUidInterfaceRules(ifName, uids);
761             return;
762         }
763 
764         // Null ifName is a wildcard to allow apps to receive packets on all interfaces and
765         // ifIndex is set to 0.
766         final int ifIndex;
767         if (ifName == null) {
768             ifIndex = 0;
769         } else {
770             ifIndex = mDeps.getIfIndex(ifName);
771             if (ifIndex == 0) {
772                 throw new ServiceSpecificException(ENODEV,
773                         "Failed to get index of interface " + ifName);
774             }
775         }
776         for (final int uid : uids) {
777             try {
778                 addRule(uid, IIF_MATCH, ifIndex, "addUidInterfaceRules");
779             } catch (ServiceSpecificException e) {
780                 Log.e(TAG, "addRule failed uid=" + uid + " ifName=" + ifName + ", " + e);
781             }
782         }
783     }
784 
785     /**
786      * Remove ingress interface filtering rules from a list of UIDs
787      *
788      * Clear the ingress interface filtering rules from the list of UIDs which were previously set
789      * by addUidInterfaceRules(). Ignore any uid which does not have filtering rule.
790      *
791      * @param uids an array of UIDs from which the filtering rules will be removed
792      * @throws RemoteException when netd has crashed.
793      * @throws ServiceSpecificException in case of failure, with an error code indicating the
794      *                                  cause of the failure.
795      */
removeUidInterfaceRules(final int[] uids)796     public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
797         if (!SdkLevel.isAtLeastT()) {
798             mNetd.firewallRemoveUidInterfaceRules(uids);
799             return;
800         }
801 
802         for (final int uid : uids) {
803             try {
804                 removeRule(uid, IIF_MATCH, "removeUidInterfaceRules");
805             } catch (ServiceSpecificException e) {
806                 Log.e(TAG, "removeRule failed uid=" + uid + ", " + e);
807             }
808         }
809     }
810 
811     /**
812      * Update lockdown rule for uid
813      *
814      * @param  uid          target uid to add/remove the rule
815      * @param  add          {@code true} to add the rule, {@code false} to remove the rule.
816      * @throws ServiceSpecificException in case of failure, with an error code indicating the
817      *                                  cause of the failure.
818      */
819     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
updateUidLockdownRule(final int uid, final boolean add)820     public void updateUidLockdownRule(final int uid, final boolean add) {
821         throwIfPreT("updateUidLockdownRule is not available on pre-T devices");
822 
823         if (add) {
824             addRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule");
825         } else {
826             removeRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule");
827         }
828     }
829 
830     /**
831      * Request netd to change the current active network stats map.
832      *
833      * @throws UnsupportedOperationException if called on pre-T devices.
834      * @throws ServiceSpecificException in case of failure, with an error code indicating the
835      *                                  cause of the failure.
836      */
837     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
swapActiveStatsMap()838     public void swapActiveStatsMap() {
839         throwIfPreT("swapActiveStatsMap is not available on pre-T devices");
840 
841         try {
842             synchronized (sCurrentStatsMapConfigLock) {
843                 final long config = sConfigurationMap.getValue(
844                         CURRENT_STATS_MAP_CONFIGURATION_KEY).val;
845                 final long newConfig = (config == STATS_SELECT_MAP_A)
846                         ? STATS_SELECT_MAP_B : STATS_SELECT_MAP_A;
847                 sConfigurationMap.updateEntry(CURRENT_STATS_MAP_CONFIGURATION_KEY,
848                         new U32(newConfig));
849             }
850         } catch (ErrnoException e) {
851             throw new ServiceSpecificException(e.errno, "Failed to swap active stats map");
852         }
853 
854         // After changing the config, it's needed to make sure all the current running eBPF
855         // programs are finished and all the CPUs are aware of this config change before the old
856         // map is modified. So special hack is needed here to wait for the kernel to do a
857         // synchronize_rcu(). Once the kernel called synchronize_rcu(), the updated config will
858         // be available to all cores and the next eBPF programs triggered inside the kernel will
859         // use the new map configuration. So once this function returns it is safe to modify the
860         // old stats map without concerning about race between the kernel and userspace.
861         final int err = mDeps.synchronizeKernelRCU();
862         maybeThrow(err, "synchronizeKernelRCU failed");
863     }
864 
865     /**
866      * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
867      * specified. Or remove all permissions from the uids.
868      *
869      * @param permissions The permission to grant, it could be either PERMISSION_INTERNET and/or
870      *                    PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
871      *                    revoke all permissions for the uids.
872      * @param uids        uid of users to grant permission
873      * @throws RemoteException when netd has crashed.
874      */
setNetPermForUids(final int permissions, final int[] uids)875     public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
876         if (!SdkLevel.isAtLeastT()) {
877             mNetd.trafficSetNetPermForUids(permissions, uids);
878             return;
879         }
880 
881         // Remove the entry if package is uninstalled or uid has only INTERNET permission.
882         if (permissions == TRAFFIC_PERMISSION_UNINSTALLED
883                 || permissions == TRAFFIC_PERMISSION_INTERNET) {
884             for (final int uid : uids) {
885                 try {
886                     sUidPermissionMap.deleteEntry(new S32(uid));
887                 } catch (ErrnoException e) {
888                     Log.e(TAG, "Failed to remove uid " + uid + " from permission map: " + e);
889                 }
890             }
891             return;
892         }
893 
894         for (final int uid : uids) {
895             try {
896                 sUidPermissionMap.updateEntry(new S32(uid), new U8((short) permissions));
897             } catch (ErrnoException e) {
898                 Log.e(TAG, "Failed to set permission "
899                         + permissions + " to uid " + uid + ": " + e);
900             }
901         }
902     }
903 
904     /**
905      * Add configuration to local_net_access trie map.
906      * @param lpmBitlen prefix length that will be used for longest matching
907      * @param iface interface name
908      * @param address remote address. ipv4 addresses would be mapped to v6
909      * @param protocol required for longest match in special cases
910      * @param remotePort src/dst port for ingress/egress
911      * @param isAllowed is the local network call allowed or blocked.
912      */
913     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
addLocalNetAccess(final int lpmBitlen, @Nullable final String iface, final InetAddress address, final int protocol, final int remotePort, final boolean isAllowed)914     public void addLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
915             final InetAddress address, final int protocol, final int remotePort,
916             final boolean isAllowed) {
917         throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
918         if (iface == null) {
919             Log.e(TAG, "Null iface, skip addLocalNetAccess for " + address);
920             return;
921         }
922         int ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
923         if (ifIndex == 0) {
924             mInterfaceTracker.addInterface(iface);
925             ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
926         }
927         if (ifIndex == 0) {
928             Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address
929                     + "(" + iface + ")");
930             return;
931         }
932         final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
933                 address, protocol, remotePort);
934 
935         try {
936             sLocalNetAccessMap.updateEntry(localNetAccessKey, new Bool(isAllowed));
937         } catch (ErrnoException e) {
938             Log.e(TAG, "Failed to add local network access for localNetAccessKey : "
939                     + localNetAccessKey + ", isAllowed : " + isAllowed);
940         }
941     }
942 
943     /**
944      * Remove configuration to local_net_access trie map.
945      * @param lpmBitlen prefix length that will be used for longest matching
946      * @param iface interface name
947      * @param address remote address. ipv4 addresses would be mapped to v6
948      * @param protocol required for longest match in special cases
949      * @param remotePort src/dst port for ingress/egress
950      */
951     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
removeLocalNetAccess(final int lpmBitlen, @Nullable final String iface, final InetAddress address, final int protocol, final int remotePort)952     public void removeLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
953             final InetAddress address, final int protocol, final int remotePort) {
954         throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices");
955         final int ifIndex;
956         if (iface == null) {
957             ifIndex = 0;
958         } else {
959             ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
960         }
961         if (ifIndex == 0) {
962             Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address
963                     + "(" + iface + ")");
964             return;
965         }
966         final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
967                 address, protocol, remotePort);
968 
969         try {
970             sLocalNetAccessMap.deleteEntry(localNetAccessKey);
971         } catch (ErrnoException e) {
972             Log.e(TAG, "Failed to remove local network access for localNetAccessKey : "
973                     + localNetAccessKey);
974         }
975     }
976 
977     /**
978      * Fetches value available in local_net_access bpf map for provided configuration
979      * @param lpmBitlen  prefix length that will be used for longest matching
980      * @param iface    interface name
981      * @param address    remote address. ipv4 addresses would be mapped to v6
982      * @param protocol   required for longest match in special cases
983      * @param remotePort src/dst port for ingress/egress
984      * @return false if the configuration is disallowed, true if the configuration is absent i.e. it
985      * is not local network or if configuration is allowed like local dns servers.
986      */
987     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
getLocalNetAccess(final int lpmBitlen, @Nullable final String iface, final InetAddress address, final int protocol, final int remotePort)988     public boolean getLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
989             final InetAddress address, final int protocol, final int remotePort) {
990         throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices");
991         final int ifIndex;
992         if (iface == null) {
993             ifIndex = 0;
994         } else {
995             ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
996         }
997         if (ifIndex == 0) {
998             Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for "
999                     + address + "(" + iface + ")");
1000             return true;
1001         }
1002         final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
1003                 address, protocol, remotePort);
1004         try {
1005             final Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
1006             return value == null ? true : value.val;
1007         } catch (ErrnoException e) {
1008             Log.e(TAG, "Failed to find local network access configuration for "
1009                     + "localNetAccessKey : " + localNetAccessKey);
1010         }
1011         return true;
1012     }
1013 
1014     /**
1015      * Add uid to local_net_blocked_uid map.
1016      * @param uid application uid that needs to block local network calls.
1017      */
1018     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
addUidToLocalNetBlockMap(final int uid)1019     public void addUidToLocalNetBlockMap(final int uid) {
1020         throwIfPre25Q2("addUidToLocalNetBlockMap is not available on pre-B devices");
1021         try {
1022             sLocalNetBlockedUidMap.updateEntry(new U32(uid), new Bool(true));
1023         } catch (ErrnoException e) {
1024             Log.e(TAG, "Failed to add local network blocked for uid : " + uid);
1025         }
1026     }
1027 
1028     /**
1029      * True if local network calls are blocked for application.
1030      * @param uid application uid that needs check if local network calls are blocked.
1031      */
1032     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
isUidBlockedFromUsingLocalNetwork(final int uid)1033     public boolean isUidBlockedFromUsingLocalNetwork(final int uid) {
1034         throwIfPre25Q2("isUidBlockedFromUsingLocalNetwork is not available on pre-B devices");
1035         try {
1036             final Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
1037             return value == null ? false : value.val;
1038         } catch (ErrnoException e) {
1039             Log.e(TAG, "Failed to find uid(" + uid
1040                     + ") is present in local network blocked map");
1041         }
1042         return false;
1043     }
1044 
1045     /**
1046      * Remove uid from local_net_blocked_uid map(if present).
1047      * @param uid application uid that needs check if local network calls are blocked.
1048      */
1049     @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
removeUidFromLocalNetBlockMap(final int uid)1050     public void removeUidFromLocalNetBlockMap(final int uid) {
1051         throwIfPre25Q2("removeUidFromLocalNetBlockMap is not available on pre-B devices");
1052         try {
1053             sLocalNetBlockedUidMap.deleteEntry(new U32(uid));
1054         } catch (ErrnoException e) {
1055             Log.e(TAG, "Failed to remove uid(" + uid + ") from local network blocked map");
1056         }
1057     }
1058 
1059     /**
1060      * Get granted permissions for specified uid. If uid is not in the map, this method returns
1061      * {@link android.net.INetd.PERMISSION_INTERNET} since this is a default permission.
1062      * See {@link #setNetPermForUids}
1063      *
1064      * @param uid target uid
1065      * @return    granted permissions.
1066      */
1067     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getNetPermForUid(final int uid)1068     public int getNetPermForUid(final int uid) {
1069         final int appId = UserHandle.getAppId(uid);
1070         try {
1071             // Key of uid permission map is appId
1072             // TODO: Rename map name
1073             final U8 permissions = sUidPermissionMap.getValue(new S32(appId));
1074             return permissions != null ? permissions.val : TRAFFIC_PERMISSION_INTERNET;
1075         } catch (ErrnoException e) {
1076             Log.wtf(TAG, "Failed to get permission for uid: " + uid);
1077             return TRAFFIC_PERMISSION_INTERNET;
1078         }
1079     }
1080 
1081     /**
1082      * Set Data Saver enabled or disabled
1083      *
1084      * @param enable     whether Data Saver is enabled or disabled.
1085      * @throws UnsupportedOperationException if called on pre-T devices.
1086      * @throws ServiceSpecificException in case of failure, with an error code indicating the
1087      *                                  cause of the failure.
1088      */
1089     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setDataSaverEnabled(boolean enable)1090     public void setDataSaverEnabled(boolean enable) {
1091         throwIfPreT("setDataSaverEnabled is not available on pre-T devices");
1092 
1093         try {
1094             final short config = enable ? DATA_SAVER_ENABLED : DATA_SAVER_DISABLED;
1095             sDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(config));
1096         } catch (ErrnoException e) {
1097             throw new ServiceSpecificException(e.errno, "Unable to set data saver: "
1098                     + Os.strerror(e.errno));
1099         }
1100     }
1101 
1102     /**
1103      * Set ingress discard rule
1104      *
1105      * @param address target address to set the ingress discard rule
1106      * @param iface allowed interface
1107      */
1108     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setIngressDiscardRule(final InetAddress address, final String iface)1109     public void setIngressDiscardRule(final InetAddress address, final String iface) {
1110         throwIfPreT("setIngressDiscardRule is not available on pre-T devices");
1111         final int ifIndex = mDeps.getIfIndex(iface);
1112         if (ifIndex == 0) {
1113             Log.e(TAG, "Failed to get if index, skip setting ingress discard rule for " + address
1114                     + "(" + iface + ")");
1115             return;
1116         }
1117         try {
1118             sIngressDiscardMap.updateEntry(new IngressDiscardKey(address),
1119                     new IngressDiscardValue(ifIndex, ifIndex));
1120         } catch (ErrnoException e) {
1121             Log.e(TAG, "Failed to set ingress discard rule for " + address + "("
1122                     + iface + "), " + e);
1123         }
1124     }
1125 
1126     /**
1127      * Remove ingress discard rule
1128      *
1129      * @param address target address to remove the ingress discard rule
1130      */
1131     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
removeIngressDiscardRule(final InetAddress address)1132     public void removeIngressDiscardRule(final InetAddress address) {
1133         throwIfPreT("removeIngressDiscardRule is not available on pre-T devices");
1134         try {
1135             sIngressDiscardMap.deleteEntry(new IngressDiscardKey(address));
1136         } catch (ErrnoException e) {
1137             Log.e(TAG, "Failed to remove ingress discard rule for " + address + ", " + e);
1138         }
1139     }
1140 
1141     /**
1142      * Get blocked reasons for specified uid
1143      *
1144      * @param uid Target Uid
1145      * @return Reasons of network access blocking for an UID
1146      */
1147     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUidNetworkingBlockedReasons(final int uid)1148     public int getUidNetworkingBlockedReasons(final int uid) {
1149         return BpfNetMapsUtils.getUidNetworkingBlockedReasons(uid,
1150                 sConfigurationMap, sUidOwnerMap, sDataSaverEnabledMap);
1151     }
1152 
1153     /**
1154      * Return whether the network access of specified uid is blocked on metered networks
1155      *
1156      * @param uid The target uid.
1157      * @return True if the network access is blocked on metered networks. Otherwise, false
1158      */
1159     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
isUidRestrictedOnMeteredNetworks(final int uid)1160     public boolean isUidRestrictedOnMeteredNetworks(final int uid) {
1161         final int blockedReasons = getUidNetworkingBlockedReasons(uid);
1162         return (blockedReasons & BLOCKED_METERED_REASON_MASK) != BLOCKED_REASON_NONE;
1163     }
1164 
1165     /*
1166      * Return whether the network is blocked by firewall chains for the given uid.
1167      *
1168      * Note that {@link #getDataSaverEnabled()} has a latency before V.
1169      *
1170      * @param uid The target uid.
1171      * @param isNetworkMetered Whether the target network is metered.
1172      *
1173      * @return True if the network is blocked. Otherwise, false.
1174      * @throws ServiceSpecificException if the read fails.
1175      *
1176      * @hide
1177      */
1178     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
isUidNetworkingBlocked(final int uid, boolean isNetworkMetered)1179     public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered) {
1180         return BpfNetMapsUtils.isUidNetworkingBlocked(uid, isNetworkMetered,
1181                 sConfigurationMap, sUidOwnerMap, sDataSaverEnabledMap);
1182     }
1183 
1184     /** Register callback for statsd to pull atom. */
1185     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setPullAtomCallback(final Context context)1186     public void setPullAtomCallback(final Context context) {
1187         throwIfPreT("setPullAtomCallback is not available on pre-T devices");
1188 
1189         final StatsManager statsManager = context.getSystemService(StatsManager.class);
1190         statsManager.setPullAtomCallback(NETWORK_BPF_MAP_INFO, null /* metadata */,
1191                 BackgroundThread.getExecutor(), this::pullBpfMapInfoAtom);
1192     }
1193 
getMapSize(IBpfMap<K, V> map)1194     private <K extends Struct, V extends Struct> int getMapSize(IBpfMap<K, V> map)
1195             throws ErrnoException {
1196         // forEach could restart iteration from the beginning if there is a concurrent entry
1197         // deletion. netd and skDestroyListener could delete CookieTagMap entry concurrently.
1198         // So using Set to count the number of entry in the map.
1199         Set<K> keySet = new ArraySet<>();
1200         map.forEach((k, v) -> keySet.add(k));
1201         return keySet.size();
1202     }
1203 
1204     /** Callback for StatsManager#setPullAtomCallback */
1205     @VisibleForTesting
pullBpfMapInfoAtom(final int atomTag, final List<StatsEvent> data)1206     public int pullBpfMapInfoAtom(final int atomTag, final List<StatsEvent> data) {
1207         if (atomTag != NETWORK_BPF_MAP_INFO) {
1208             Log.e(TAG, "Unexpected atom tag: " + atomTag);
1209             return StatsManager.PULL_SKIP;
1210         }
1211 
1212         try {
1213             data.add(mDeps.buildStatsEvent(getMapSize(sCookieTagMap), getMapSize(sUidOwnerMap),
1214                     getMapSize(sUidPermissionMap)));
1215         } catch (ErrnoException e) {
1216             Log.e(TAG, "Failed to pull NETWORK_BPF_MAP_INFO atom: " + e);
1217             return StatsManager.PULL_SKIP;
1218         }
1219         return StatsManager.PULL_SUCCESS;
1220     }
1221 
permissionToString(int permissionMask)1222     private String permissionToString(int permissionMask) {
1223         if (permissionMask == PERMISSION_NONE) {
1224             return "PERMISSION_NONE";
1225         }
1226         if (permissionMask == TRAFFIC_PERMISSION_UNINSTALLED) {
1227             // PERMISSION_UNINSTALLED should never appear in the map
1228             return "PERMISSION_UNINSTALLED error!";
1229         }
1230 
1231         final StringJoiner sj = new StringJoiner(" ");
1232         for (Pair<Integer, String> permission: PERMISSION_LIST) {
1233             final int permissionFlag = permission.first;
1234             final String permissionName = permission.second;
1235             if ((permissionMask & permissionFlag) != 0) {
1236                 sj.add(permissionName);
1237                 permissionMask &= ~permissionFlag;
1238             }
1239         }
1240         if (permissionMask != 0) {
1241             sj.add("PERMISSION_UNKNOWN(" + permissionMask + ")");
1242         }
1243         return sj.toString();
1244     }
1245 
1246     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
dumpOwnerMatchConfig(final IndentingPrintWriter pw)1247     private void dumpOwnerMatchConfig(final IndentingPrintWriter pw) {
1248         try {
1249             final long match = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
1250             pw.println("current ownerMatch configuration: " + match + " " + matchToString(match));
1251         } catch (ErrnoException e) {
1252             pw.println("Failed to read ownerMatch configuration: " + e);
1253         }
1254     }
1255 
dumpCurrentStatsMapConfig(final IndentingPrintWriter pw)1256     private void dumpCurrentStatsMapConfig(final IndentingPrintWriter pw) {
1257         try {
1258             final long config = sConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val;
1259             final String currentStatsMap =
1260                     (config == STATS_SELECT_MAP_A) ? "SELECT_MAP_A" : "SELECT_MAP_B";
1261             pw.println("current statsMap configuration: " + config + " " + currentStatsMap);
1262         } catch (ErrnoException e) {
1263             pw.println("Falied to read current statsMap configuration: " + e);
1264         }
1265     }
1266 
dumpDataSaverConfig(final IndentingPrintWriter pw)1267     private void dumpDataSaverConfig(final IndentingPrintWriter pw) {
1268         try {
1269             final short config = sDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val;
1270             // Any non-zero value converted from short to boolean is true by convention.
1271             pw.println("sDataSaverEnabledMap: " + (config != DATA_SAVER_DISABLED));
1272         } catch (ErrnoException e) {
1273             pw.println("Failed to read data saver configuration: " + e);
1274         }
1275     }
1276     /**
1277      * Dump BPF maps
1278      *
1279      * @param pw print writer
1280      * @param fd file descriptor to output
1281      * @param verbose verbose dump flag, if true dump the BpfMap contents
1282      * @throws IOException when file descriptor is invalid.
1283      * @throws ServiceSpecificException when the method is called on an unsupported device.
1284      */
1285     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)1286     public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
1287             throws IOException, ServiceSpecificException {
1288         if (!SdkLevel.isAtLeastT()) {
1289             throw new ServiceSpecificException(
1290                     EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
1291                     + " devices, use dumpsys netd trafficcontroller instead.");
1292         }
1293 
1294         pw.println("TrafficController");  // required by CTS testDumpBpfNetMaps
1295 
1296         pw.println();
1297         if (verbose) {
1298             pw.println();
1299             pw.println("BPF map content:");
1300             pw.increaseIndent();
1301 
1302             dumpOwnerMatchConfig(pw);
1303             dumpCurrentStatsMapConfig(pw);
1304             pw.println();
1305 
1306             // TODO: Remove CookieTagMap content dump
1307             // NetworkStatsService also dumps CookieTagMap and NetworkStatsService is a right place
1308             // to dump CookieTagMap. But the TagSocketTest in CTS depends on this dump so the tests
1309             // need to be updated before remove the dump from BpfNetMaps.
1310             BpfDump.dumpMap(sCookieTagMap, pw, "sCookieTagMap",
1311                     (key, value) -> "cookie=" + key.socketCookie
1312                             + " tag=0x" + Long.toHexString(value.tag)
1313                             + " uid=" + value.uid);
1314             BpfDump.dumpMap(sUidOwnerMap, pw, "sUidOwnerMap",
1315                     (uid, match) -> {
1316                         if ((match.rule & IIF_MATCH) != 0) {
1317                             // TODO: convert interface index to interface name by IfaceIndexNameMap
1318                             return uid.val + " " + matchToString(match.rule) + " " + match.iif;
1319                         } else {
1320                             return uid.val + " " + matchToString(match.rule);
1321                         }
1322                     });
1323             BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap",
1324                     (uid, permission) -> uid.val + " " + permissionToString(permission.val));
1325             BpfDump.dumpMap(sIngressDiscardMap, pw, "sIngressDiscardMap",
1326                     (key, value) -> "[" + key.dstAddr + "]: "
1327                             + value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
1328                             + value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
1329             if (sLocalNetBlockedUidMap != null) {
1330                 BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap (default is true meaning global)",
1331                         (key, value) -> "" + key + ": " + value.val);
1332             }
1333             if (sLocalNetBlockedUidMap != null) {
1334                 BpfDump.dumpMap(sLocalNetBlockedUidMap, pw, "sLocalNetBlockedUidMap",
1335                         (key, value) -> "" + key + ": " + value.val);
1336             }
1337             dumpDataSaverConfig(pw);
1338             pw.decreaseIndent();
1339         }
1340     }
1341 }
1342