Add API to get current firewall chain status

This commit adds ConnectivityManager#getFirewallChainEnabled to read the
current firewall chain status

Bug: 208371987
Test: m
Change-Id: I1eadb69f953af5d031cd8dabde3e1f098cf0f4df
This commit is contained in:
Motomu Utsumi
2022-06-08 10:05:07 +00:00
parent 1c52aa12e8
commit be3ff1e923
6 changed files with 281 additions and 2 deletions

View File

@@ -148,6 +148,7 @@ ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilte
#endif // __cplusplus
// LINT.IfChange(match_type)
enum UidOwnerMatchType {
NO_MATCH = 0,
HAPPY_BOX_MATCH = (1 << 0),
@@ -163,6 +164,7 @@ enum UidOwnerMatchType {
OEM_DENY_2_MATCH = (1 << 10),
OEM_DENY_3_MATCH = (1 << 11),
};
// LINT.ThenChange(packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java)
enum BpfPermissionMatch {
BPF_PERMISSION_INTERNET = 1 << 2,

View File

@@ -5920,6 +5920,30 @@ public class ConnectivityManager {
}
}
/**
* Get the specified firewall chain status.
*
* @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
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
})
public boolean getFirewallChainEnabled(@FirewallChain final int chain) {
try {
return mService.getFirewallChainEnabled(chain);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Replaces the contents of the specified UID-based firewall chain.
*

View File

@@ -244,5 +244,7 @@ interface IConnectivityManager
void setFirewallChainEnabled(int chain, boolean enable);
boolean getFirewallChainEnabled(int chain);
void replaceFirewallChain(int chain, in int[] uids);
}

View File

@@ -16,15 +16,29 @@
package com.android.server;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
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.ENOENT;
import static android.system.OsConstants.EOPNOTSUPP;
import android.net.INetd;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.Struct.U32;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -41,6 +55,51 @@ public class BpfNetMaps {
private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
private static boolean sInitialized = false;
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);
private static BpfMap<U32, U32> sConfigurationMap = null;
// LINT.IfChange(match_type)
private static final long NO_MATCH = 0;
private static final long HAPPY_BOX_MATCH = (1 << 0);
private static final long PENALTY_BOX_MATCH = (1 << 1);
private static final long DOZABLE_MATCH = (1 << 2);
private static final long STANDBY_MATCH = (1 << 3);
private static final long POWERSAVE_MATCH = (1 << 4);
private static final long RESTRICTED_MATCH = (1 << 5);
private static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
private static final long IIF_MATCH = (1 << 7);
private static final long LOCKDOWN_VPN_MATCH = (1 << 8);
private static final long OEM_DENY_1_MATCH = (1 << 9);
private static final long OEM_DENY_2_MATCH = (1 << 10);
private static final long OEM_DENY_3_MATCH = (1 << 11);
// LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
// TODO: Use Java BpfMap instead of JNI code (TrafficController) for map update.
// Currently, BpfNetMaps uses TrafficController for map update and TrafficController
// (changeUidOwnerRule and toggleUidOwnerMap) also does conversion from "firewall chain" to
// "match". Migrating map update from JNI to Java BpfMap will solve this duplication.
private static final SparseLongArray FIREWALL_CHAIN_TO_MATCH = new SparseLongArray();
static {
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_DOZABLE, DOZABLE_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_STANDBY, STANDBY_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_POWERSAVE, POWERSAVE_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_RESTRICTED, RESTRICTED_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_1, OEM_DENY_1_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_2, OEM_DENY_2_MATCH);
FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_3, OEM_DENY_3_MATCH);
}
/**
* Only tests or BpfNetMaps#ensureInitialized can call this function.
*/
@VisibleForTesting
public static void initialize(final Dependencies deps) {
sConfigurationMap = deps.getConfigurationMap();
}
/**
* Initializes the class if it is not already initialized. This method will open maps but not
* cause any other effects. This method may be called multiple times on any thread.
@@ -50,10 +109,30 @@ public class BpfNetMaps {
if (!USE_NETD) {
System.loadLibrary("service-connectivity");
native_init();
initialize(new Dependencies());
}
sInitialized = true;
}
/**
* Dependencies of BpfNetMaps, for injection in tests.
*/
@VisibleForTesting
public static class Dependencies {
/**
* Get configuration BPF map.
*/
public BpfMap<U32, U32> getConfigurationMap() {
try {
return new BpfMap<>(
CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot open netd configuration map: " + e);
return null;
}
}
}
/** Constructor used after T that doesn't need to use netd anymore. */
public BpfNetMaps() {
this(null);
@@ -61,11 +140,23 @@ public class BpfNetMaps {
if (USE_NETD) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
}
public BpfNetMaps(INetd netd) {
public BpfNetMaps(final INetd netd) {
ensureInitialized();
mNetd = netd;
}
/**
* Get corresponding match from firewall chain.
*/
@VisibleForTesting
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);
}
return match;
}
private void maybeThrow(final int err, final String msg) {
if (err != 0) {
throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
@@ -133,6 +224,37 @@ public class BpfNetMaps {
maybeThrow(err, "Unable to set child chain");
}
/**
* Get the specified firewall chain status.
*
* @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");
}
final long match = getMatchByFirewallChain(childChain);
try {
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");
}
return (config.val & match) != 0;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
"Unable to get firewall chain status: " + Os.strerror(e.errno));
}
}
/**
* Replaces the contents of the specified UID-based firewall chain.
*

View File

@@ -11383,6 +11383,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
@Override
public boolean getFirewallChainEnabled(final int chain) {
enforceNetworkStackOrSettingsPermission();
return mBpfNetMaps.getChainEnabled(chain);
}
@Override
public void replaceFirewallChain(final int chain, final int[] uids) {
enforceNetworkStackOrSettingsPermission();

View File

@@ -16,43 +16,92 @@
package com.android.server;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
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.net.INetd.PERMISSION_INTERNET;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.Mockito.verify;
import android.net.INetd;
import android.os.Build;
import android.os.ServiceSpecificException;
import androidx.test.filters.SmallTest;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.Struct.U32;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.TestBpfMap;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public final class BpfNetMapsTest {
private static final String TAG = "BpfNetMapsTest";
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
private static final int TEST_UID = 10086;
private static final int[] TEST_UIDS = {10002, 10003};
private static final String IFNAME = "wlan0";
private static final String CHAINNAME = "fw_dozable";
private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
private static final List<Integer> FIREWALL_CHAINS = List.of(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2,
FIREWALL_CHAIN_OEM_DENY_3
);
private BpfNetMaps mBpfNetMaps;
@Mock INetd mNetd;
private static final TestBpfMap<U32, U32> sConfigurationMap =
new TestBpfMap<>(U32.class, U32.class);
@Before
public void setUp() {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mBpfNetMaps = new BpfNetMaps(mNetd);
BpfNetMaps.initialize(makeDependencies());
sConfigurationMap.clear();
}
private static BpfNetMaps.Dependencies makeDependencies() {
return new BpfNetMaps.Dependencies() {
@Override
public BpfMap<U32, U32> getConfigurationMap() {
return sConfigurationMap;
}
};
}
@Test
@@ -65,4 +114,77 @@ public final class BpfNetMapsTest {
mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
}
private void doTestGetChainEnabled(final List<Integer> enableChains) throws Exception {
long match = 0;
for (final int chain: enableChains) {
match |= mBpfNetMaps.getMatchByFirewallChain(chain);
}
sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
for (final int chain: FIREWALL_CHAINS) {
final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
if (enableChains.contains(chain)) {
assertTrue("Expected getChainEnabled returns True, " + testCase,
mBpfNetMaps.getChainEnabled(chain));
} else {
assertFalse("Expected getChainEnabled returns False, " + testCase,
mBpfNetMaps.getChainEnabled(chain));
}
}
}
private void doTestGetChainEnabled(final int enableChain) throws Exception {
doTestGetChainEnabled(List.of(enableChain));
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testGetChainEnabled() throws Exception {
doTestGetChainEnabled(FIREWALL_CHAIN_DOZABLE);
doTestGetChainEnabled(FIREWALL_CHAIN_STANDBY);
doTestGetChainEnabled(FIREWALL_CHAIN_POWERSAVE);
doTestGetChainEnabled(FIREWALL_CHAIN_RESTRICTED);
doTestGetChainEnabled(FIREWALL_CHAIN_LOW_POWER_STANDBY);
doTestGetChainEnabled(FIREWALL_CHAIN_OEM_DENY_1);
doTestGetChainEnabled(FIREWALL_CHAIN_OEM_DENY_2);
doTestGetChainEnabled(FIREWALL_CHAIN_OEM_DENY_3);
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testGetChainEnabledMultipleChainEnabled() throws Exception {
doTestGetChainEnabled(List.of(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY));
doTestGetChainEnabled(List.of(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED));
doTestGetChainEnabled(FIREWALL_CHAINS);
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testGetChainEnabledInvalidChain() {
final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(-1 /* childChain */));
assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(1000 /* childChain */));
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testGetChainEnabledMissingConfiguration() {
// sConfigurationMap does not have entry for UID_RULES_CONFIGURATION_KEY
assertThrows(ServiceSpecificException.class,
() -> mBpfNetMaps.getChainEnabled(FIREWALL_CHAIN_DOZABLE));
}
@Test
@IgnoreAfter(Build.VERSION_CODES.S_V2)
public void testGetChainEnabledBeforeT() {
assertThrows(UnsupportedOperationException.class,
() -> mBpfNetMaps.getChainEnabled(FIREWALL_CHAIN_DOZABLE));
}
}