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