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