This is needed for data stall detection mechanism in NetworkStack to get the information about whether the network is blocked for a given uid and conditions. Because the API will be called frequently from NetworkStack to resolve all status for all uids on the device, the API cannot call into the service which creates IPC. Instead, the API need to directly access bpf maps in the user process to retrieve the status. In this case the user process is the network stack, the access control is provided by linux file permission and selinux. Test: atest FrameworksNetTests:android.net.connectivity.android.net.BpfNetMapsReaderTest Test: atest FrameworksNetTests:android.net.connectivity.android.net.ConnectivityManagerTest NO_IFTTT=Refactor only change for firewall chains definitions Bug: 297836825 Change-Id: Iaf983b71ec98cbfe5152dcfade8a3120f938f135
241 lines
9.6 KiB
Java
241 lines
9.6 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.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 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;
|
|
import com.android.net.module.util.Struct.S32;
|
|
import com.android.net.module.util.Struct.U32;
|
|
|
|
/**
|
|
* A helper class to *read* java BpfMaps.
|
|
* @hide
|
|
*/
|
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
|
|
public class BpfNetMapsReader {
|
|
// 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 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();
|
|
}
|
|
|
|
/**
|
|
* 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 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<Struct.S32, Struct.U32> configurationMap, final int chain) {
|
|
throwIfPreT("isChainEnabled is not available on pre-T devices");
|
|
|
|
final long match = getMatchByFirewallChain(chain);
|
|
try {
|
|
final Struct.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<Struct.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 Struct.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.
|
|
*
|
|
* @return True if the network is blocked. Otherwise, false.
|
|
* @throws ServiceSpecificException if the read fails.
|
|
*
|
|
* @hide
|
|
*/
|
|
public boolean isUidBlockedByFirewallChains(final int uid) {
|
|
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);
|
|
return blockedByAllowChains || blockedByDenyChains;
|
|
}
|
|
}
|