1 /*
2  * Copyright (C) 2023 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 android.net;
18 
19 import static android.net.BpfNetMapsConstants.ALLOW_CHAINS;
20 import static android.net.BpfNetMapsConstants.BACKGROUND_MATCH;
21 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
22 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
23 import static android.net.BpfNetMapsConstants.DENY_CHAINS;
24 import static android.net.BpfNetMapsConstants.DOZABLE_MATCH;
25 import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
26 import static android.net.BpfNetMapsConstants.LOW_POWER_STANDBY_MATCH;
27 import static android.net.BpfNetMapsConstants.MATCH_LIST;
28 import static android.net.BpfNetMapsConstants.METERED_ALLOW_CHAINS;
29 import static android.net.BpfNetMapsConstants.METERED_DENY_CHAINS;
30 import static android.net.BpfNetMapsConstants.NO_MATCH;
31 import static android.net.BpfNetMapsConstants.OEM_DENY_1_MATCH;
32 import static android.net.BpfNetMapsConstants.OEM_DENY_2_MATCH;
33 import static android.net.BpfNetMapsConstants.OEM_DENY_3_MATCH;
34 import static android.net.BpfNetMapsConstants.PENALTY_BOX_ADMIN_MATCH;
35 import static android.net.BpfNetMapsConstants.PENALTY_BOX_USER_MATCH;
36 import static android.net.BpfNetMapsConstants.POWERSAVE_MATCH;
37 import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
38 import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
39 import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
40 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
41 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
42 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
43 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
44 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
45 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
46 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
47 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
48 import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
49 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
50 import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
51 import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
52 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
53 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
54 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
55 import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
56 import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
57 import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
58 import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
59 import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
60 import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
61 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
62 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
63 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
64 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
65 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
66 import static android.system.OsConstants.EINVAL;
67 
68 import android.os.Build;
69 import android.os.Process;
70 import android.os.ServiceSpecificException;
71 import android.os.UserHandle;
72 import android.system.ErrnoException;
73 import android.system.Os;
74 import android.util.Pair;
75 
76 import androidx.annotation.RequiresApi;
77 
78 import com.android.modules.utils.build.SdkLevel;
79 import com.android.net.module.util.IBpfMap;
80 import com.android.net.module.util.Struct;
81 import com.android.net.module.util.Struct.S32;
82 import com.android.net.module.util.Struct.U32;
83 import com.android.net.module.util.Struct.U8;
84 
85 import java.util.StringJoiner;
86 
87 /**
88  * The classes and the methods for BpfNetMaps utilization.
89  *
90  * @hide
91  */
92 // Note that this class should be put into bootclasspath instead of static libraries.
93 // Because modules could have different copies of this class if this is statically linked,
94 // which would be problematic if the definitions in these modules are not synchronized.
95 // Note that NetworkStack can not use this before U due to b/326143935
96 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
97 public class BpfNetMapsUtils {
98     // Bitmaps for calculating whether a given uid is blocked by firewall chains.
99     private static final long sMaskDropIfSet;
100     private static final long sMaskDropIfUnset;
101 
102     static {
103         long maskDropIfSet = 0L;
104         long maskDropIfUnset = 0L;
105 
106         for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
107             final long match = getMatchByFirewallChain(chain);
108             maskDropIfUnset |= match;
109         }
110         for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
111             final long match = getMatchByFirewallChain(chain);
112             maskDropIfSet |= match;
113         }
114         sMaskDropIfSet = maskDropIfSet;
115         sMaskDropIfUnset = maskDropIfUnset;
116     }
117 
118     // Prevent this class from being accidental instantiated.
BpfNetMapsUtils()119     private BpfNetMapsUtils() {}
120 
121     /**
122      * Get corresponding match from firewall chain.
123      */
getMatchByFirewallChain(final int chain)124     public static long getMatchByFirewallChain(final int chain) {
125         switch (chain) {
126             case FIREWALL_CHAIN_DOZABLE:
127                 return DOZABLE_MATCH;
128             case FIREWALL_CHAIN_STANDBY:
129                 return STANDBY_MATCH;
130             case FIREWALL_CHAIN_POWERSAVE:
131                 return POWERSAVE_MATCH;
132             case FIREWALL_CHAIN_RESTRICTED:
133                 return RESTRICTED_MATCH;
134             case FIREWALL_CHAIN_BACKGROUND:
135                 return BACKGROUND_MATCH;
136             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
137                 return LOW_POWER_STANDBY_MATCH;
138             case FIREWALL_CHAIN_OEM_DENY_1:
139                 return OEM_DENY_1_MATCH;
140             case FIREWALL_CHAIN_OEM_DENY_2:
141                 return OEM_DENY_2_MATCH;
142             case FIREWALL_CHAIN_OEM_DENY_3:
143                 return OEM_DENY_3_MATCH;
144             case FIREWALL_CHAIN_METERED_ALLOW:
145                 return HAPPY_BOX_MATCH;
146             case FIREWALL_CHAIN_METERED_DENY_USER:
147                 return PENALTY_BOX_USER_MATCH;
148             case FIREWALL_CHAIN_METERED_DENY_ADMIN:
149                 return PENALTY_BOX_ADMIN_MATCH;
150             default:
151                 throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
152         }
153     }
154 
155     /**
156      * Get whether the chain is an allow-list or a deny-list.
157      *
158      * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
159      * DENYLIST means the firewall allows all by default, uids must be explicitly denied
160      */
isFirewallAllowList(final int chain)161     public static boolean isFirewallAllowList(final int chain) {
162         if (ALLOW_CHAINS.contains(chain) || METERED_ALLOW_CHAINS.contains(chain)) {
163             return true;
164         } else if (DENY_CHAINS.contains(chain) || METERED_DENY_CHAINS.contains(chain)) {
165             return false;
166         }
167         throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
168     }
169 
170     /**
171      * Get match string representation from the given match bitmap.
172      */
matchToString(long matchMask)173     public static String matchToString(long matchMask) {
174         if (matchMask == NO_MATCH) {
175             return "NO_MATCH";
176         }
177 
178         final StringJoiner sj = new StringJoiner(" ");
179         for (final Pair<Long, String> match : MATCH_LIST) {
180             final long matchFlag = match.first;
181             final String matchName = match.second;
182             if ((matchMask & matchFlag) != 0) {
183                 sj.add(matchName);
184                 matchMask &= ~matchFlag;
185             }
186         }
187         if (matchMask != 0) {
188             sj.add("UNKNOWN_MATCH(" + matchMask + ")");
189         }
190         return sj.toString();
191     }
192 
193     /**
194      * Throw UnsupportedOperationException if SdkLevel is before T.
195      */
throwIfPreT(final String msg)196     public static void throwIfPreT(final String msg) {
197         if (!SdkLevel.isAtLeastT()) {
198             throw new UnsupportedOperationException(msg);
199         }
200     }
201 
202     /**
203      * Get the specified firewall chain's status.
204      *
205      * @param configurationMap target configurationMap
206      * @param chain target chain
207      * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
208      * @throws UnsupportedOperationException if called on pre-T devices.
209      * @throws ServiceSpecificException in case of failure, with an error code indicating the
210      *                                  cause of the failure.
211      */
isChainEnabled( final IBpfMap<S32, U32> configurationMap, final int chain)212     public static boolean isChainEnabled(
213             final IBpfMap<S32, U32> configurationMap, final int chain) {
214         throwIfPreT("isChainEnabled is not available on pre-T devices");
215 
216         final long match = getMatchByFirewallChain(chain);
217         try {
218             final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
219             return (config.val & match) != 0;
220         } catch (ErrnoException e) {
221             throw new ServiceSpecificException(e.errno,
222                     "Unable to get firewall chain status: " + Os.strerror(e.errno));
223         }
224     }
225 
226     /**
227      * Get firewall rule of specified firewall chain on specified uid.
228      *
229      * @param uidOwnerMap target uidOwnerMap.
230      * @param chain target chain.
231      * @param uid target uid.
232      * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
233      * @throws UnsupportedOperationException if called on pre-T devices.
234      * @throws ServiceSpecificException      in case of failure, with an error code indicating the
235      *                                       cause of the failure.
236      */
getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap, final int chain, final int uid)237     public static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
238             final int chain, final int uid) {
239         throwIfPreT("getUidRule is not available on pre-T devices");
240 
241         final long match = getMatchByFirewallChain(chain);
242         final boolean isAllowList = isFirewallAllowList(chain);
243         try {
244             final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
245             final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
246             return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
247         } catch (ErrnoException e) {
248             throw new ServiceSpecificException(e.errno,
249                     "Unable to get uid rule status: " + Os.strerror(e.errno));
250         }
251     }
252 
253     /**
254      * Get blocked reasons for specified uid
255      *
256      * @param uid Target Uid
257      * @return Reasons of network access blocking for an UID
258      */
getUidNetworkingBlockedReasons(final int uid, IBpfMap<S32, U32> configurationMap, IBpfMap<S32, UidOwnerValue> uidOwnerMap, IBpfMap<S32, U8> dataSaverEnabledMap )259     public static int getUidNetworkingBlockedReasons(final int uid,
260             IBpfMap<S32, U32> configurationMap,
261             IBpfMap<S32, UidOwnerValue> uidOwnerMap,
262             IBpfMap<S32, U8> dataSaverEnabledMap
263     ) {
264         final long uidRuleConfig;
265         final long uidMatch;
266         try {
267             uidRuleConfig = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
268             final UidOwnerValue value = uidOwnerMap.getValue(new Struct.S32(uid));
269             uidMatch = (value != null) ? value.rule : 0L;
270         } catch (ErrnoException e) {
271             throw new ServiceSpecificException(e.errno,
272                     "Unable to get firewall chain status: " + Os.strerror(e.errno));
273         }
274         final long blockingMatches = (uidRuleConfig & ~uidMatch & sMaskDropIfUnset)
275                 | (uidRuleConfig & uidMatch & sMaskDropIfSet);
276 
277         int blockedReasons = BLOCKED_REASON_NONE;
278         if ((blockingMatches & POWERSAVE_MATCH) != 0) {
279             blockedReasons |= BLOCKED_REASON_BATTERY_SAVER;
280         }
281         if ((blockingMatches & DOZABLE_MATCH) != 0) {
282             blockedReasons |= BLOCKED_REASON_DOZE;
283         }
284         if ((blockingMatches & STANDBY_MATCH) != 0) {
285             blockedReasons |= BLOCKED_REASON_APP_STANDBY;
286         }
287         if ((blockingMatches & RESTRICTED_MATCH) != 0) {
288             blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
289         }
290         if ((blockingMatches & LOW_POWER_STANDBY_MATCH) != 0) {
291             blockedReasons |= BLOCKED_REASON_LOW_POWER_STANDBY;
292         }
293         if ((blockingMatches & BACKGROUND_MATCH) != 0) {
294             blockedReasons |= BLOCKED_REASON_APP_BACKGROUND;
295         }
296         if ((blockingMatches & (OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)) != 0) {
297             blockedReasons |= BLOCKED_REASON_OEM_DENY;
298         }
299 
300         // Metered chains are not enabled by configuration map currently.
301         if ((uidMatch & PENALTY_BOX_USER_MATCH) != 0) {
302             blockedReasons |= BLOCKED_METERED_REASON_USER_RESTRICTED;
303         }
304         if ((uidMatch & PENALTY_BOX_ADMIN_MATCH) != 0) {
305             blockedReasons |= BLOCKED_METERED_REASON_ADMIN_DISABLED;
306         }
307         if ((uidMatch & HAPPY_BOX_MATCH) == 0 && getDataSaverEnabled(dataSaverEnabledMap)) {
308             blockedReasons |= BLOCKED_METERED_REASON_DATA_SAVER;
309         }
310 
311         return blockedReasons;
312     }
313 
314     /**
315      * Return whether the network is blocked by firewall chains for the given uid.
316      *
317      * Note that {@link #getDataSaverEnabled(IBpfMap)} has a latency before V.
318      *
319      * @param uid The target uid.
320      * @param isNetworkMetered Whether the target network is metered.
321      *
322      * @return True if the network is blocked. Otherwise, false.
323      * @throws ServiceSpecificException if the read fails.
324      *
325      * @hide
326      */
isUidNetworkingBlocked(final int uid, boolean isNetworkMetered, IBpfMap<S32, U32> configurationMap, IBpfMap<S32, UidOwnerValue> uidOwnerMap, IBpfMap<S32, U8> dataSaverEnabledMap )327     public static boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
328             IBpfMap<S32, U32> configurationMap,
329             IBpfMap<S32, UidOwnerValue> uidOwnerMap,
330             IBpfMap<S32, U8> dataSaverEnabledMap
331     ) {
332         throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
333 
334         // System uids are not blocked by firewall chains, see bpf_progs/netd.c
335         // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
336         if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
337             return false;
338         }
339 
340         final int blockedReasons = getUidNetworkingBlockedReasons(
341                 uid,
342                 configurationMap,
343                 uidOwnerMap,
344                 dataSaverEnabledMap);
345         if (isNetworkMetered) {
346             return blockedReasons != BLOCKED_REASON_NONE;
347         } else {
348             return (blockedReasons & ~BLOCKED_METERED_REASON_MASK) != BLOCKED_REASON_NONE;
349         }
350     }
351 
352     /**
353      * Get Data Saver enabled or disabled
354      *
355      * Note that before V, the data saver status in bpf is written by ConnectivityService
356      * when receiving {@link ConnectivityManager#ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
357      * the status is not synchronized.
358      * On V+, the data saver status is set by platform code when enabling/disabling
359      * data saver, which is synchronized.
360      *
361      * @return whether Data Saver is enabled or disabled.
362      * @throws ServiceSpecificException in case of failure, with an error code indicating the
363      *                                  cause of the failure.
364      */
getDataSaverEnabled(IBpfMap<S32, U8> dataSaverEnabledMap)365     public static boolean getDataSaverEnabled(IBpfMap<S32, U8> dataSaverEnabledMap) {
366         throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
367 
368         try {
369             return dataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
370         } catch (ErrnoException e) {
371             throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
372                     + Os.strerror(e.errno));
373         }
374     }
375 }
376