diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index 6599c7f47d..f0e9ec4a49 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -25,6 +25,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.ENODEV; import static android.system.OsConstants.ENOENT; import static android.system.OsConstants.EOPNOTSUPP; @@ -60,6 +61,7 @@ public class BpfNetMaps { private static final String TAG = "BpfNetMaps"; private final INetd mNetd; + private final Dependencies mDeps; // Use legacy netd for releases before T. private static boolean sInitialized = false; @@ -163,6 +165,19 @@ public class BpfNetMaps { sInitialized = true; } + /** + * Dependencies of BpfNetMaps, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** + * Get interface index. + */ + public int getIfIndex(final String ifName) { + return Os.if_nametoindex(ifName); + } + } + /** Constructor used after T that doesn't need to use netd anymore. */ public BpfNetMaps() { this(null); @@ -171,10 +186,16 @@ public class BpfNetMaps { } public BpfNetMaps(final INetd netd) { + this(netd, new Dependencies()); + } + + @VisibleForTesting + public BpfNetMaps(final INetd netd, final Dependencies deps) { if (!PRE_T) { ensureInitialized(); } mNetd = netd; + mDeps = deps; } /** @@ -419,9 +440,24 @@ public class BpfNetMaps { mNetd.firewallAddUidInterfaceRules(ifName, uids); return; } - synchronized (sUidOwnerMap) { - final int err = native_addUidInterfaceRules(ifName, uids); - maybeThrow(err, "Unable to add uid interface rules"); + // 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); + } } } diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java index 433b892927..1148e112dc 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -41,6 +41,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import android.net.INetd; @@ -81,6 +82,7 @@ public final class BpfNetMapsTest { private static final String TEST_IF_NAME = "wlan0"; private static final int TEST_IF_INDEX = 7; private static final int NO_IIF = 0; + 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 List FIREWALL_CHAINS = List.of( @@ -97,6 +99,7 @@ public final class BpfNetMapsTest { private BpfNetMaps mBpfNetMaps; @Mock INetd mNetd; + @Mock BpfNetMaps.Dependencies mDeps; private final BpfMap mConfigurationMap = new TestBpfMap<>(U32.class, U32.class); private final BpfMap mUidOwnerMap = new TestBpfMap<>(U32.class, UidOwnerValue.class); @@ -104,9 +107,10 @@ public final class BpfNetMapsTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME); BpfNetMaps.setConfigurationMapForTest(mConfigurationMap); BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap); - mBpfNetMaps = new BpfNetMaps(mNetd); + mBpfNetMaps = new BpfNetMaps(mNetd, mDeps); } @Test @@ -458,4 +462,72 @@ public final class BpfNetMapsTest { assertThrows(UnsupportedOperationException.class, () -> mBpfNetMaps.updateUidLockdownRule(TEST_UID, true /* add */)); } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testAddUidInterfaceRules() throws Exception { + final int uid0 = TEST_UIDS[0]; + final int uid1 = TEST_UIDS[1]; + + mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS); + + checkUidOwnerValue(uid0, TEST_IF_INDEX, IIF_MATCH); + checkUidOwnerValue(uid1, TEST_IF_INDEX, IIF_MATCH); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testAddUidInterfaceRulesWithOtherMatch() throws Exception { + final int uid0 = TEST_UIDS[0]; + final int uid1 = TEST_UIDS[1]; + final long match0 = DOZABLE_MATCH; + final long match1 = DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH; + mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(NO_IIF, match0)); + mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NO_IIF, match1)); + + mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS); + + checkUidOwnerValue(uid0, TEST_IF_INDEX, match0 | IIF_MATCH); + checkUidOwnerValue(uid1, TEST_IF_INDEX, match1 | IIF_MATCH); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testAddUidInterfaceRulesWithExistingIifMatch() throws Exception { + final int uid0 = TEST_UIDS[0]; + final int uid1 = TEST_UIDS[1]; + final long match0 = IIF_MATCH; + final long match1 = IIF_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH; + mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX + 1, match0)); + mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1)); + + mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS); + + checkUidOwnerValue(uid0, TEST_IF_INDEX, match0); + checkUidOwnerValue(uid1, TEST_IF_INDEX, match1); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testAddUidInterfaceRulesGetIfIndexFail() { + doReturn(0).when(mDeps).getIfIndex(TEST_IF_NAME); + assertThrows(ServiceSpecificException.class, + () -> mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS)); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testAddUidInterfaceRulesWithNullInterface() throws Exception { + final int uid0 = TEST_UIDS[0]; + final int uid1 = TEST_UIDS[1]; + final long match0 = IIF_MATCH; + final long match1 = IIF_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH; + mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0)); + mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1)); + + mBpfNetMaps.addUidInterfaceRules(null /* ifName */, TEST_UIDS); + + checkUidOwnerValue(uid0, NULL_IIF, match0); + checkUidOwnerValue(uid1, NULL_IIF, match1); + } }