diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 9ae2c12dfb..02083fff55 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5903,6 +5903,7 @@ public class ConnectivityManager { * * @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 @@ public class ConnectivityManager { * @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 7af50f9f8b..4073359775 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_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.system.OsConstants.EINVAL; import static android.system.OsConstants.ENOENT; import static android.system.OsConstants.EOPNOTSUPP; @@ -55,6 +56,11 @@ public class BpfNetMaps { 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 class BpfNetMaps { 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 @@ public class BpfNetMaps { } } + 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 @@ public class BpfNetMaps { * * @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 @@ public class BpfNetMaps { * @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 9d63762e3c..99e7ecc3ab 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_RESTRICTED; 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 @@ public final class BpfNetMapsTest { @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void testGetChainEnabledInvalidChain() { - final Class expected = IllegalArgumentException.class; + final Class expected = ServiceSpecificException.class; assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(-1 /* childChain */)); assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(1000 /* childChain */)); } @@ -187,4 +188,81 @@ public final class BpfNetMapsTest { assertThrows(UnsupportedOperationException.class, () -> mBpfNetMaps.getChainEnabled(FIREWALL_CHAIN_DOZABLE)); } + + private void doTestSetChildChain(final List 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 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 */)); + } }