Merge "Support java BpfMap in BpfNetMaps#swapActiveStatsMap"

This commit is contained in:
Motomu Utsumi
2022-08-23 04:01:30 +00:00
committed by Gerrit Code Review
3 changed files with 80 additions and 2 deletions

View File

@@ -191,6 +191,10 @@ static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verb
mTc.dump(fd, verbose); mTc.dump(fd, verbose);
} }
static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) {
return -bpf::synchronizeKernelRCU();
}
/* /*
* JNI registration. * JNI registration.
*/ */
@@ -225,6 +229,8 @@ static const JNINativeMethod gMethods[] = {
(void*)native_setPermissionForUids}, (void*)native_setPermissionForUids},
{"native_dump", "(Ljava/io/FileDescriptor;Z)V", {"native_dump", "(Ljava/io/FileDescriptor;Z)V",
(void*)native_dump}, (void*)native_dump},
{"native_synchronizeKernelRCU", "()I",
(void*)native_synchronizeKernelRCU},
}; };
// clang-format on // clang-format on

View File

@@ -84,6 +84,11 @@ public class BpfNetMaps {
// BpfNetMaps acquires this lock while sequence of read, modify, and write. // BpfNetMaps acquires this lock while sequence of read, modify, and write.
private static final Object sUidRulesConfigBpfMapLock = new Object(); private static final Object sUidRulesConfigBpfMapLock = new Object();
// Lock for sConfigurationMap entry for CURRENT_STATS_MAP_CONFIGURATION_KEY.
// BpfNetMaps acquires this lock while sequence of read, modify, and write.
// BpfNetMaps is an only writer of this entry.
private static final Object sCurrentStatsMapConfigLock = new Object();
private static final String CONFIGURATION_MAP_PATH = private static final String CONFIGURATION_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_configuration_map"; "/sys/fs/bpf/netd_shared/map_netd_configuration_map";
private static final String UID_OWNER_MAP_PATH = private static final String UID_OWNER_MAP_PATH =
@@ -236,6 +241,13 @@ public class BpfNetMaps {
public int getIfIndex(final String ifName) { public int getIfIndex(final String ifName) {
return Os.if_nametoindex(ifName); return Os.if_nametoindex(ifName);
} }
/**
* Call synchronize_rcu()
*/
public int synchronizeKernelRCU() {
return native_synchronizeKernelRCU();
}
} }
/** Constructor used after T that doesn't need to use netd anymore. */ /** Constructor used after T that doesn't need to use netd anymore. */
@@ -723,13 +735,41 @@ public class BpfNetMaps {
/** /**
* Request netd to change the current active network stats map. * Request netd to change the current active network stats map.
* *
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the * @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure. * cause of the failure.
*/ */
public void swapActiveStatsMap() { 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");
}
// 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(); final int err = native_swapActiveStatsMap();
maybeThrow(err, "Unable to swap active stats map"); maybeThrow(err, "Unable to swap active stats map");
} }
}
/** /**
* Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
@@ -804,4 +844,5 @@ public class BpfNetMaps {
private native int native_swapActiveStatsMap(); private native int native_swapActiveStatsMap();
private native void native_setPermissionForUids(int permissions, int[] uids); private native void native_setPermissionForUids(int permissions, int[] uids);
private native void native_dump(FileDescriptor fd, boolean verbose); private native void native_dump(FileDescriptor fd, boolean verbose);
private static native int native_synchronizeKernelRCU();
} }

View File

@@ -30,6 +30,7 @@ import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NONE; import static android.net.INetd.PERMISSION_NONE;
import static android.net.INetd.PERMISSION_UNINSTALLED; import static android.net.INetd.PERMISSION_UNINSTALLED;
import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS; import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
import static android.system.OsConstants.EPERM;
import static com.android.server.BpfNetMaps.DOZABLE_MATCH; import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH; import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
@@ -92,6 +93,7 @@ public final class BpfNetMapsTest {
private static final int NULL_IIF = 0; private static final int NULL_IIF = 0;
private static final String CHAINNAME = "fw_dozable"; private static final String CHAINNAME = "fw_dozable";
private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0); private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
private static final U32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new U32(1);
private static final List<Integer> FIREWALL_CHAINS = List.of( private static final List<Integer> FIREWALL_CHAINS = List.of(
FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_STANDBY,
@@ -103,6 +105,9 @@ public final class BpfNetMapsTest {
FIREWALL_CHAIN_OEM_DENY_3 FIREWALL_CHAIN_OEM_DENY_3
); );
private static final long STATS_SELECT_MAP_A = 0;
private static final long STATS_SELECT_MAP_B = 1;
private BpfNetMaps mBpfNetMaps; private BpfNetMaps mBpfNetMaps;
@Mock INetd mNetd; @Mock INetd mNetd;
@@ -117,6 +122,7 @@ public final class BpfNetMapsTest {
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME); doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
doReturn(0).when(mDeps).synchronizeKernelRCU();
BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */); BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap); BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap); BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
@@ -846,4 +852,29 @@ public final class BpfNetMapsTest {
assertNull(mUidPermissionMap.getValue(new U32(uid0))); assertNull(mUidPermissionMap.getValue(new U32(uid0)));
assertNull(mUidPermissionMap.getValue(new U32(uid1))); assertNull(mUidPermissionMap.getValue(new U32(uid1)));
} }
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testSwapActiveStatsMap() throws Exception {
mConfigurationMap.updateEntry(
CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
mBpfNetMaps.swapActiveStatsMap();
assertEquals(STATS_SELECT_MAP_B,
mConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val);
mBpfNetMaps.swapActiveStatsMap();
assertEquals(STATS_SELECT_MAP_A,
mConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val);
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testSwapActiveStatsMapSynchronizeKernelRCUFail() throws Exception {
doReturn(EPERM).when(mDeps).synchronizeKernelRCU();
mConfigurationMap.updateEntry(
CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
assertThrows(ServiceSpecificException.class, () -> mBpfNetMaps.swapActiveStatsMap());
}
} }