Merge "Support java BpfMap in BpfNetMaps#swapActiveStatsMap"
This commit is contained in:
@@ -191,6 +191,10 @@ static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verb
|
||||
mTc.dump(fd, verbose);
|
||||
}
|
||||
|
||||
static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) {
|
||||
return -bpf::synchronizeKernelRCU();
|
||||
}
|
||||
|
||||
/*
|
||||
* JNI registration.
|
||||
*/
|
||||
@@ -225,6 +229,8 @@ static const JNINativeMethod gMethods[] = {
|
||||
(void*)native_setPermissionForUids},
|
||||
{"native_dump", "(Ljava/io/FileDescriptor;Z)V",
|
||||
(void*)native_dump},
|
||||
{"native_synchronizeKernelRCU", "()I",
|
||||
(void*)native_synchronizeKernelRCU},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -84,6 +84,11 @@ public class BpfNetMaps {
|
||||
// BpfNetMaps acquires this lock while sequence of read, modify, and write.
|
||||
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 =
|
||||
"/sys/fs/bpf/netd_shared/map_netd_configuration_map";
|
||||
private static final String UID_OWNER_MAP_PATH =
|
||||
@@ -236,6 +241,13 @@ public class BpfNetMaps {
|
||||
public int getIfIndex(final String 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. */
|
||||
@@ -723,13 +735,41 @@ public class BpfNetMaps {
|
||||
/**
|
||||
* 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
|
||||
* cause of the failure.
|
||||
*/
|
||||
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();
|
||||
maybeThrow(err, "Unable to swap active stats map");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void native_setPermissionForUids(int permissions, int[] uids);
|
||||
private native void native_dump(FileDescriptor fd, boolean verbose);
|
||||
private static native int native_synchronizeKernelRCU();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import static android.net.INetd.PERMISSION_INTERNET;
|
||||
import static android.net.INetd.PERMISSION_NONE;
|
||||
import static android.net.INetd.PERMISSION_UNINSTALLED;
|
||||
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.HAPPY_BOX_MATCH;
|
||||
@@ -92,6 +93,7 @@ public final class BpfNetMapsTest {
|
||||
private static final int NULL_IIF = 0;
|
||||
private static final String CHAINNAME = "fw_dozable";
|
||||
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(
|
||||
FIREWALL_CHAIN_DOZABLE,
|
||||
FIREWALL_CHAIN_STANDBY,
|
||||
@@ -103,6 +105,9 @@ public final class BpfNetMapsTest {
|
||||
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;
|
||||
|
||||
@Mock INetd mNetd;
|
||||
@@ -117,6 +122,7 @@ public final class BpfNetMapsTest {
|
||||
public void setUp() throws Exception {
|
||||
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);
|
||||
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
|
||||
@@ -846,4 +852,29 @@ public final class BpfNetMapsTest {
|
||||
assertNull(mUidPermissionMap.getValue(new U32(uid0)));
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user