Use java BpfMap in BpfNetMaps#setChildChain
Bug: 217624062
Test: atest BpfNetMapsTest android.net.cts.ConnectivityManagerTest#testFirewallBlocking
Change-Id: I13e96911eccd7d1d0545a156ddc2859bcaac09eb
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 9ae2c12..02083ff 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -5903,6 +5903,7 @@
*
* @param chain target chain.
* @param enable whether the chain should be enabled.
+ * @throws UnsupportedOperationException if called on pre-T devices.
* @throws IllegalStateException if enabling or disabling the firewall chain failed.
* @hide
*/
@@ -5926,7 +5927,6 @@
* @param chain target chain.
* @return {@code true} if chain is enabled, {@code false} if chain is disabled.
* @throws UnsupportedOperationException if called on pre-T devices.
- * @throws IllegalArgumentException if {@code chain} is a invalid value.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
* @hide
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 7af50f9..4073359 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -24,6 +24,7 @@
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.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENOENT;
import static android.system.OsConstants.EOPNOTSUPP;
@@ -55,6 +56,11 @@
private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
private static boolean sInitialized = false;
+ // Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
+ // This entry is not accessed by others.
+ // BpfNetMaps acquires this lock while sequence of read, modify, and write.
+ private static final Object sUidRulesConfigBpfMapLock = new Object();
+
private static final String CONFIGURATION_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_configuration_map";
private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
@@ -152,7 +158,7 @@
public long getMatchByFirewallChain(final int chain) {
final long match = FIREWALL_CHAIN_TO_MATCH.get(chain, NO_MATCH);
if (match == NO_MATCH) {
- throw new IllegalArgumentException("Invalid firewall chain: " + chain);
+ throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
}
return match;
}
@@ -163,6 +169,12 @@
}
}
+ private void throwIfUseNetd(final String msg) {
+ if (USE_NETD) {
+ throw new UnsupportedOperationException(msg);
+ }
+ }
+
/**
* Add naughty app bandwidth rule for specific app
*
@@ -216,12 +228,29 @@
*
* @param childChain target chain to enable
* @param enable whether to enable or disable child chain.
+ * @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 setChildChain(final int childChain, final boolean enable) {
- final int err = native_setChildChain(childChain, enable);
- maybeThrow(err, "Unable to set child chain");
+ throwIfUseNetd("setChildChain is not available on pre-T devices");
+
+ final long match = getMatchByFirewallChain(childChain);
+ try {
+ synchronized (sUidRulesConfigBpfMapLock) {
+ final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
+ if (config == null) {
+ throw new ServiceSpecificException(ENOENT,
+ "Unable to get firewall chain status: sConfigurationMap does not have"
+ + " entry for 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));
+ }
}
/**
@@ -230,15 +259,11 @@
* @param childChain target chain
* @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
* @throws UnsupportedOperationException if called on pre-T devices.
- * @throws IllegalArgumentException if {@code childChain} is a invalid value.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public boolean getChainEnabled(final int childChain) {
- if (USE_NETD) {
- throw new UnsupportedOperationException("getChainEnabled is not available on pre-T"
- + " devices");
- }
+ throwIfUseNetd("getChainEnabled is not available on pre-T devices");
final long match = getMatchByFirewallChain(childChain);
try {
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 9d63762..99e7ecc 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -26,6 +26,7 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.PERMISSION_INTERNET;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -168,7 +169,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testGetChainEnabledInvalidChain() {
- final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(-1 /* childChain */));
assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(1000 /* childChain */));
}
@@ -187,4 +188,81 @@
assertThrows(UnsupportedOperationException.class,
() -> mBpfNetMaps.getChainEnabled(FIREWALL_CHAIN_DOZABLE));
}
+
+ private void doTestSetChildChain(final List<Integer> testChains) throws Exception {
+ long expectedMatch = 0;
+ for (final int chain: testChains) {
+ expectedMatch |= mBpfNetMaps.getMatchByFirewallChain(chain);
+ }
+
+ assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+
+ for (final int chain: testChains) {
+ mBpfNetMaps.setChildChain(chain, true /* enable */);
+ }
+ assertEquals(expectedMatch, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+
+ for (final int chain: testChains) {
+ mBpfNetMaps.setChildChain(chain, false /* enable */);
+ }
+ assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+ }
+
+ private void doTestSetChildChain(final int testChain) throws Exception {
+ doTestSetChildChain(List.of(testChain));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetChildChain() throws Exception {
+ sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+ doTestSetChildChain(FIREWALL_CHAIN_DOZABLE);
+ doTestSetChildChain(FIREWALL_CHAIN_STANDBY);
+ doTestSetChildChain(FIREWALL_CHAIN_POWERSAVE);
+ doTestSetChildChain(FIREWALL_CHAIN_RESTRICTED);
+ doTestSetChildChain(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+ doTestSetChildChain(FIREWALL_CHAIN_OEM_DENY_1);
+ doTestSetChildChain(FIREWALL_CHAIN_OEM_DENY_2);
+ doTestSetChildChain(FIREWALL_CHAIN_OEM_DENY_3);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetChildChainMultipleChain() throws Exception {
+ sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+ doTestSetChildChain(List.of(
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY));
+ doTestSetChildChain(List.of(
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_RESTRICTED));
+ doTestSetChildChain(FIREWALL_CHAINS);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetChildChainInvalidChain() {
+ final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+ assertThrows(expected,
+ () -> mBpfNetMaps.setChildChain(-1 /* childChain */, true /* enable */));
+ assertThrows(expected,
+ () -> mBpfNetMaps.setChildChain(1000 /* childChain */, true /* enable */));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetChildChainMissingConfiguration() {
+ // sConfigurationMap does not have entry for UID_RULES_CONFIGURATION_KEY
+ assertThrows(ServiceSpecificException.class,
+ () -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.S_V2)
+ public void testSetChildChainBeforeT() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
+ }
}