From 96b6c39229df3a96388649b128840ed8202e5736 Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Tue, 7 Nov 2023 15:45:54 +0900 Subject: [PATCH 1/3] Remove native code path in BpfNetMaps.java BpfNetMaps uses java library for updating bpf map as the default code path. This is already released to 100% production without any issues. So this CL removes the old code path from BpfNetMaps. Note that native_init only opens the bpf maps if sEnableJavaBpfMap is true. But if sEnableJavaBpfMap is true, no native code in TrafficController access the bpf map. So this CL can remove native_init. Following CL will remove the nativce codes for old code path. Bug: 217624062 Test: NetworkStaticLibsTests Change-Id: Ifba198d9e6f93b53fd1dbf3b2aafb644da0b147d --- .../src/com/android/server/BpfNetMaps.java | 376 +++++------------- .../com/android/server/BpfNetMapsTest.java | 1 - 2 files changed, 109 insertions(+), 268 deletions(-) diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index 14ab2a1075..d1218a2b75 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -34,14 +34,6 @@ import static android.net.BpfNetMapsUtils.PRE_T; import static android.net.BpfNetMapsUtils.getMatchByFirewallChain; import static android.net.BpfNetMapsUtils.isFirewallAllowList; import static android.net.BpfNetMapsUtils.matchToString; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; -import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW; import static android.net.ConnectivityManager.FIREWALL_RULE_DENY; import static android.net.INetd.PERMISSION_INTERNET; @@ -75,10 +67,8 @@ import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.BackgroundThread; -import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BpfDump; import com.android.net.module.util.BpfMap; -import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.IBpfMap; import com.android.net.module.util.Struct; import com.android.net.module.util.Struct.S32; @@ -112,7 +102,6 @@ public class BpfNetMaps { // Use legacy netd for releases before T. private static boolean sInitialized = false; - private static Boolean sEnableJavaBpfMap = null; private static final String BPF_NET_MAPS_FORCE_DISABLE_JAVA_BPF_MAP = "bpf_net_maps_force_disable_java_bpf_map"; @@ -143,14 +132,6 @@ public class BpfNetMaps { Pair.create(PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS") ); - /** - * Set sEnableJavaBpfMap for test. - */ - @VisibleForTesting - public static void setEnableJavaBpfMapForTest(boolean enable) { - sEnableJavaBpfMap = enable; - } - /** * Set configurationMap for test. */ @@ -287,20 +268,12 @@ public class BpfNetMaps { */ private static synchronized void ensureInitialized(final Context context) { if (sInitialized) return; - if (sEnableJavaBpfMap == null) { - sEnableJavaBpfMap = SdkLevel.isAtLeastU() || - DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, - BPF_NET_MAPS_FORCE_DISABLE_JAVA_BPF_MAP); - } - Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap); - initBpfMaps(); - native_init(!sEnableJavaBpfMap /* startSkDestroyListener */); sInitialized = true; } public boolean isSkDestroyListenerRunning() { - return !sEnableJavaBpfMap; + return false; } /** @@ -437,12 +410,7 @@ public class BpfNetMaps { public void addNaughtyApp(final int uid) { throwIfPreT("addNaughtyApp is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - addRule(uid, PENALTY_BOX_MATCH, "addNaughtyApp"); - } else { - final int err = native_addNaughtyApp(uid); - maybeThrow(err, "Unable to add naughty app"); - } + addRule(uid, PENALTY_BOX_MATCH, "addNaughtyApp"); } /** @@ -456,12 +424,7 @@ public class BpfNetMaps { public void removeNaughtyApp(final int uid) { throwIfPreT("removeNaughtyApp is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - removeRule(uid, PENALTY_BOX_MATCH, "removeNaughtyApp"); - } else { - final int err = native_removeNaughtyApp(uid); - maybeThrow(err, "Unable to remove naughty app"); - } + removeRule(uid, PENALTY_BOX_MATCH, "removeNaughtyApp"); } /** @@ -475,12 +438,7 @@ public class BpfNetMaps { public void addNiceApp(final int uid) { throwIfPreT("addNiceApp is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - addRule(uid, HAPPY_BOX_MATCH, "addNiceApp"); - } else { - final int err = native_addNiceApp(uid); - maybeThrow(err, "Unable to add nice app"); - } + addRule(uid, HAPPY_BOX_MATCH, "addNiceApp"); } /** @@ -494,12 +452,7 @@ public class BpfNetMaps { public void removeNiceApp(final int uid) { throwIfPreT("removeNiceApp is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - removeRule(uid, HAPPY_BOX_MATCH, "removeNiceApp"); - } else { - final int err = native_removeNiceApp(uid); - maybeThrow(err, "Unable to remove nice app"); - } + removeRule(uid, HAPPY_BOX_MATCH, "removeNiceApp"); } /** @@ -515,21 +468,16 @@ public class BpfNetMaps { public void setChildChain(final int childChain, final boolean enable) { throwIfPreT("setChildChain is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - final long match = getMatchByFirewallChain(childChain); - try { - synchronized (sUidRulesConfigBpfMapLock) { - final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY); - final long newConfig = enable ? (config.val | match) : (config.val & ~match); - sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(newConfig)); - } - } catch (ErrnoException e) { - throw new ServiceSpecificException(e.errno, - "Unable to set child chain: " + Os.strerror(e.errno)); + final long match = getMatchByFirewallChain(childChain); + try { + synchronized (sUidRulesConfigBpfMapLock) { + final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY); + final long newConfig = enable ? (config.val | match) : (config.val & ~match); + sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(newConfig)); } - } else { - final int err = native_setChildChain(childChain, enable); - maybeThrow(err, "Unable to set child chain"); + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, + "Unable to set child chain: " + Os.strerror(e.errno)); } } @@ -572,75 +520,38 @@ public class BpfNetMaps { public void replaceUidChain(final int chain, final int[] uids) { throwIfPreT("replaceUidChain is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - final long match; - try { - match = getMatchByFirewallChain(chain); - } catch (ServiceSpecificException e) { - // Throws IllegalArgumentException to keep the behavior of - // ConnectivityManager#replaceFirewallChain API - throw new IllegalArgumentException("Invalid firewall chain: " + chain); - } - final Set uidSet = asSet(uids); - final Set uidSetToRemoveRule = new ArraySet<>(); - try { - synchronized (sUidOwnerMap) { - sUidOwnerMap.forEach((uid, config) -> { - // config could be null if there is a concurrent entry deletion. - // http://b/220084230. But sUidOwnerMap update must be done while holding a - // lock, so this should not happen. - if (config == null) { - Log.wtf(TAG, "sUidOwnerMap entry was deleted while holding a lock"); - } else if (!uidSet.contains((int) uid.val) && (config.rule & match) != 0) { - uidSetToRemoveRule.add((int) uid.val); - } - }); + final long match; + try { + match = getMatchByFirewallChain(chain); + } catch (ServiceSpecificException e) { + // Throws IllegalArgumentException to keep the behavior of + // ConnectivityManager#replaceFirewallChain API + throw new IllegalArgumentException("Invalid firewall chain: " + chain); + } + final Set uidSet = asSet(uids); + final Set uidSetToRemoveRule = new ArraySet<>(); + try { + synchronized (sUidOwnerMap) { + sUidOwnerMap.forEach((uid, config) -> { + // config could be null if there is a concurrent entry deletion. + // http://b/220084230. But sUidOwnerMap update must be done while holding a + // lock, so this should not happen. + if (config == null) { + Log.wtf(TAG, "sUidOwnerMap entry was deleted while holding a lock"); + } else if (!uidSet.contains((int) uid.val) && (config.rule & match) != 0) { + uidSetToRemoveRule.add((int) uid.val); + } + }); - for (final int uid : uidSetToRemoveRule) { - removeRule(uid, match, "replaceUidChain"); - } - for (final int uid : uids) { - addRule(uid, match, "replaceUidChain"); - } + for (final int uid : uidSetToRemoveRule) { + removeRule(uid, match, "replaceUidChain"); + } + for (final int uid : uids) { + addRule(uid, match, "replaceUidChain"); } - } catch (ErrnoException | ServiceSpecificException e) { - Log.e(TAG, "replaceUidChain failed: " + e); - } - } else { - final int err; - switch (chain) { - case FIREWALL_CHAIN_DOZABLE: - err = native_replaceUidChain("fw_dozable", true /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_STANDBY: - err = native_replaceUidChain("fw_standby", false /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_POWERSAVE: - err = native_replaceUidChain("fw_powersave", true /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_RESTRICTED: - err = native_replaceUidChain("fw_restricted", true /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_LOW_POWER_STANDBY: - err = native_replaceUidChain( - "fw_low_power_standby", true /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_OEM_DENY_1: - err = native_replaceUidChain("fw_oem_deny_1", false /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_OEM_DENY_2: - err = native_replaceUidChain("fw_oem_deny_2", false /* isAllowList */, uids); - break; - case FIREWALL_CHAIN_OEM_DENY_3: - err = native_replaceUidChain("fw_oem_deny_3", false /* isAllowList */, uids); - break; - default: - throw new IllegalArgumentException("replaceFirewallChain with invalid chain: " - + chain); - } - if (err != 0) { - Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err)); } + } catch (ErrnoException | ServiceSpecificException e) { + Log.e(TAG, "replaceUidChain failed: " + e); } } @@ -657,20 +568,15 @@ public class BpfNetMaps { public void setUidRule(final int childChain, final int uid, final int firewallRule) { throwIfPreT("setUidRule is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - final long match = getMatchByFirewallChain(childChain); - final boolean isAllowList = isFirewallAllowList(childChain); - final boolean add = (firewallRule == FIREWALL_RULE_ALLOW && isAllowList) - || (firewallRule == FIREWALL_RULE_DENY && !isAllowList); + final long match = getMatchByFirewallChain(childChain); + final boolean isAllowList = isFirewallAllowList(childChain); + final boolean add = (firewallRule == FIREWALL_RULE_ALLOW && isAllowList) + || (firewallRule == FIREWALL_RULE_DENY && !isAllowList); - if (add) { - addRule(uid, match, "setUidRule"); - } else { - removeRule(uid, match, "setUidRule"); - } + if (add) { + addRule(uid, match, "setUidRule"); } else { - final int err = native_setUidRule(childChain, uid, firewallRule); - maybeThrow(err, "Unable to set uid rule"); + removeRule(uid, match, "setUidRule"); } } @@ -773,29 +679,24 @@ public class BpfNetMaps { return; } - if (sEnableJavaBpfMap) { - // Null ifName is a wildcard to allow apps to receive packets on all interfaces and - // ifIndex is set to 0. - final int ifIndex; - if (ifName == null) { - ifIndex = 0; - } else { - ifIndex = mDeps.getIfIndex(ifName); - if (ifIndex == 0) { - throw new ServiceSpecificException(ENODEV, - "Failed to get index of interface " + ifName); - } - } - for (final int uid : uids) { - try { - addRule(uid, IIF_MATCH, ifIndex, "addUidInterfaceRules"); - } catch (ServiceSpecificException e) { - Log.e(TAG, "addRule failed uid=" + uid + " ifName=" + ifName + ", " + e); - } - } + // Null ifName is a wildcard to allow apps to receive packets on all interfaces and + // ifIndex is set to 0. + final int ifIndex; + if (ifName == null) { + ifIndex = 0; } else { - final int err = native_addUidInterfaceRules(ifName, uids); - maybeThrow(err, "Unable to add uid interface rules"); + ifIndex = mDeps.getIfIndex(ifName); + if (ifIndex == 0) { + throw new ServiceSpecificException(ENODEV, + "Failed to get index of interface " + ifName); + } + } + for (final int uid : uids) { + try { + addRule(uid, IIF_MATCH, ifIndex, "addUidInterfaceRules"); + } catch (ServiceSpecificException e) { + Log.e(TAG, "addRule failed uid=" + uid + " ifName=" + ifName + ", " + e); + } } } @@ -816,17 +717,12 @@ public class BpfNetMaps { return; } - if (sEnableJavaBpfMap) { - for (final int uid : uids) { - try { - removeRule(uid, IIF_MATCH, "removeUidInterfaceRules"); - } catch (ServiceSpecificException e) { - Log.e(TAG, "removeRule failed uid=" + uid + ", " + e); - } + for (final int uid : uids) { + try { + removeRule(uid, IIF_MATCH, "removeUidInterfaceRules"); + } catch (ServiceSpecificException e) { + Log.e(TAG, "removeRule failed uid=" + uid + ", " + e); } - } else { - final int err = native_removeUidInterfaceRules(uids); - maybeThrow(err, "Unable to remove uid interface rules"); } } @@ -842,15 +738,10 @@ public class BpfNetMaps { public void updateUidLockdownRule(final int uid, final boolean add) { throwIfPreT("updateUidLockdownRule is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - if (add) { - addRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule"); - } else { - removeRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule"); - } + if (add) { + addRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule"); } else { - final int err = native_updateUidLockdownRule(uid, add); - maybeThrow(err, "Unable to update lockdown rule"); + removeRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule"); } } @@ -865,33 +756,28 @@ public class BpfNetMaps { public void swapActiveStatsMap() { throwIfPreT("swapActiveStatsMap is not available on pre-T devices"); - if (sEnableJavaBpfMap) { - try { - synchronized (sCurrentStatsMapConfigLock) { - final long config = sConfigurationMap.getValue( - CURRENT_STATS_MAP_CONFIGURATION_KEY).val; - final long newConfig = (config == STATS_SELECT_MAP_A) - ? STATS_SELECT_MAP_B : STATS_SELECT_MAP_A; - sConfigurationMap.updateEntry(CURRENT_STATS_MAP_CONFIGURATION_KEY, - new U32(newConfig)); - } - } catch (ErrnoException e) { - throw new ServiceSpecificException(e.errno, "Failed to swap active stats map"); + try { + synchronized (sCurrentStatsMapConfigLock) { + final long config = sConfigurationMap.getValue( + CURRENT_STATS_MAP_CONFIGURATION_KEY).val; + final long newConfig = (config == STATS_SELECT_MAP_A) + ? STATS_SELECT_MAP_B : STATS_SELECT_MAP_A; + sConfigurationMap.updateEntry(CURRENT_STATS_MAP_CONFIGURATION_KEY, + new U32(newConfig)); } - - // After changing the config, it's needed to make sure all the current running eBPF - // programs are finished and all the CPUs are aware of this config change before the old - // map is modified. So special hack is needed here to wait for the kernel to do a - // synchronize_rcu(). Once the kernel called synchronize_rcu(), the updated config will - // be available to all cores and the next eBPF programs triggered inside the kernel will - // use the new map configuration. So once this function returns it is safe to modify the - // old stats map without concerning about race between the kernel and userspace. - final int err = mDeps.synchronizeKernelRCU(); - maybeThrow(err, "synchronizeKernelRCU failed"); - } else { - final int err = native_swapActiveStatsMap(); - maybeThrow(err, "Unable to swap active stats map"); + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, "Failed to swap active stats map"); } + + // After changing the config, it's needed to make sure all the current running eBPF + // programs are finished and all the CPUs are aware of this config change before the old + // map is modified. So special hack is needed here to wait for the kernel to do a + // synchronize_rcu(). Once the kernel called synchronize_rcu(), the updated config will + // be available to all cores and the next eBPF programs triggered inside the kernel will + // use the new map configuration. So once this function returns it is safe to modify the + // old stats map without concerning about race between the kernel and userspace. + final int err = mDeps.synchronizeKernelRCU(); + maybeThrow(err, "synchronizeKernelRCU failed"); } /** @@ -910,29 +796,25 @@ public class BpfNetMaps { return; } - if (sEnableJavaBpfMap) { - // Remove the entry if package is uninstalled or uid has only INTERNET permission. - if (permissions == PERMISSION_UNINSTALLED || permissions == PERMISSION_INTERNET) { - for (final int uid : uids) { - try { - sUidPermissionMap.deleteEntry(new S32(uid)); - } catch (ErrnoException e) { - Log.e(TAG, "Failed to remove uid " + uid + " from permission map: " + e); - } - } - return; - } - + // Remove the entry if package is uninstalled or uid has only INTERNET permission. + if (permissions == PERMISSION_UNINSTALLED || permissions == PERMISSION_INTERNET) { for (final int uid : uids) { try { - sUidPermissionMap.updateEntry(new S32(uid), new U8((short) permissions)); + sUidPermissionMap.deleteEntry(new S32(uid)); } catch (ErrnoException e) { - Log.e(TAG, "Failed to set permission " - + permissions + " to uid " + uid + ": " + e); + Log.e(TAG, "Failed to remove uid " + uid + " from permission map: " + e); } } - } else { - native_setPermissionForUids(permissions, uids); + return; + } + + for (final int uid : uids) { + try { + sUidPermissionMap.updateEntry(new S32(uid), new U8((short) permissions)); + } catch (ErrnoException e) { + Log.e(TAG, "Failed to set permission " + + permissions + " to uid " + uid + ": " + e); + } } } @@ -1069,7 +951,6 @@ public class BpfNetMaps { pw.println("TrafficController"); // required by CTS testDumpBpfNetMaps pw.println(); - pw.println("sEnableJavaBpfMap: " + sEnableJavaBpfMap); if (verbose) { pw.println(); pw.println("BPF map content:"); @@ -1104,45 +985,6 @@ public class BpfNetMaps { } } - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private static native void native_init(boolean startSkDestroyListener); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_addNaughtyApp(int uid); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_removeNaughtyApp(int uid); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_addNiceApp(int uid); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_removeNiceApp(int uid); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_setChildChain(int childChain, boolean enable); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_setUidRule(int childChain, int uid, int firewallRule); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_addUidInterfaceRules(String ifName, int[] uids); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_removeUidInterfaceRules(int[] uids); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_updateUidLockdownRule(int uid, boolean add); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native int native_swapActiveStatsMap(); - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private native void native_setPermissionForUids(int permissions, int[] uids); - @RequiresApi(Build.VERSION_CODES.TIRAMISU) private static native int native_synchronizeKernelRCU(); } diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java index 1dfc8c0160..b77162c45a 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -149,7 +149,6 @@ public final class BpfNetMapsTest { MockitoAnnotations.initMocks(this); doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME); doReturn(0).when(mDeps).synchronizeKernelRCU(); - BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */); BpfNetMaps.setConfigurationMapForTest(mConfigurationMap); mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0)); mConfigurationMap.updateEntry( From 424038fe25f232a996822120106abb49ff851631 Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Tue, 7 Nov 2023 17:37:54 +0900 Subject: [PATCH 2/3] Remove TrafficController BpfNetMaps now updates bpf map by Java library and does not use TrafficController. Bug: 217624062 Test: TH Change-Id: Id727cf7b79592e913c967156e6e224de338f5f65 --- TEST_MAPPING | 10 - service/Android.bp | 1 - service/jni/com_android_server_BpfNetMaps.cpp | 194 +---- service/native/Android.bp | 84 -- service/native/TrafficController.cpp | 577 ------------ service/native/TrafficControllerTest.cpp | 822 ------------------ service/native/include/TrafficController.h | 185 ---- 7 files changed, 1 insertion(+), 1872 deletions(-) delete mode 100644 service/native/Android.bp delete mode 100644 service/native/TrafficController.cpp delete mode 100644 service/native/TrafficControllerTest.cpp delete mode 100644 service/native/include/TrafficController.h diff --git a/TEST_MAPPING b/TEST_MAPPING index 520124dc3c..ab3ed66bfc 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -99,9 +99,6 @@ { "name": "TetheringIntegrationTests" }, - { - "name": "traffic_controller_unit_test" - }, { "name": "libnetworkstats_test" }, @@ -133,10 +130,6 @@ { "name": "dns_helper_unit_test" }, - { - "name": "traffic_controller_unit_test", - "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"] - }, { "name": "FrameworksNetDeflakeTest" }, @@ -273,9 +266,6 @@ } ] }, - { - "name": "traffic_controller_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" - }, { "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" }, diff --git a/service/Android.bp b/service/Android.bp index 7def20053f..15ae501564 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -124,7 +124,6 @@ cc_library_shared { "libmodules-utils-build", "libnetjniutils", "libnet_utils_device_common_bpfjni", - "libtraffic_controller", "netd_aidl_interface-lateststable-ndk", ], shared_libs: [ diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp index 50a0635070..29f6a606ff 100644 --- a/service/jni/com_android_server_BpfNetMaps.cpp +++ b/service/jni/com_android_server_BpfNetMaps.cpp @@ -14,179 +14,13 @@ * limitations under the License. */ -#define LOG_TAG "TrafficControllerJni" - -#include "TrafficController.h" - -#include "netd.h" +#include "bpf/BpfUtils.h" #include -#include #include -#include -#include -#include -#include -#include -#include -#include - - -using android::net::TrafficController; -using android::netdutils::Status; - -using UidOwnerMatchType::PENALTY_BOX_MATCH; -using UidOwnerMatchType::HAPPY_BOX_MATCH; - -static android::net::TrafficController mTc; namespace android { -#define CHECK_LOG(status) \ - do { \ - if (!isOk(status)) \ - ALOGE("%s failed, error code = %d", __func__, status.code()); \ - } while (0) - -static void native_init(JNIEnv* env, jclass clazz, jboolean startSkDestroyListener) { - Status status = mTc.start(startSkDestroyListener); - CHECK_LOG(status); - if (!isOk(status)) { - uid_t uid = getuid(); - ALOGE("BpfNetMaps jni init failure as uid=%d", uid); - // We probably only ever get called from system_server (ie. AID_SYSTEM) - // or from tests, and never from network_stack (ie. AID_NETWORK_STACK). - // However, if we ever do add calls from production network_stack code - // we do want to make sure this initializes correctly. - // TODO: Fix tests to not use this jni lib, so we can unconditionally abort() - if (uid == AID_SYSTEM || uid == AID_NETWORK_STACK) abort(); - } -} - -static jint native_addNaughtyApp(JNIEnv* env, jobject self, jint uid) { - const uint32_t appUids = static_cast(abs(uid)); - Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOp::IptOpInsert); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_removeNaughtyApp(JNIEnv* env, jobject self, jint uid) { - const uint32_t appUids = static_cast(abs(uid)); - Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOp::IptOpDelete); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_addNiceApp(JNIEnv* env, jobject self, jint uid) { - const uint32_t appUids = static_cast(abs(uid)); - Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, - TrafficController::IptOp::IptOpInsert); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_removeNiceApp(JNIEnv* env, jobject self, jint uid) { - const uint32_t appUids = static_cast(abs(uid)); - Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, - TrafficController::IptOp::IptOpDelete); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_setChildChain(JNIEnv* env, jobject self, jint childChain, jboolean enable) { - auto chain = static_cast(childChain); - int res = mTc.toggleUidOwnerMap(chain, enable); - if (res) ALOGE("%s failed, error code = %d", __func__, res); - return (jint)res; -} - -static jint native_replaceUidChain(JNIEnv* env, jobject self, jstring name, jboolean isAllowlist, - jintArray jUids) { - const ScopedUtfChars chainNameUtf8(env, name); - if (chainNameUtf8.c_str() == nullptr) return -EINVAL; - const std::string chainName(chainNameUtf8.c_str()); - - ScopedIntArrayRO uids(env, jUids); - if (uids.get() == nullptr) return -EINVAL; - - size_t size = uids.size(); - static_assert(sizeof(*(uids.get())) == sizeof(int32_t)); - std::vector data ((int32_t *)&uids[0], (int32_t*)&uids[size]); - int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data); - if (res) ALOGE("%s failed, error code = %d", __func__, res); - return (jint)res; -} - -static jint native_setUidRule(JNIEnv* env, jobject self, jint childChain, jint uid, - jint firewallRule) { - auto chain = static_cast(childChain); - auto rule = static_cast(firewallRule); - FirewallType fType = mTc.getFirewallType(chain); - - int res = mTc.changeUidOwnerRule(chain, uid, rule, fType); - if (res) ALOGE("%s failed, error code = %d", __func__, res); - return (jint)res; -} - -static jint native_addUidInterfaceRules(JNIEnv* env, jobject self, jstring ifName, - jintArray jUids) { - // Null ifName is a wildcard to allow apps to receive packets on all interfaces and ifIndex is - // set to 0. - int ifIndex = 0; - if (ifName != nullptr) { - const ScopedUtfChars ifNameUtf8(env, ifName); - const std::string interfaceName(ifNameUtf8.c_str()); - ifIndex = if_nametoindex(interfaceName.c_str()); - } - - ScopedIntArrayRO uids(env, jUids); - if (uids.get() == nullptr) return -EINVAL; - - size_t size = uids.size(); - static_assert(sizeof(*(uids.get())) == sizeof(int32_t)); - std::vector data ((int32_t *)&uids[0], (int32_t*)&uids[size]); - Status status = mTc.addUidInterfaceRules(ifIndex, data); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_removeUidInterfaceRules(JNIEnv* env, jobject self, jintArray jUids) { - ScopedIntArrayRO uids(env, jUids); - if (uids.get() == nullptr) return -EINVAL; - - size_t size = uids.size(); - static_assert(sizeof(*(uids.get())) == sizeof(int32_t)); - std::vector data ((int32_t *)&uids[0], (int32_t*)&uids[size]); - Status status = mTc.removeUidInterfaceRules(data); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_updateUidLockdownRule(JNIEnv* env, jobject self, jint uid, jboolean add) { - Status status = mTc.updateUidLockdownRule(uid, add); - CHECK_LOG(status); - return (jint)status.code(); -} - -static jint native_swapActiveStatsMap(JNIEnv* env, jobject self) { - Status status = mTc.swapActiveStatsMap(); - CHECK_LOG(status); - return (jint)status.code(); -} - -static void native_setPermissionForUids(JNIEnv* env, jobject self, jint permission, - jintArray jUids) { - ScopedIntArrayRO uids(env, jUids); - if (uids.get() == nullptr) return; - - size_t size = uids.size(); - static_assert(sizeof(*(uids.get())) == sizeof(uid_t)); - std::vector data ((uid_t *)&uids[0], (uid_t*)&uids[size]); - mTc.setPermissionForUids(permission, data); -} - static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) { return -bpf::synchronizeKernelRCU(); } @@ -197,32 +31,6 @@ static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) { // clang-format off static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"native_init", "(Z)V", - (void*)native_init}, - {"native_addNaughtyApp", "(I)I", - (void*)native_addNaughtyApp}, - {"native_removeNaughtyApp", "(I)I", - (void*)native_removeNaughtyApp}, - {"native_addNiceApp", "(I)I", - (void*)native_addNiceApp}, - {"native_removeNiceApp", "(I)I", - (void*)native_removeNiceApp}, - {"native_setChildChain", "(IZ)I", - (void*)native_setChildChain}, - {"native_replaceUidChain", "(Ljava/lang/String;Z[I)I", - (void*)native_replaceUidChain}, - {"native_setUidRule", "(III)I", - (void*)native_setUidRule}, - {"native_addUidInterfaceRules", "(Ljava/lang/String;[I)I", - (void*)native_addUidInterfaceRules}, - {"native_removeUidInterfaceRules", "([I)I", - (void*)native_removeUidInterfaceRules}, - {"native_updateUidLockdownRule", "(IZ)I", - (void*)native_updateUidLockdownRule}, - {"native_swapActiveStatsMap", "()I", - (void*)native_swapActiveStatsMap}, - {"native_setPermissionForUids", "(I[I)V", - (void*)native_setPermissionForUids}, {"native_synchronizeKernelRCU", "()I", (void*)native_synchronizeKernelRCU}, }; diff --git a/service/native/Android.bp b/service/native/Android.bp deleted file mode 100644 index 697fcbd898..0000000000 --- a/service/native/Android.bp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -cc_library { - name: "libtraffic_controller", - defaults: ["netd_defaults"], - srcs: [ - "TrafficController.cpp", - ], - header_libs: [ - "bpf_connectivity_headers", - ], - static_libs: [ - // TrafficController would use the constants of INetd so that add - // netd_aidl_interface-lateststable-ndk. - "netd_aidl_interface-lateststable-ndk", - ], - shared_libs: [ - // TODO: Find a good way to remove libbase. - "libbase", - "libcutils", - "libnetdutils", - "libutils", - "liblog", - ], - export_include_dirs: ["include"], - sanitize: { - cfi: true, - }, - apex_available: [ - "com.android.tethering", - ], - min_sdk_version: "30", -} - -cc_test { - name: "traffic_controller_unit_test", - test_suites: ["general-tests", "mts-tethering"], - test_config_template: ":net_native_test_config_template", - require_root: true, - local_include_dirs: ["include"], - header_libs: [ - "bpf_connectivity_headers", - ], - srcs: [ - "TrafficControllerTest.cpp", - ], - static_libs: [ - "libbase", - "libgmock", - "liblog", - "libnetdutils", - "libtraffic_controller", - "libutils", - "libnetd_updatable", - "netd_aidl_interface-lateststable-ndk", - ], - compile_multilib: "both", - multilib: { - lib32: { - suffix: "32", - }, - lib64: { - suffix: "64", - }, - }, -} diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp deleted file mode 100644 index 8cd698e47f..0000000000 --- a/service/native/TrafficController.cpp +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "TrafficController" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "TrafficController.h" -#include "bpf/BpfMap.h" - -namespace android { -namespace net { - -using base::StringPrintf; -using base::unique_fd; -using bpf::BpfMap; -using bpf::synchronizeKernelRCU; -using netdutils::NetlinkListener; -using netdutils::NetlinkListenerInterface; -using netdutils::Slice; -using netdutils::sSyscalls; -using netdutils::Status; -using netdutils::statusFromErrno; -using netdutils::StatusOr; - -constexpr int kSockDiagMsgType = SOCK_DIAG_BY_FAMILY; -constexpr int kSockDiagDoneMsgType = NLMSG_DONE; - -const char* TrafficController::LOCAL_DOZABLE = "fw_dozable"; -const char* TrafficController::LOCAL_STANDBY = "fw_standby"; -const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave"; -const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted"; -const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby"; -const char* TrafficController::LOCAL_OEM_DENY_1 = "fw_oem_deny_1"; -const char* TrafficController::LOCAL_OEM_DENY_2 = "fw_oem_deny_2"; -const char* TrafficController::LOCAL_OEM_DENY_3 = "fw_oem_deny_3"; - -static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET, - "Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET"); -static_assert(BPF_PERMISSION_UPDATE_DEVICE_STATS == INetd::PERMISSION_UPDATE_DEVICE_STATS, - "Mismatch between BPF and AIDL permissions: PERMISSION_UPDATE_DEVICE_STATS"); - -#define FLAG_MSG_TRANS(result, flag, value) \ - do { \ - if ((value) & (flag)) { \ - (result).append(" " #flag); \ - (value) &= ~(flag); \ - } \ - } while (0) - -const std::string uidMatchTypeToString(uint32_t match) { - std::string matchType; - FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match); - FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match); - FLAG_MSG_TRANS(matchType, DOZABLE_MATCH, match); - FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match); - FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match); - FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match); - FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match); - FLAG_MSG_TRANS(matchType, IIF_MATCH, match); - FLAG_MSG_TRANS(matchType, LOCKDOWN_VPN_MATCH, match); - FLAG_MSG_TRANS(matchType, OEM_DENY_1_MATCH, match); - FLAG_MSG_TRANS(matchType, OEM_DENY_2_MATCH, match); - FLAG_MSG_TRANS(matchType, OEM_DENY_3_MATCH, match); - if (match) { - return StringPrintf("Unknown match: %u", match); - } - return matchType; -} - -const std::string UidPermissionTypeToString(int permission) { - if (permission == INetd::PERMISSION_NONE) { - return "PERMISSION_NONE"; - } - if (permission == INetd::PERMISSION_UNINSTALLED) { - // This should never appear in the map, complain loudly if it does. - return "PERMISSION_UNINSTALLED error!"; - } - std::string permissionType; - FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_INTERNET, permission); - FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_UPDATE_DEVICE_STATS, permission); - if (permission) { - return StringPrintf("Unknown permission: %u", permission); - } - return permissionType; -} - -StatusOr> TrafficController::makeSkDestroyListener() { - const auto& sys = sSyscalls.get(); - ASSIGN_OR_RETURN(auto event, sys.eventfd(0, EFD_CLOEXEC)); - const int domain = AF_NETLINK; - const int type = SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK; - const int protocol = NETLINK_INET_DIAG; - ASSIGN_OR_RETURN(auto sock, sys.socket(domain, type, protocol)); - - // TODO: if too many sockets are closed too quickly, we can overflow the socket buffer, and - // some entries in mCookieTagMap will not be freed. In order to fix this we would need to - // periodically dump all sockets and remove the tag entries for sockets that have been closed. - // For now, set a large-enough buffer that we can close hundreds of sockets without getting - // ENOBUFS and leaking mCookieTagMap entries. - int rcvbuf = 512 * 1024; - auto ret = sys.setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); - if (!ret.ok()) { - ALOGW("Failed to set SkDestroyListener buffer size to %d: %s", rcvbuf, ret.msg().c_str()); - } - - sockaddr_nl addr = { - .nl_family = AF_NETLINK, - .nl_groups = 1 << (SKNLGRP_INET_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET_UDP_DESTROY - 1) | - 1 << (SKNLGRP_INET6_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET6_UDP_DESTROY - 1)}; - RETURN_IF_NOT_OK(sys.bind(sock, addr)); - - const sockaddr_nl kernel = {.nl_family = AF_NETLINK}; - RETURN_IF_NOT_OK(sys.connect(sock, kernel)); - - std::unique_ptr listener = - std::make_unique(std::move(event), std::move(sock), "SkDestroyListen"); - - return listener; -} - -Status TrafficController::initMaps() { - std::lock_guard guard(mMutex); - - RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH)); - RETURN_IF_NOT_OK(mUidCounterSetMap.init(UID_COUNTERSET_MAP_PATH)); - RETURN_IF_NOT_OK(mAppUidStatsMap.init(APP_UID_STATS_MAP_PATH)); - RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH)); - RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH)); - RETURN_IF_NOT_OK(mIfaceIndexNameMap.init(IFACE_INDEX_NAME_MAP_PATH)); - RETURN_IF_NOT_OK(mIfaceStatsMap.init(IFACE_STATS_MAP_PATH)); - - RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH)); - - RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH)); - RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH)); - ALOGI("%s successfully", __func__); - - return netdutils::status::ok; -} - -Status TrafficController::start(bool startSkDestroyListener) { - RETURN_IF_NOT_OK(initMaps()); - - if (!startSkDestroyListener) { - return netdutils::status::ok; - } - - auto result = makeSkDestroyListener(); - if (!isOk(result)) { - ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str()); - } else { - mSkDestroyListener = std::move(result.value()); - } - // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function. - const auto rxHandler = [this](const nlmsghdr&, const Slice msg) { - std::lock_guard guard(mMutex); - inet_diag_msg diagmsg = {}; - if (extract(msg, diagmsg) < sizeof(inet_diag_msg)) { - ALOGE("Unrecognized netlink message: %s", toString(msg).c_str()); - return; - } - uint64_t sock_cookie = static_cast(diagmsg.id.idiag_cookie[0]) | - (static_cast(diagmsg.id.idiag_cookie[1]) << 32); - - Status s = mCookieTagMap.deleteValue(sock_cookie); - if (!isOk(s) && s.code() != ENOENT) { - ALOGE("Failed to delete cookie %" PRIx64 ": %s", sock_cookie, toString(s).c_str()); - return; - } - }; - expectOk(mSkDestroyListener->subscribe(kSockDiagMsgType, rxHandler)); - - // In case multiple netlink message comes in as a stream, we need to handle the rxDone message - // properly. - const auto rxDoneHandler = [](const nlmsghdr&, const Slice msg) { - // Ignore NLMSG_DONE messages - inet_diag_msg diagmsg = {}; - extract(msg, diagmsg); - }; - expectOk(mSkDestroyListener->subscribe(kSockDiagDoneMsgType, rxDoneHandler)); - - return netdutils::status::ok; -} - -Status TrafficController::updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule, - FirewallType type) { - std::lock_guard guard(mMutex); - if ((rule == ALLOW && type == ALLOWLIST) || (rule == DENY && type == DENYLIST)) { - RETURN_IF_NOT_OK(addRule(uid, match)); - } else if ((rule == ALLOW && type == DENYLIST) || (rule == DENY && type == ALLOWLIST)) { - RETURN_IF_NOT_OK(removeRule(uid, match)); - } else { - //Cannot happen. - return statusFromErrno(EINVAL, ""); - } - return netdutils::status::ok; -} - -Status TrafficController::removeRule(uint32_t uid, UidOwnerMatchType match) { - auto oldMatch = mUidOwnerMap.readValue(uid); - if (oldMatch.ok()) { - UidOwnerValue newMatch = { - .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif, - .rule = oldMatch.value().rule & ~match, - }; - if (newMatch.rule == 0) { - RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid)); - } else { - RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY)); - } - } else { - return statusFromErrno(ENOENT, StringPrintf("uid: %u does not exist in map", uid)); - } - return netdutils::status::ok; -} - -Status TrafficController::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) { - if (match != IIF_MATCH && iif != 0) { - return statusFromErrno(EINVAL, "Non-interface match must have zero interface index"); - } - auto oldMatch = mUidOwnerMap.readValue(uid); - if (oldMatch.ok()) { - UidOwnerValue newMatch = { - .iif = (match == IIF_MATCH) ? iif : oldMatch.value().iif, - .rule = oldMatch.value().rule | match, - }; - RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY)); - } else { - UidOwnerValue newMatch = { - .iif = iif, - .rule = match, - }; - RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY)); - } - return netdutils::status::ok; -} - -Status TrafficController::updateUidOwnerMap(const uint32_t uid, - UidOwnerMatchType matchType, IptOp op) { - std::lock_guard guard(mMutex); - if (op == IptOpDelete) { - RETURN_IF_NOT_OK(removeRule(uid, matchType)); - } else if (op == IptOpInsert) { - RETURN_IF_NOT_OK(addRule(uid, matchType)); - } else { - // Cannot happen. - return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, matchType)); - } - return netdutils::status::ok; -} - -FirewallType TrafficController::getFirewallType(ChildChain chain) { - switch (chain) { - case DOZABLE: - return ALLOWLIST; - case STANDBY: - return DENYLIST; - case POWERSAVE: - return ALLOWLIST; - case RESTRICTED: - return ALLOWLIST; - case LOW_POWER_STANDBY: - return ALLOWLIST; - case OEM_DENY_1: - return DENYLIST; - case OEM_DENY_2: - return DENYLIST; - case OEM_DENY_3: - return DENYLIST; - case NONE: - default: - return DENYLIST; - } -} - -int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallRule rule, - FirewallType type) { - Status res; - switch (chain) { - case DOZABLE: - res = updateOwnerMapEntry(DOZABLE_MATCH, uid, rule, type); - break; - case STANDBY: - res = updateOwnerMapEntry(STANDBY_MATCH, uid, rule, type); - break; - case POWERSAVE: - res = updateOwnerMapEntry(POWERSAVE_MATCH, uid, rule, type); - break; - case RESTRICTED: - res = updateOwnerMapEntry(RESTRICTED_MATCH, uid, rule, type); - break; - case LOW_POWER_STANDBY: - res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type); - break; - case OEM_DENY_1: - res = updateOwnerMapEntry(OEM_DENY_1_MATCH, uid, rule, type); - break; - case OEM_DENY_2: - res = updateOwnerMapEntry(OEM_DENY_2_MATCH, uid, rule, type); - break; - case OEM_DENY_3: - res = updateOwnerMapEntry(OEM_DENY_3_MATCH, uid, rule, type); - break; - case NONE: - default: - ALOGW("Unknown child chain: %d", chain); - return -EINVAL; - } - if (!isOk(res)) { - ALOGE("change uid(%u) rule of %d failed: %s, rule: %d, type: %d", uid, chain, - res.msg().c_str(), rule, type); - return -res.code(); - } - return 0; -} - -Status TrafficController::replaceRulesInMap(const UidOwnerMatchType match, - const std::vector& uids) { - std::lock_guard guard(mMutex); - std::set uidSet(uids.begin(), uids.end()); - std::vector uidsToDelete; - auto getUidsToDelete = [&uidsToDelete, &uidSet](const uint32_t& key, - const BpfMap&) { - if (uidSet.find((int32_t) key) == uidSet.end()) { - uidsToDelete.push_back(key); - } - return base::Result(); - }; - RETURN_IF_NOT_OK(mUidOwnerMap.iterate(getUidsToDelete)); - - for(auto uid : uidsToDelete) { - RETURN_IF_NOT_OK(removeRule(uid, match)); - } - - for (auto uid : uids) { - RETURN_IF_NOT_OK(addRule(uid, match)); - } - return netdutils::status::ok; -} - -Status TrafficController::addUidInterfaceRules(const int iif, - const std::vector& uidsToAdd) { - std::lock_guard guard(mMutex); - - for (auto uid : uidsToAdd) { - netdutils::Status result = addRule(uid, IIF_MATCH, iif); - if (!isOk(result)) { - ALOGW("addRule failed(%d): uid=%d iif=%d", result.code(), uid, iif); - } - } - return netdutils::status::ok; -} - -Status TrafficController::removeUidInterfaceRules(const std::vector& uidsToDelete) { - std::lock_guard guard(mMutex); - - for (auto uid : uidsToDelete) { - netdutils::Status result = removeRule(uid, IIF_MATCH); - if (!isOk(result)) { - ALOGW("removeRule failed(%d): uid=%d", result.code(), uid); - } - } - return netdutils::status::ok; -} - -Status TrafficController::updateUidLockdownRule(const uid_t uid, const bool add) { - std::lock_guard guard(mMutex); - - netdutils::Status result = add ? addRule(uid, LOCKDOWN_VPN_MATCH) - : removeRule(uid, LOCKDOWN_VPN_MATCH); - if (!isOk(result)) { - ALOGW("%s Lockdown rule failed(%d): uid=%d", - (add ? "add": "remove"), result.code(), uid); - } - return result; -} - -int TrafficController::replaceUidOwnerMap(const std::string& name, bool isAllowlist __unused, - const std::vector& uids) { - // FirewallRule rule = isAllowlist ? ALLOW : DENY; - // FirewallType type = isAllowlist ? ALLOWLIST : DENYLIST; - Status res; - if (!name.compare(LOCAL_DOZABLE)) { - res = replaceRulesInMap(DOZABLE_MATCH, uids); - } else if (!name.compare(LOCAL_STANDBY)) { - res = replaceRulesInMap(STANDBY_MATCH, uids); - } else if (!name.compare(LOCAL_POWERSAVE)) { - res = replaceRulesInMap(POWERSAVE_MATCH, uids); - } else if (!name.compare(LOCAL_RESTRICTED)) { - res = replaceRulesInMap(RESTRICTED_MATCH, uids); - } else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) { - res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids); - } else if (!name.compare(LOCAL_OEM_DENY_1)) { - res = replaceRulesInMap(OEM_DENY_1_MATCH, uids); - } else if (!name.compare(LOCAL_OEM_DENY_2)) { - res = replaceRulesInMap(OEM_DENY_2_MATCH, uids); - } else if (!name.compare(LOCAL_OEM_DENY_3)) { - res = replaceRulesInMap(OEM_DENY_3_MATCH, uids); - } else { - ALOGE("unknown chain name: %s", name.c_str()); - return -EINVAL; - } - if (!isOk(res)) { - ALOGE("Failed to clean up chain: %s: %s", name.c_str(), res.msg().c_str()); - return -res.code(); - } - return 0; -} - -int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) { - std::lock_guard guard(mMutex); - uint32_t key = UID_RULES_CONFIGURATION_KEY; - auto oldConfigure = mConfigurationMap.readValue(key); - if (!oldConfigure.ok()) { - ALOGE("Cannot read the old configuration from map: %s", - oldConfigure.error().message().c_str()); - return -oldConfigure.error().code(); - } - uint32_t match; - switch (chain) { - case DOZABLE: - match = DOZABLE_MATCH; - break; - case STANDBY: - match = STANDBY_MATCH; - break; - case POWERSAVE: - match = POWERSAVE_MATCH; - break; - case RESTRICTED: - match = RESTRICTED_MATCH; - break; - case LOW_POWER_STANDBY: - match = LOW_POWER_STANDBY_MATCH; - break; - case OEM_DENY_1: - match = OEM_DENY_1_MATCH; - break; - case OEM_DENY_2: - match = OEM_DENY_2_MATCH; - break; - case OEM_DENY_3: - match = OEM_DENY_3_MATCH; - break; - default: - return -EINVAL; - } - BpfConfig newConfiguration = - enable ? (oldConfigure.value() | match) : (oldConfigure.value() & ~match); - Status res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST); - if (!isOk(res)) { - ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str()); - } - return -res.code(); -} - -Status TrafficController::swapActiveStatsMap() { - std::lock_guard guard(mMutex); - - uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY; - auto oldConfigure = mConfigurationMap.readValue(key); - if (!oldConfigure.ok()) { - ALOGE("Cannot read the old configuration from map: %s", - oldConfigure.error().message().c_str()); - return Status(oldConfigure.error().code(), oldConfigure.error().message()); - } - - // Write to the configuration map to inform the kernel eBPF program to switch - // from using one map to the other. Use flag BPF_EXIST here since the map should - // be already populated in initMaps. - uint32_t newConfigure = (oldConfigure.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A; - auto res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure, - BPF_EXIST); - if (!res.ok()) { - ALOGE("Failed to toggle the stats map: %s", strerror(res.error().code())); - return res; - } - // After changing the config, we need to make sure all the current running - // eBPF programs are finished and all the CPUs are aware of this config change - // before we modify the old map. So we do a special hack here to wait for - // the kernel to do a synchronize_rcu(). Once the kernel called - // synchronize_rcu(), the config we just updated will be available to all cores - // and the next eBPF programs triggered inside the kernel will use the new - // map configuration. So once this function returns we can safely modify the - // old stats map without concerning about race between the kernel and - // userspace. - int ret = synchronizeKernelRCU(); - if (ret) { - ALOGE("map swap synchronize_rcu() ended with failure: %s", strerror(-ret)); - return statusFromErrno(-ret, "map swap synchronize_rcu() failed"); - } - return netdutils::status::ok; -} - -void TrafficController::setPermissionForUids(int permission, const std::vector& uids) { - std::lock_guard guard(mMutex); - if (permission == INetd::PERMISSION_UNINSTALLED) { - for (uid_t uid : uids) { - // Clean up all permission information for the related uid if all the - // packages related to it are uninstalled. - mPrivilegedUser.erase(uid); - Status ret = mUidPermissionMap.deleteValue(uid); - if (!isOk(ret) && ret.code() != ENOENT) { - ALOGE("Failed to clean up the permission for %u: %s", uid, strerror(ret.code())); - } - } - return; - } - - bool privileged = (permission & INetd::PERMISSION_UPDATE_DEVICE_STATS); - - for (uid_t uid : uids) { - if (privileged) { - mPrivilegedUser.insert(uid); - } else { - mPrivilegedUser.erase(uid); - } - - // The map stores all the permissions that the UID has, except if the only permission - // the UID has is the INTERNET permission, then the UID should not appear in the map. - if (permission != INetd::PERMISSION_INTERNET) { - Status ret = mUidPermissionMap.writeValue(uid, permission, BPF_ANY); - if (!isOk(ret)) { - ALOGE("Failed to set permission: %s of uid(%u) to permission map: %s", - UidPermissionTypeToString(permission).c_str(), uid, strerror(ret.code())); - } - } else { - Status ret = mUidPermissionMap.deleteValue(uid); - if (!isOk(ret) && ret.code() != ENOENT) { - ALOGE("Failed to remove uid %u from permission map: %s", uid, strerror(ret.code())); - } - } - } -} - -} // namespace net -} // namespace android diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp deleted file mode 100644 index 99e9831499..0000000000 --- a/service/native/TrafficControllerTest.cpp +++ /dev/null @@ -1,822 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * TrafficControllerTest.cpp - unit tests for TrafficController.cpp - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING -#include "TrafficController.h" -#include "bpf/BpfUtils.h" -#include "NetdUpdatablePublic.h" - -using namespace android::bpf; // NOLINT(google-build-using-namespace): grandfathered - -namespace android { -namespace net { - -using android::netdutils::Status; -using base::Result; -using netdutils::isOk; -using netdutils::statusFromErrno; - -constexpr int TEST_MAP_SIZE = 10; -constexpr uid_t TEST_UID = 10086; -constexpr uid_t TEST_UID2 = 54321; -constexpr uid_t TEST_UID3 = 98765; -constexpr uint32_t TEST_TAG = 42; -constexpr uint32_t TEST_COUNTERSET = 1; -constexpr int TEST_IFINDEX = 999; -constexpr int RXPACKETS = 1; -constexpr int RXBYTES = 100; -constexpr int TXPACKETS = 0; -constexpr int TXBYTES = 0; - -#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid()) -#define ASSERT_INVALID(x) ASSERT_FALSE((x).isValid()) - -class TrafficControllerTest : public ::testing::Test { - protected: - TrafficControllerTest() {} - TrafficController mTc; - BpfMap mFakeCookieTagMap; - BpfMap mFakeAppUidStatsMap; - BpfMap mFakeStatsMapA; - BpfMap mFakeStatsMapB; // makeTrafficControllerMapsInvalid only - BpfMap mFakeIfaceStatsMap; ; // makeTrafficControllerMapsInvalid only - BpfMap mFakeConfigurationMap; - BpfMap mFakeUidOwnerMap; - BpfMap mFakeUidPermissionMap; - BpfMap mFakeUidCounterSetMap; - BpfMap mFakeIfaceIndexNameMap; - - void SetUp() { - std::lock_guard guard(mTc.mMutex); - ASSERT_EQ(0, setrlimitForTest()); - - mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeCookieTagMap); - - mFakeAppUidStatsMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeAppUidStatsMap); - - mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeStatsMapA); - - mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_ARRAY, CONFIGURATION_MAP_SIZE); - ASSERT_VALID(mFakeConfigurationMap); - - mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeUidOwnerMap); - mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeUidPermissionMap); - - mFakeUidCounterSetMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeUidCounterSetMap); - - mFakeIfaceIndexNameMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); - ASSERT_VALID(mFakeIfaceIndexNameMap); - - mTc.mCookieTagMap = mFakeCookieTagMap; - ASSERT_VALID(mTc.mCookieTagMap); - mTc.mAppUidStatsMap = mFakeAppUidStatsMap; - ASSERT_VALID(mTc.mAppUidStatsMap); - mTc.mStatsMapA = mFakeStatsMapA; - ASSERT_VALID(mTc.mStatsMapA); - mTc.mConfigurationMap = mFakeConfigurationMap; - ASSERT_VALID(mTc.mConfigurationMap); - - // Always write to stats map A by default. - static_assert(SELECT_MAP_A == 0); - - mTc.mUidOwnerMap = mFakeUidOwnerMap; - ASSERT_VALID(mTc.mUidOwnerMap); - mTc.mUidPermissionMap = mFakeUidPermissionMap; - ASSERT_VALID(mTc.mUidPermissionMap); - mTc.mPrivilegedUser.clear(); - - mTc.mUidCounterSetMap = mFakeUidCounterSetMap; - ASSERT_VALID(mTc.mUidCounterSetMap); - - mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap; - ASSERT_VALID(mTc.mIfaceIndexNameMap); - } - - void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) { - UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag}; - EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY)); - *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = TEST_IFINDEX}; - StatsValue statsMapValue = {.rxPackets = RXPACKETS, .rxBytes = RXBYTES, - .txPackets = TXPACKETS, .txBytes = TXBYTES}; - EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY)); - EXPECT_RESULT_OK(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY)); - // put tag information back to statsKey - key->tag = tag; - } - - void populateFakeCounterSet(uint32_t uid, uint32_t counterSet) { - EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY)); - } - - void populateFakeIfaceIndexName(const char* name, uint32_t ifaceIndex) { - if (name == nullptr || ifaceIndex <= 0) return; - - IfaceValue iface; - strlcpy(iface.name, name, sizeof(IfaceValue)); - EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY)); - } - - void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) { - uint32_t uid = TEST_UID; - EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST)); - Result value = mFakeUidOwnerMap.readValue(uid); - EXPECT_RESULT_OK(value); - EXPECT_TRUE(value.value().rule & match); - - uid = TEST_UID2; - EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, ALLOWLIST)); - value = mFakeUidOwnerMap.readValue(uid); - EXPECT_RESULT_OK(value); - EXPECT_TRUE(value.value().rule & match); - - EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, ALLOWLIST)); - value = mFakeUidOwnerMap.readValue(uid); - EXPECT_FALSE(value.ok()); - EXPECT_EQ(ENOENT, value.error().code()); - - uid = TEST_UID; - EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, DENYLIST)); - value = mFakeUidOwnerMap.readValue(uid); - EXPECT_FALSE(value.ok()); - EXPECT_EQ(ENOENT, value.error().code()); - - uid = TEST_UID3; - EXPECT_EQ(-ENOENT, mTc.changeUidOwnerRule(chain, uid, ALLOW, DENYLIST)); - value = mFakeUidOwnerMap.readValue(uid); - EXPECT_FALSE(value.ok()); - EXPECT_EQ(ENOENT, value.error().code()); - } - - void checkEachUidValue(const std::vector& uids, UidOwnerMatchType match) { - for (uint32_t uid : uids) { - Result value = mFakeUidOwnerMap.readValue(uid); - EXPECT_RESULT_OK(value); - EXPECT_TRUE(value.value().rule & match); - } - std::set uidSet(uids.begin(), uids.end()); - const auto checkNoOtherUid = [&uidSet](const int32_t& key, - const BpfMap&) { - EXPECT_NE(uidSet.end(), uidSet.find(key)); - return Result(); - }; - EXPECT_RESULT_OK(mFakeUidOwnerMap.iterate(checkNoOtherUid)); - } - - void checkUidMapReplace(const std::string& name, const std::vector& uids, - UidOwnerMatchType match) { - bool isAllowlist = true; - EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isAllowlist, uids)); - checkEachUidValue(uids, match); - - isAllowlist = false; - EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isAllowlist, uids)); - checkEachUidValue(uids, match); - } - - void expectUidOwnerMapValues(const std::vector& appUids, uint32_t expectedRule, - uint32_t expectedIif) { - for (uint32_t uid : appUids) { - Result value = mFakeUidOwnerMap.readValue(uid); - EXPECT_RESULT_OK(value); - EXPECT_EQ(expectedRule, value.value().rule) - << "Expected rule for UID " << uid << " to be " << expectedRule << ", but was " - << value.value().rule; - EXPECT_EQ(expectedIif, value.value().iif) - << "Expected iif for UID " << uid << " to be " << expectedIif << ", but was " - << value.value().iif; - } - } - - template - void expectMapEmpty(BpfMap& map) { - auto isEmpty = map.isEmpty(); - EXPECT_RESULT_OK(isEmpty); - EXPECT_TRUE(isEmpty.value()); - } - - void expectUidPermissionMapValues(const std::vector& appUids, uint8_t expectedValue) { - for (uid_t uid : appUids) { - Result value = mFakeUidPermissionMap.readValue(uid); - EXPECT_RESULT_OK(value); - EXPECT_EQ(expectedValue, value.value()) - << "Expected value for UID " << uid << " to be " << expectedValue - << ", but was " << value.value(); - } - } - - void expectPrivilegedUserSet(const std::vector& appUids) { - std::lock_guard guard(mTc.mMutex); - EXPECT_EQ(appUids.size(), mTc.mPrivilegedUser.size()); - for (uid_t uid : appUids) { - EXPECT_NE(mTc.mPrivilegedUser.end(), mTc.mPrivilegedUser.find(uid)); - } - } - - void expectPrivilegedUserSetEmpty() { - std::lock_guard guard(mTc.mMutex); - EXPECT_TRUE(mTc.mPrivilegedUser.empty()); - } - - Status updateUidOwnerMaps(const std::vector& appUids, - UidOwnerMatchType matchType, TrafficController::IptOp op) { - Status ret(0); - for (auto uid : appUids) { - ret = mTc.updateUidOwnerMap(uid, matchType, op); - if(!isOk(ret)) break; - } - return ret; - } -}; - -TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) { - uint32_t uid = TEST_UID; - ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, DENYLIST))); - Result value = mFakeUidOwnerMap.readValue(uid); - ASSERT_RESULT_OK(value); - ASSERT_TRUE(value.value().rule & STANDBY_MATCH); - - ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, ALLOW, ALLOWLIST))); - value = mFakeUidOwnerMap.readValue(uid); - ASSERT_RESULT_OK(value); - ASSERT_TRUE(value.value().rule & DOZABLE_MATCH); - - ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, DENY, ALLOWLIST))); - value = mFakeUidOwnerMap.readValue(uid); - ASSERT_RESULT_OK(value); - ASSERT_FALSE(value.value().rule & DOZABLE_MATCH); - - ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, DENYLIST))); - ASSERT_FALSE(mFakeUidOwnerMap.readValue(uid).ok()); - - uid = TEST_UID2; - ASSERT_FALSE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, DENYLIST))); - ASSERT_FALSE(mFakeUidOwnerMap.readValue(uid).ok()); -} - -TEST_F(TrafficControllerTest, TestChangeUidOwnerRule) { - checkUidOwnerRuleForChain(DOZABLE, DOZABLE_MATCH); - checkUidOwnerRuleForChain(STANDBY, STANDBY_MATCH); - checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH); - checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH); - checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH); - checkUidOwnerRuleForChain(OEM_DENY_1, OEM_DENY_1_MATCH); - checkUidOwnerRuleForChain(OEM_DENY_2, OEM_DENY_2_MATCH); - checkUidOwnerRuleForChain(OEM_DENY_3, OEM_DENY_3_MATCH); - ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST)); - ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST)); -} - -TEST_F(TrafficControllerTest, TestReplaceUidOwnerMap) { - std::vector uids = {TEST_UID, TEST_UID2, TEST_UID3}; - checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH); - checkUidMapReplace("fw_standby", uids, STANDBY_MATCH); - checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH); - checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH); - checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH); - checkUidMapReplace("fw_oem_deny_1", uids, OEM_DENY_1_MATCH); - checkUidMapReplace("fw_oem_deny_2", uids, OEM_DENY_2_MATCH); - checkUidMapReplace("fw_oem_deny_3", uids, OEM_DENY_3_MATCH); - ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids)); -} - -TEST_F(TrafficControllerTest, TestReplaceSameChain) { - std::vector uids = {TEST_UID, TEST_UID2, TEST_UID3}; - checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH); - std::vector newUids = {TEST_UID2, TEST_UID3}; - checkUidMapReplace("fw_dozable", newUids, DOZABLE_MATCH); -} - -TEST_F(TrafficControllerTest, TestDenylistUidMatch) { - std::vector appUids = {1000, 1001, 10012}; - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOpInsert))); - expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0); - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOpDelete))); - expectMapEmpty(mFakeUidOwnerMap); -} - -TEST_F(TrafficControllerTest, TestAllowlistUidMatch) { - std::vector appUids = {1000, 1001, 10012}; - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert))); - expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0); - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete))); - expectMapEmpty(mFakeUidOwnerMap); -} - -TEST_F(TrafficControllerTest, TestReplaceMatchUid) { - std::vector appUids = {1000, 1001, 10012}; - // Add appUids to the denylist and expect that their values are all PENALTY_BOX_MATCH. - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOpInsert))); - expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0); - - // Add the same UIDs to the allowlist and expect that we get PENALTY_BOX_MATCH | - // HAPPY_BOX_MATCH. - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert))); - expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0); - - // Remove the same UIDs from the allowlist and check the PENALTY_BOX_MATCH is still there. - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete))); - expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0); - - // Remove the same UIDs from the denylist and check the map is empty. - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOpDelete))); - ASSERT_FALSE(mFakeUidOwnerMap.getFirstKey().ok()); -} - -TEST_F(TrafficControllerTest, TestDeleteWrongMatchSilentlyFails) { - std::vector appUids = {1000, 1001, 10012}; - // If the uid does not exist in the map, trying to delete a rule about it will fail. - ASSERT_FALSE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, - TrafficController::IptOpDelete))); - expectMapEmpty(mFakeUidOwnerMap); - - // Add denylist rules for appUids. - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, - TrafficController::IptOpInsert))); - expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0); - - // Delete (non-existent) denylist rules for appUids, and check that this silently does - // nothing if the uid is in the map but does not have denylist match. This is required because - // NetworkManagementService will try to remove a uid from denylist after adding it to the - // allowlist and if the remove fails it will not update the uid status. - ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH, - TrafficController::IptOpDelete))); - expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0); -} - -TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRules) { - int iif0 = 15; - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001}))); - expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0); - - // Add some non-overlapping new uids. They should coexist with existing rules - int iif1 = 16; - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001}))); - expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0); - expectUidOwnerMapValues({2000, 2001}, IIF_MATCH, iif1); - - // Overwrite some existing uids - int iif2 = 17; - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif2, {1000, 2000}))); - expectUidOwnerMapValues({1001}, IIF_MATCH, iif0); - expectUidOwnerMapValues({2001}, IIF_MATCH, iif1); - expectUidOwnerMapValues({1000, 2000}, IIF_MATCH, iif2); -} - -TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRules) { - int iif0 = 15; - int iif1 = 16; - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001}))); - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001}))); - expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0); - expectUidOwnerMapValues({2000, 2001}, IIF_MATCH, iif1); - - // Rmove some uids - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001, 2001}))); - expectUidOwnerMapValues({1000}, IIF_MATCH, iif0); - expectUidOwnerMapValues({2000}, IIF_MATCH, iif1); - checkEachUidValue({1000, 2000}, IIF_MATCH); // Make sure there are only two uids remaining - - // Remove non-existent uids shouldn't fail - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({2000, 3000}))); - expectUidOwnerMapValues({1000}, IIF_MATCH, iif0); - checkEachUidValue({1000}, IIF_MATCH); // Make sure there are only one uid remaining - - // Remove everything - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000}))); - expectMapEmpty(mFakeUidOwnerMap); -} - -TEST_F(TrafficControllerTest, TestUpdateUidLockdownRule) { - // Add Lockdown rules - ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1000, true /* add */))); - ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1001, true /* add */))); - expectUidOwnerMapValues({1000, 1001}, LOCKDOWN_VPN_MATCH, 0); - - // Remove one of Lockdown rules - ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1000, false /* add */))); - expectUidOwnerMapValues({1001}, LOCKDOWN_VPN_MATCH, 0); - - // Remove remaining Lockdown rule - ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1001, false /* add */))); - expectMapEmpty(mFakeUidOwnerMap); -} - -TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) { - // Set up existing PENALTY_BOX_MATCH rules - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000, 1001, 10012}, PENALTY_BOX_MATCH, - TrafficController::IptOpInsert))); - expectUidOwnerMapValues({1000, 1001, 10012}, PENALTY_BOX_MATCH, 0); - - // Add some partially-overlapping uid owner rules and check result - int iif1 = 32; - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10012, 10013, 10014}))); - expectUidOwnerMapValues({1000, 1001}, PENALTY_BOX_MATCH, 0); - expectUidOwnerMapValues({10012}, PENALTY_BOX_MATCH | IIF_MATCH, iif1); - expectUidOwnerMapValues({10013, 10014}, IIF_MATCH, iif1); - - // Removing some PENALTY_BOX_MATCH rules should not change uid interface rule - ASSERT_TRUE(isOk(updateUidOwnerMaps({1001, 10012}, PENALTY_BOX_MATCH, - TrafficController::IptOpDelete))); - expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0); - expectUidOwnerMapValues({10012, 10013, 10014}, IIF_MATCH, iif1); - - // Remove all uid interface rules - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({10012, 10013, 10014}))); - expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0); - // Make sure these are the only uids left - checkEachUidValue({1000}, PENALTY_BOX_MATCH); -} - -TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithNewMatches) { - int iif1 = 56; - // Set up existing uid interface rules - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10001, 10002}))); - expectUidOwnerMapValues({10001, 10002}, IIF_MATCH, iif1); - - // Add some partially-overlapping doze rules - EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {10002, 10003})); - expectUidOwnerMapValues({10001}, IIF_MATCH, iif1); - expectUidOwnerMapValues({10002}, DOZABLE_MATCH | IIF_MATCH, iif1); - expectUidOwnerMapValues({10003}, DOZABLE_MATCH, 0); - - // Introduce a third rule type (powersave) on various existing UIDs - EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {10000, 10001, 10002, 10003})); - expectUidOwnerMapValues({10000}, POWERSAVE_MATCH, 0); - expectUidOwnerMapValues({10001}, POWERSAVE_MATCH | IIF_MATCH, iif1); - expectUidOwnerMapValues({10002}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif1); - expectUidOwnerMapValues({10003}, POWERSAVE_MATCH | DOZABLE_MATCH, 0); - - // Remove all doze rules - EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {})); - expectUidOwnerMapValues({10000}, POWERSAVE_MATCH, 0); - expectUidOwnerMapValues({10001}, POWERSAVE_MATCH | IIF_MATCH, iif1); - expectUidOwnerMapValues({10002}, POWERSAVE_MATCH | IIF_MATCH, iif1); - expectUidOwnerMapValues({10003}, POWERSAVE_MATCH, 0); - - // Remove all powersave rules, expect ownerMap to only have uid interface rules left - EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {})); - expectUidOwnerMapValues({10001, 10002}, IIF_MATCH, iif1); - // Make sure these are the only uids left - checkEachUidValue({10001, 10002}, IIF_MATCH); -} - -TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRulesWithWildcard) { - // iif=0 is a wildcard - int iif = 0; - // Add interface rule with wildcard to uids - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000, 1001}))); - expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif); -} - -TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRulesWithWildcard) { - // iif=0 is a wildcard - int iif = 0; - // Add interface rule with wildcard to two uids - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000, 1001}))); - expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif); - - // Remove interface rule from one of the uids - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000}))); - expectUidOwnerMapValues({1001}, IIF_MATCH, iif); - checkEachUidValue({1001}, IIF_MATCH); - - // Remove interface rule from the remaining uid - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001}))); - expectMapEmpty(mFakeUidOwnerMap); -} - -TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesWithWildcardAndExistingMatches) { - // Set up existing DOZABLE_MATCH and POWERSAVE_MATCH rule - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH, - TrafficController::IptOpInsert))); - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH, - TrafficController::IptOpInsert))); - - // iif=0 is a wildcard - int iif = 0; - // Add interface rule with wildcard to the existing uid - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000}))); - expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif); - - // Remove interface rule with wildcard from the existing uid - ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000}))); - expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH, 0); -} - -TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesWithWildcardAndNewMatches) { - // iif=0 is a wildcard - int iif = 0; - // Set up existing interface rule with wildcard - ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000}))); - - // Add DOZABLE_MATCH and POWERSAVE_MATCH rule to the existing uid - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH, - TrafficController::IptOpInsert))); - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH, - TrafficController::IptOpInsert))); - expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif); - - // Remove DOZABLE_MATCH and POWERSAVE_MATCH rule from the existing uid - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH, - TrafficController::IptOpDelete))); - ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH, - TrafficController::IptOpDelete))); - expectUidOwnerMapValues({1000}, IIF_MATCH, iif); -} - -TEST_F(TrafficControllerTest, TestGrantInternetPermission) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids); - expectMapEmpty(mFakeUidPermissionMap); - expectPrivilegedUserSetEmpty(); -} - -TEST_F(TrafficControllerTest, TestRevokeInternetPermission) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids); - expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE); -} - -TEST_F(TrafficControllerTest, TestPermissionUninstalled) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids); - expectUidPermissionMapValues(appUids, INetd::PERMISSION_UPDATE_DEVICE_STATS); - expectPrivilegedUserSet(appUids); - - std::vector uidToRemove = {TEST_UID}; - mTc.setPermissionForUids(INetd::PERMISSION_UNINSTALLED, uidToRemove); - - std::vector uidRemain = {TEST_UID3, TEST_UID2}; - expectUidPermissionMapValues(uidRemain, INetd::PERMISSION_UPDATE_DEVICE_STATS); - expectPrivilegedUserSet(uidRemain); - - mTc.setPermissionForUids(INetd::PERMISSION_UNINSTALLED, uidRemain); - expectMapEmpty(mFakeUidPermissionMap); - expectPrivilegedUserSetEmpty(); -} - -TEST_F(TrafficControllerTest, TestGrantUpdateStatsPermission) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids); - expectUidPermissionMapValues(appUids, INetd::PERMISSION_UPDATE_DEVICE_STATS); - expectPrivilegedUserSet(appUids); - - mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids); - expectPrivilegedUserSetEmpty(); - expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE); -} - -TEST_F(TrafficControllerTest, TestRevokeUpdateStatsPermission) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids); - expectPrivilegedUserSet(appUids); - - std::vector uidToRemove = {TEST_UID}; - mTc.setPermissionForUids(INetd::PERMISSION_NONE, uidToRemove); - - std::vector uidRemain = {TEST_UID3, TEST_UID2}; - expectPrivilegedUserSet(uidRemain); - - mTc.setPermissionForUids(INetd::PERMISSION_NONE, uidRemain); - expectPrivilegedUserSetEmpty(); -} - -TEST_F(TrafficControllerTest, TestGrantWrongPermission) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids); - expectPrivilegedUserSetEmpty(); - expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE); -} - -TEST_F(TrafficControllerTest, TestGrantDuplicatePermissionSlientlyFail) { - std::vector appUids = {TEST_UID, TEST_UID2, TEST_UID3}; - - mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids); - expectMapEmpty(mFakeUidPermissionMap); - - std::vector uidToAdd = {TEST_UID}; - mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, uidToAdd); - - expectPrivilegedUserSetEmpty(); - - mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids); - expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE); - - mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids); - expectPrivilegedUserSet(appUids); - - mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, uidToAdd); - expectPrivilegedUserSet(appUids); - - mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids); - expectPrivilegedUserSetEmpty(); -} - -TEST_F(TrafficControllerTest, getFirewallType) { - static const struct TestConfig { - ChildChain childChain; - FirewallType firewallType; - } testConfigs[] = { - // clang-format off - {NONE, DENYLIST}, - {DOZABLE, ALLOWLIST}, - {STANDBY, DENYLIST}, - {POWERSAVE, ALLOWLIST}, - {RESTRICTED, ALLOWLIST}, - {LOW_POWER_STANDBY, ALLOWLIST}, - {OEM_DENY_1, DENYLIST}, - {OEM_DENY_2, DENYLIST}, - {OEM_DENY_3, DENYLIST}, - {INVALID_CHAIN, DENYLIST}, - // clang-format on - }; - - for (const auto& config : testConfigs) { - SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.childChain, config.firewallType)); - EXPECT_EQ(config.firewallType, mTc.getFirewallType(config.childChain)); - } -} - -constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000; -constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000; - -using android::base::Error; -using android::base::Result; -using android::bpf::BpfMap; - -// This test set up a SkDestroyListener that is running parallel with the production -// SkDestroyListener. The test will create thousands of sockets and tag them on the -// production cookieUidTagMap and close them in a short time. When the number of -// sockets get closed exceeds the buffer size, it will start to return ENOBUFF -// error. The error will be ignored by the production SkDestroyListener and the -// test will clean up the tags in tearDown if there is any remains. - -// TODO: Instead of test the ENOBUFF error, we can test the production -// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF -// triggered. -class NetlinkListenerTest : public testing::Test { - protected: - NetlinkListenerTest() {} - BpfMap mCookieTagMap; - - void SetUp() { - mCookieTagMap.init(COOKIE_TAG_MAP_PATH); - ASSERT_TRUE(mCookieTagMap.isValid()); - } - - void TearDown() { - const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value, - BpfMap& map) { - if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) { - Result res = map.deleteValue(key); - if (res.ok() || (res.error().code() == ENOENT)) { - return Result(); - } - ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s", key, - strerror(res.error().code())); - } - // Move forward to next cookie in the map. - return Result(); - }; - EXPECT_RESULT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries)); - } - - Result checkNoGarbageTagsExist() { - const auto checkGarbageTags = [](const uint64_t&, const UidTagValue& value, - const BpfMap&) -> Result { - if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) { - return Error(EUCLEAN) << "Closed socket is not untagged"; - } - return {}; - }; - return mCookieTagMap.iterateWithValue(checkGarbageTags); - } - - bool checkMassiveSocketDestroy(int totalNumber, bool expectError) { - std::unique_ptr skDestroyListener; - auto result = android::net::TrafficController::makeSkDestroyListener(); - if (!isOk(result)) { - ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str()); - } else { - skDestroyListener = std::move(result.value()); - } - int rxErrorCount = 0; - // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function. - const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; }; - skDestroyListener->registerSkErrorHandler(rxErrorHandler); - int fds[totalNumber]; - for (int i = 0; i < totalNumber; i++) { - fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); - // The likely reason for a failure is running out of available file descriptors. - EXPECT_LE(0, fds[i]) << i << " of " << totalNumber; - if (fds[i] < 0) { - // EXPECT_LE already failed above, so test case is a failure, but we don't - // want potentially tens of thousands of extra failures creating and then - // closing all these fds cluttering up the logs. - totalNumber = i; - break; - }; - libnetd_updatable_tagSocket(fds[i], TEST_TAG, TEST_UID, 1000); - } - - // TODO: Use a separate thread that has its own fd table so we can - // close sockets even faster simply by terminating that thread. - for (int i = 0; i < totalNumber; i++) { - EXPECT_EQ(0, close(fds[i])); - } - // wait a bit for netlink listener to handle all the messages. - usleep(SOCK_CLOSE_WAIT_US); - if (expectError) { - // If ENOBUFS triggered, check it only called into the handler once, ie. - // that the netlink handler is not spinning. - int currentErrorCount = rxErrorCount; - // 0 error count is acceptable because the system has chances to close all sockets - // normally. - EXPECT_LE(0, rxErrorCount); - if (!rxErrorCount) return true; - - usleep(ENOBUFS_POLL_WAIT_US); - EXPECT_EQ(currentErrorCount, rxErrorCount); - } else { - EXPECT_RESULT_OK(checkNoGarbageTagsExist()); - EXPECT_EQ(0, rxErrorCount); - } - return false; - } -}; - -TEST_F(NetlinkListenerTest, TestAllSocketUntagged) { - checkMassiveSocketDestroy(10, false); - checkMassiveSocketDestroy(100, false); -} - -// Disabled because flaky on blueline-userdebug; this test relies on the main thread -// winning a race against the NetlinkListener::run() thread. There's no way to ensure -// things will be scheduled the same way across all architectures and test environments. -TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) { - bool needRetry = false; - int retryCount = 0; - do { - needRetry = checkMassiveSocketDestroy(32500, true); - if (needRetry) retryCount++; - } while (needRetry && retryCount < 3); - // Should review test if it can always close all sockets correctly. - EXPECT_GT(3, retryCount); -} - - -} // namespace net -} // namespace android diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h deleted file mode 100644 index 86cf50a51c..0000000000 --- a/service/native/include/TrafficController.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "android-base/thread_annotations.h" -#include "bpf/BpfMap.h" -#include "netd.h" -#include "netdutils/NetlinkListener.h" -#include "netdutils/StatusOr.h" - -namespace android { -namespace net { - -using netdutils::StatusOr; - -class TrafficController { - public: - /* - * Initialize the whole controller - */ - netdutils::Status start(bool startSkDestroyListener); - - /* - * Swap the stats map config from current active stats map to the idle one. - */ - netdutils::Status swapActiveStatsMap() EXCLUDES(mMutex); - - int changeUidOwnerRule(ChildChain chain, const uid_t uid, FirewallRule rule, FirewallType type); - - int removeUidOwnerRule(const uid_t uid); - - int replaceUidOwnerMap(const std::string& name, bool isAllowlist, - const std::vector& uids); - - enum IptOp { IptOpInsert, IptOpDelete }; - - netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule, - FirewallType type) EXCLUDES(mMutex); - - netdutils::Status replaceRulesInMap(UidOwnerMatchType match, const std::vector& uids) - EXCLUDES(mMutex); - - netdutils::Status addUidInterfaceRules(const int ifIndex, const std::vector& uids) - EXCLUDES(mMutex); - netdutils::Status removeUidInterfaceRules(const std::vector& uids) EXCLUDES(mMutex); - - netdutils::Status updateUidLockdownRule(const uid_t uid, const bool add) EXCLUDES(mMutex); - - netdutils::Status updateUidOwnerMap(const uint32_t uid, - UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex); - - int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex); - - static netdutils::StatusOr> - makeSkDestroyListener(); - - void setPermissionForUids(int permission, const std::vector& uids) EXCLUDES(mMutex); - - FirewallType getFirewallType(ChildChain); - - static const char* LOCAL_DOZABLE; - static const char* LOCAL_STANDBY; - static const char* LOCAL_POWERSAVE; - static const char* LOCAL_RESTRICTED; - static const char* LOCAL_LOW_POWER_STANDBY; - static const char* LOCAL_OEM_DENY_1; - static const char* LOCAL_OEM_DENY_2; - static const char* LOCAL_OEM_DENY_3; - - private: - /* - * mCookieTagMap: Store the corresponding tag and uid for a specific socket. - * DO NOT hold any locks when modifying this map, otherwise when the untag - * operation is waiting for a lock hold by other process and there are more - * sockets being closed than can fit in the socket buffer of the netlink socket - * that receives them, then the kernel will drop some of these sockets and we - * won't delete their tags. - * Map Key: uint64_t socket cookie - * Map Value: UidTagValue, contains a uint32 uid and a uint32 tag. - */ - bpf::BpfMap mCookieTagMap GUARDED_BY(mMutex); - - /* - * mUidCounterSetMap: Store the counterSet of a specific uid. - * Map Key: uint32 uid. - * Map Value: uint32 counterSet specifies if the traffic is a background - * or foreground traffic. - */ - bpf::BpfMap mUidCounterSetMap GUARDED_BY(mMutex); - - /* - * mAppUidStatsMap: Store the total traffic stats for a uid regardless of - * tag, counterSet and iface. The stats is used by TrafficStats.getUidStats - * API to return persistent stats for a specific uid since device boot. - */ - bpf::BpfMap mAppUidStatsMap; - - /* - * mStatsMapA/mStatsMapB: Store the traffic statistics for a specific - * combination of uid, tag, iface and counterSet. These two maps contain - * both tagged and untagged traffic. - * Map Key: StatsKey contains the uid, tag, counterSet and ifaceIndex - * information. - * Map Value: Stats, contains packet count and byte count of each - * transport protocol on egress and ingress direction. - */ - bpf::BpfMap mStatsMapA GUARDED_BY(mMutex); - - bpf::BpfMap mStatsMapB GUARDED_BY(mMutex); - - /* - * mIfaceIndexNameMap: Store the index name pair of each interface show up - * on the device since boot. The interface index is used by the eBPF program - * to correctly match the iface name when receiving a packet. - */ - bpf::BpfMap mIfaceIndexNameMap; - - /* - * mIfaceStataMap: Store per iface traffic stats gathered from xt_bpf - * filter. - */ - bpf::BpfMap mIfaceStatsMap; - - /* - * mConfigurationMap: Store the current network policy about uid filtering - * and the current stats map in use. There are two configuration entries in - * the map right now: - * - Entry with UID_RULES_CONFIGURATION_KEY: - * Store the configuration for the current uid rules. It indicates the device - * is in doze/powersave/standby/restricted/low power standby/oem deny mode. - * - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY: - * Stores the current live stats map that kernel program is writing to. - * Userspace can do scraping and cleaning job on the other one depending on the - * current configs. - */ - bpf::BpfMap mConfigurationMap GUARDED_BY(mMutex); - - /* - * mUidOwnerMap: Store uids that are used for bandwidth control uid match. - */ - bpf::BpfMap mUidOwnerMap GUARDED_BY(mMutex); - - /* - * mUidOwnerMap: Store uids that are used for INTERNET permission check. - */ - bpf::BpfMap mUidPermissionMap GUARDED_BY(mMutex); - - std::unique_ptr mSkDestroyListener; - - netdutils::Status removeRule(uint32_t uid, UidOwnerMatchType match) REQUIRES(mMutex); - - netdutils::Status addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif = 0) - REQUIRES(mMutex); - - std::mutex mMutex; - - netdutils::Status initMaps() EXCLUDES(mMutex); - - // Keep track of uids that have permission UPDATE_DEVICE_STATS so we don't - // need to call back to system server for permission check. - std::set mPrivilegedUser GUARDED_BY(mMutex); - - // For testing - friend class TrafficControllerTest; -}; - -} // namespace net -} // namespace android From ffa6d6152b90d60d7a3838174fb3e7fb7283bb82 Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Tue, 7 Nov 2023 17:39:59 +0900 Subject: [PATCH 3/3] Remove unnecessary check for skDestroyListener skDestroyListener is always running with Java code so removed the unnecessary check. Bug: 217624062 Test: TH Change-Id: Ie1a36c3c736005ec4821c5e9c9983998f7042afa --- .../src/com/android/server/net/NetworkStatsService.java | 8 ++------ service/src/com/android/server/BpfNetMaps.java | 4 ---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java index 46afd3135e..804e709685 100644 --- a/service-t/src/com/android/server/net/NetworkStatsService.java +++ b/service-t/src/com/android/server/net/NetworkStatsService.java @@ -640,12 +640,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // on the experiment flag, BpfNetMaps starts C SkDestroyListener (existing code) or // NetworkStatsService starts Java SkDestroyListener (new code). final BpfNetMaps bpfNetMaps = mDeps.makeBpfNetMaps(mContext); - if (bpfNetMaps.isSkDestroyListenerRunning()) { - mSkDestroyListener = null; - } else { - mSkDestroyListener = mDeps.makeSkDestroyListener(mCookieTagMap, mHandler); - mHandler.post(mSkDestroyListener::start); - } + mSkDestroyListener = mDeps.makeSkDestroyListener(mCookieTagMap, mHandler); + mHandler.post(mSkDestroyListener::start); } /** diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index d1218a2b75..3dec6cca93 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -272,10 +272,6 @@ public class BpfNetMaps { sInitialized = true; } - public boolean isSkDestroyListenerRunning() { - return false; - } - /** * Dependencies of BpfNetMaps, for injection in tests. */