Writing data saver status to bpf is supported on Android V or later devices. Thus, read that from bpf if available. Test: atest FrameworksNetTests:android.net.connectivity.android.net.BpfNetMapsReaderTest Test: atest ConnectivityCoverageTests:android.net.connectivity.android.net.ConnectivityManagerTest Fix: 310801259 Change-Id: Ibd2616328d83f72ee6d2665239c3a44379d1ebf5
296 lines
12 KiB
Java
296 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2023 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.net;
|
|
|
|
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
|
|
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
|
|
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
|
|
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
|
|
import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
|
|
import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
|
|
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
|
|
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
|
|
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
|
|
import static android.net.BpfNetMapsUtils.isFirewallAllowList;
|
|
import static android.net.BpfNetMapsUtils.throwIfPreT;
|
|
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
|
|
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.RequiresApi;
|
|
import android.os.Build;
|
|
import android.os.ServiceSpecificException;
|
|
import android.system.ErrnoException;
|
|
import android.system.Os;
|
|
import android.util.Log;
|
|
|
|
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.IBpfMap;
|
|
import com.android.net.module.util.Struct.S32;
|
|
import com.android.net.module.util.Struct.U32;
|
|
import com.android.net.module.util.Struct.U8;
|
|
|
|
/**
|
|
* A helper class to *read* java BpfMaps.
|
|
* @hide
|
|
*/
|
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
|
|
public class BpfNetMapsReader {
|
|
private static final String TAG = BpfNetMapsReader.class.getSimpleName();
|
|
|
|
// Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
|
|
// BpfMap implementation.
|
|
|
|
// Bpf map to store various networking configurations, the format of the value is different
|
|
// for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
|
|
private final IBpfMap<S32, U32> mConfigurationMap;
|
|
// Bpf map to store per uid traffic control configurations.
|
|
// See {@link UidOwnerValue} for more detail.
|
|
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
|
|
private final IBpfMap<S32, U8> mDataSaverEnabledMap;
|
|
private final Dependencies mDeps;
|
|
|
|
// Bitmaps for calculating whether a given uid is blocked by firewall chains.
|
|
private static final long sMaskDropIfSet;
|
|
private static final long sMaskDropIfUnset;
|
|
|
|
static {
|
|
long maskDropIfSet = 0L;
|
|
long maskDropIfUnset = 0L;
|
|
|
|
for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
|
|
final long match = getMatchByFirewallChain(chain);
|
|
maskDropIfUnset |= match;
|
|
}
|
|
for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
|
|
final long match = getMatchByFirewallChain(chain);
|
|
maskDropIfSet |= match;
|
|
}
|
|
sMaskDropIfSet = maskDropIfSet;
|
|
sMaskDropIfUnset = maskDropIfUnset;
|
|
}
|
|
|
|
private static class SingletonHolder {
|
|
static final BpfNetMapsReader sInstance = new BpfNetMapsReader();
|
|
}
|
|
|
|
@NonNull
|
|
public static BpfNetMapsReader getInstance() {
|
|
return SingletonHolder.sInstance;
|
|
}
|
|
|
|
private BpfNetMapsReader() {
|
|
this(new Dependencies());
|
|
}
|
|
|
|
// While the production code uses the singleton to optimize for performance and deal with
|
|
// concurrent access, the test needs to use a non-static approach for dependency injection and
|
|
// mocking virtual bpf maps.
|
|
@VisibleForTesting
|
|
public BpfNetMapsReader(@NonNull Dependencies deps) {
|
|
if (!SdkLevel.isAtLeastT()) {
|
|
throw new UnsupportedOperationException(
|
|
BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
|
|
}
|
|
mDeps = deps;
|
|
mConfigurationMap = mDeps.getConfigurationMap();
|
|
mUidOwnerMap = mDeps.getUidOwnerMap();
|
|
mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap();
|
|
}
|
|
|
|
/**
|
|
* Dependencies of BpfNetMapReader, for injection in tests.
|
|
*/
|
|
@VisibleForTesting
|
|
public static class Dependencies {
|
|
/** Get the configuration map. */
|
|
public IBpfMap<S32, U32> getConfigurationMap() {
|
|
try {
|
|
return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
|
|
S32.class, U32.class);
|
|
} catch (ErrnoException e) {
|
|
throw new IllegalStateException("Cannot open configuration map", e);
|
|
}
|
|
}
|
|
|
|
/** Get the uid owner map. */
|
|
public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
|
|
try {
|
|
return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
|
|
S32.class, UidOwnerValue.class);
|
|
} catch (ErrnoException e) {
|
|
throw new IllegalStateException("Cannot open uid owner map", e);
|
|
}
|
|
}
|
|
|
|
/** Get the data saver enabled map. */
|
|
public IBpfMap<S32, U8> getDataSaverEnabledMap() {
|
|
try {
|
|
return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class,
|
|
U8.class);
|
|
} catch (ErrnoException e) {
|
|
throw new IllegalStateException("Cannot open data saver enabled map", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the specified firewall chain's status.
|
|
*
|
|
* @param chain 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 ServiceSpecificException in case of failure, with an error code indicating the
|
|
* cause of the failure.
|
|
*/
|
|
public boolean isChainEnabled(final int chain) {
|
|
return isChainEnabled(mConfigurationMap, chain);
|
|
}
|
|
|
|
/**
|
|
* Get firewall rule of specified firewall chain on specified uid.
|
|
*
|
|
* @param chain target chain
|
|
* @param uid target uid
|
|
* @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
|
|
* {@link ConnectivityManager#FIREWALL_RULE_DENY}.
|
|
* @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 int getUidRule(final int chain, final int uid) {
|
|
return getUidRule(mUidOwnerMap, chain, uid);
|
|
}
|
|
|
|
/**
|
|
* Get the specified firewall chain's status.
|
|
*
|
|
* @param configurationMap target configurationMap
|
|
* @param chain 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 ServiceSpecificException in case of failure, with an error code indicating the
|
|
* cause of the failure.
|
|
*/
|
|
public static boolean isChainEnabled(
|
|
final IBpfMap<S32, U32> configurationMap, final int chain) {
|
|
throwIfPreT("isChainEnabled is not available on pre-T devices");
|
|
|
|
final long match = getMatchByFirewallChain(chain);
|
|
try {
|
|
final U32 config = configurationMap.getValue(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));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get firewall rule of specified firewall chain on specified uid.
|
|
*
|
|
* @param uidOwnerMap target uidOwnerMap.
|
|
* @param chain target chain.
|
|
* @param uid target uid.
|
|
* @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
|
|
* @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 static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
|
|
final int chain, final int uid) {
|
|
throwIfPreT("getUidRule is not available on pre-T devices");
|
|
|
|
final long match = getMatchByFirewallChain(chain);
|
|
final boolean isAllowList = isFirewallAllowList(chain);
|
|
try {
|
|
final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
|
|
final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
|
|
return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
|
|
} catch (ErrnoException e) {
|
|
throw new ServiceSpecificException(e.errno,
|
|
"Unable to get uid rule status: " + Os.strerror(e.errno));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return whether the network is blocked by firewall chains for the given uid.
|
|
*
|
|
* @param uid The target uid.
|
|
* @param isNetworkMetered Whether the target network is metered.
|
|
* @param isDataSaverEnabled Whether the data saver is enabled.
|
|
*
|
|
* @return True if the network is blocked. Otherwise, false.
|
|
* @throws ServiceSpecificException if the read fails.
|
|
*
|
|
* @hide
|
|
*/
|
|
public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
|
|
boolean isDataSaverEnabled) {
|
|
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
|
|
|
|
final long uidRuleConfig;
|
|
final long uidMatch;
|
|
try {
|
|
uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
|
|
final UidOwnerValue value = mUidOwnerMap.getValue(new S32(uid));
|
|
uidMatch = (value != null) ? value.rule : 0L;
|
|
} catch (ErrnoException e) {
|
|
throw new ServiceSpecificException(e.errno,
|
|
"Unable to get firewall chain status: " + Os.strerror(e.errno));
|
|
}
|
|
|
|
final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
|
|
final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
|
|
if (blockedByAllowChains || blockedByDenyChains) {
|
|
return true;
|
|
}
|
|
|
|
if (!isNetworkMetered) return false;
|
|
if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
|
|
if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
|
|
return isDataSaverEnabled;
|
|
}
|
|
|
|
/**
|
|
* Get Data Saver enabled or disabled
|
|
*
|
|
* @return whether Data Saver is enabled or disabled.
|
|
* @throws ServiceSpecificException in case of failure, with an error code indicating the
|
|
* cause of the failure.
|
|
*/
|
|
public boolean getDataSaverEnabled() {
|
|
throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
|
|
|
|
// Note that this is not expected to be called until V given that it relies on the
|
|
// counterpart platform solution to set data saver status to bpf.
|
|
// See {@code NetworkManagementService#setDataSaverModeEnabled}.
|
|
if (!SdkLevel.isAtLeastV()) {
|
|
Log.wtf(TAG, "getDataSaverEnabled is not expected to be called on pre-V devices");
|
|
}
|
|
|
|
try {
|
|
return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
|
|
} catch (ErrnoException e) {
|
|
throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
|
|
+ Os.strerror(e.errno));
|
|
}
|
|
}
|
|
}
|