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