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