Merge "Remove an NPE in tearDown" into main
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index a280046..4d1e7ef 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -167,7 +167,6 @@
@Override
public boolean addIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) {
- if (!isInitialized()) return false;
// RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
@@ -185,7 +184,6 @@
@Override
public boolean removeIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) {
- if (!isInitialized()) return false;
// RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
@@ -200,8 +198,6 @@
@Override
public boolean addIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) {
- if (!isInitialized()) return false;
-
final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
final Tether6Value value = rule.makeTether6Value();
@@ -217,8 +213,6 @@
@Override
public boolean removeIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) {
- if (!isInitialized()) return false;
-
try {
mBpfDownstream6Map.deleteEntry(rule.makeTetherDownstream6Key());
} catch (ErrnoException e) {
@@ -234,8 +228,6 @@
@Override
@Nullable
public SparseArray<TetherStatsValue> tetherOffloadGetStats() {
- if (!isInitialized()) return null;
-
final SparseArray<TetherStatsValue> tetherStatsList = new SparseArray<TetherStatsValue>();
try {
// The reported tether stats are total data usage for all currently-active upstream
@@ -250,8 +242,6 @@
@Override
public boolean tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes) {
- if (!isInitialized()) return false;
-
// The common case is an update, where the stats already exist,
// hence we read first, even though writing with BPF_NOEXIST
// first would make the code simpler.
@@ -307,8 +297,6 @@
@Override
@Nullable
public TetherStatsValue tetherOffloadGetAndClearStats(int ifIndex) {
- if (!isInitialized()) return null;
-
// getAndClearTetherOffloadStats is called after all offload rules have already been
// deleted for the given upstream interface. Before starting to do cleanup stuff in this
// function, use synchronizeKernelRCU to make sure that all the current running eBPF
@@ -354,8 +342,6 @@
@Override
public boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key,
@NonNull Tether4Value value) {
- if (!isInitialized()) return false;
-
try {
if (downstream) {
mBpfDownstream4Map.insertEntry(key, value);
@@ -379,8 +365,6 @@
@Override
public boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key) {
- if (!isInitialized()) return false;
-
try {
if (downstream) {
if (!mBpfDownstream4Map.deleteEntry(key)) return false; // Rule did not exist
@@ -413,8 +397,6 @@
@Override
public void tetherOffloadRuleForEach(boolean downstream,
@NonNull ThrowingBiConsumer<Tether4Key, Tether4Value> action) {
- if (!isInitialized()) return;
-
try {
if (downstream) {
mBpfDownstream4Map.forEach(action);
@@ -428,8 +410,6 @@
@Override
public boolean attachProgram(String iface, boolean downstream, boolean ipv4) {
- if (!isInitialized()) return false;
-
try {
BpfUtils.attachProgram(iface, downstream, ipv4);
} catch (IOException e) {
@@ -441,8 +421,6 @@
@Override
public boolean detachProgram(String iface, boolean ipv4) {
- if (!isInitialized()) return false;
-
try {
BpfUtils.detachProgram(iface, ipv4);
} catch (IOException e) {
@@ -460,8 +438,6 @@
@Override
public boolean addDevMap(int ifIndex) {
- if (!isInitialized()) return false;
-
try {
mBpfDevMap.updateEntry(new TetherDevKey(ifIndex), new TetherDevValue(ifIndex));
} catch (ErrnoException e) {
@@ -473,8 +449,6 @@
@Override
public boolean removeDevMap(int ifIndex) {
- if (!isInitialized()) return false;
-
try {
mBpfDevMap.deleteEntry(new TetherDevKey(ifIndex));
} catch (ErrnoException e) {
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index a851410..e030902 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -127,6 +127,7 @@
// TODO: have this configurable
private static final int DHCP_LEASE_TIME_SECS = 3600;
+ private static final int NO_UPSTREAM = 0;
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString("00:00:00:00:00:00");
private static final String TAG = "IpServer";
@@ -259,6 +260,12 @@
private int mLastError;
private int mServingMode;
private InterfaceSet mUpstreamIfaceSet; // may change over time
+ // mInterfaceParams can't be final now because IpServer will be created when receives
+ // WIFI_AP_STATE_CHANGED broadcasts or when it detects that the wifi interface has come up.
+ // In the latter case, the interface is not fully initialized and the MAC address might not
+ // be correct (it will be set with a randomized MAC address later).
+ // TODO: Consider create the IpServer only when tethering want to enable it, then we can
+ // make mInterfaceParams final.
private InterfaceParams mInterfaceParams;
// TODO: De-duplicate this with mLinkProperties above. Currently, these link
// properties are those selected by the IPv6TetheringCoordinator and relayed
@@ -740,7 +747,7 @@
RaParams params = null;
String upstreamIface = null;
InterfaceParams upstreamIfaceParams = null;
- int upstreamIfIndex = 0;
+ int upstreamIfIndex = NO_UPSTREAM;
if (v6only != null) {
upstreamIface = v6only.getInterfaceName();
@@ -772,7 +779,7 @@
// CMD_TETHER_CONNECTION_CHANGED. Adding the mapping update here to the avoid potential
// timing issue. It prevents that the IPv6 capability is updated later than
// CMD_TETHER_CONNECTION_CHANGED.
- mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface);
+ mBpfCoordinator.maybeAddUpstreamToLookupTable(upstreamIfIndex, upstreamIface);
// If v6only is null, we pass in null to setRaParams(), which handles
// deprecation of any existing RA data.
@@ -780,8 +787,7 @@
// Not support BPF on virtual upstream interface
final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
- updateIpv6ForwardingRules(
- mLastIPv6UpstreamIfindex, upstreamIfIndex, upstreamSupportsBpf, null);
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, upstreamSupportsBpf);
mLastIPv6LinkProperties = v6only;
mLastIPv6UpstreamIfindex = upstreamIfIndex;
mUpstreamSupportsBpf = upstreamSupportsBpf;
@@ -887,25 +893,23 @@
}
}
- // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
- // changes or if a neighbor event is received.
+ private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
+ return supportsBpf ? ifindex : NO_UPSTREAM;
+ }
+
+ // Handles updates to IPv6 forwarding rules if the upstream changes.
private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
- boolean upstreamSupportsBpf, NeighborEvent e) {
- // If no longer have an upstream or upstream not supports BPF, clear forwarding rules and do
- // nothing else.
- // TODO: Rather than always clear rules, ensure whether ipv6 ever enable first.
- if (upstreamIfindex == 0 || !upstreamSupportsBpf) {
- mBpfCoordinator.tetherOffloadRuleClear(this);
- return;
- }
-
+ boolean upstreamSupportsBpf) {
// If the upstream interface has changed, remove all rules and re-add them with the new
- // upstream interface.
+ // upstream interface. If upstream is a virtual network, treated as no upstream.
if (prevUpstreamIfindex != upstreamIfindex) {
- mBpfCoordinator.tetherOffloadRuleUpdate(this, upstreamIfindex);
+ mBpfCoordinator.updateAllIpv6Rules(this, this.mInterfaceParams,
+ getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf));
}
+ }
- // If we're here to process a NeighborEvent, do so now.
+ // Handles updates to IPv6 downstream rules if a neighbor event is received.
+ private void addOrRemoveIpv6Downstream(NeighborEvent e) {
// mInterfaceParams must be non-null or the event would not have arrived.
if (e == null) return;
if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
@@ -917,8 +921,9 @@
// Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
// never add rules with a null MAC, only delete them.
MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- Ipv6DownstreamRule rule = new Ipv6DownstreamRule(upstreamIfindex, mInterfaceParams.index,
- (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
+ Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
+ getInterfaceIndexForRule(mLastIPv6UpstreamIfindex, mUpstreamSupportsBpf),
+ mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
if (e.isValid()) {
mBpfCoordinator.addIpv6DownstreamRule(this, rule);
} else {
@@ -951,8 +956,7 @@
if (mInterfaceParams != null
&& mInterfaceParams.index == e.ifindex
&& mInterfaceParams.hasMacAddress) {
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex,
- mUpstreamSupportsBpf, e);
+ addOrRemoveIpv6Downstream(e);
updateClientInfoIpv4(e);
}
}
@@ -1285,6 +1289,7 @@
@Override
public void exit() {
cleanupUpstream();
+ mBpfCoordinator.clearAllIpv6Rules(IpServer.this);
super.exit();
}
@@ -1303,7 +1308,8 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
- mBpfCoordinator.tetherOffloadRuleClear(IpServer.this);
+ mBpfCoordinator.updateAllIpv6Rules(
+ IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM);
}
private void cleanupUpstreamInterface(String upstreamIface) {
@@ -1370,7 +1376,7 @@
final InterfaceParams upstreamIfaceParams =
mDeps.getInterfaceParams(ifname);
if (upstreamIfaceParams != null) {
- mBpfCoordinator.addUpstreamNameToLookupTable(
+ mBpfCoordinator.maybeAddUpstreamToLookupTable(
upstreamIfaceParams.index, ifname);
}
}
@@ -1430,6 +1436,8 @@
class UnavailableState extends State {
@Override
public void enter() {
+ // TODO: move mIpNeighborMonitor.stop() to TetheredState#exit, and trigger a neighbours
+ // dump after starting mIpNeighborMonitor.
mIpNeighborMonitor.stop();
mLastError = TETHER_ERROR_NO_ERROR;
sendInterfaceState(STATE_UNAVAILABLE);
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 1b23a6c..46c815f 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -50,6 +50,7 @@
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -153,6 +154,7 @@
static final int NF_CONNTRACK_UDP_TIMEOUT_STREAM = 180;
@VisibleForTesting
static final int INVALID_MTU = 0;
+ static final int NO_UPSTREAM = 0;
// List of TCP port numbers which aren't offloaded because the packets require the netfilter
// conntrack helper. See also TetherController::setForwardRules in netd.
@@ -221,6 +223,23 @@
// TODO: Remove the unused interface name.
private final SparseArray<String> mInterfaceNames = new SparseArray<>();
+ // How IPv6 upstream rules and downstream rules are managed in BpfCoordinator:
+ // 1. Each upstream rule represents a downstream interface to an upstream interface forwarding.
+ // No upstream rule will be exist if there is no upstream interface.
+ // Note that there is at most one upstream interface for a given downstream interface.
+ // 2. Each downstream rule represents an IPv6 neighbor, regardless of the existence of the
+ // upstream interface. If the upstream is not present, the downstream rules have an upstream
+ // interface index of NO_UPSTREAM, only exist in BpfCoordinator and won't be written to the
+ // BPF map. When the upstream comes back, those downstream rules will be updated by calling
+ // Ipv6DownstreamRule#onNewUpstream and written to the BPF map again. We don't remove the
+ // downstream rules when upstream is lost is because the upstream may come back with the
+ // same prefix and we won't receive any neighbor update event in this case.
+ // TODO: Remove downstream rules when upstream is lost and dump neighbors table when upstream
+ // interface comes back in order to reconstruct the downstream rules.
+ // 3. It is the same thing for BpfCoordinator if there is no upstream interface or the upstream
+ // interface is a virtual interface (which currently not supports BPF). In this case,
+ // IpServer will update its upstream ifindex to NO_UPSTREAM to the BpfCoordinator.
+
// Map of downstream rule maps. Each of these maps represents the IPv6 forwarding rules for a
// given downstream. Each map:
// - Is owned by the IpServer that is responsible for that downstream.
@@ -240,6 +259,16 @@
private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6DownstreamRule>>
mIpv6DownstreamRules = new LinkedHashMap<>();
+ // Map of IPv6 upstream rules maps. Each of these maps represents the IPv6 upstream rules for a
+ // given downstream. Each map:
+ // - Is owned by the IpServer that is responsible for that downstream.
+ // - Must only be modified by that IpServer.
+ // - Is created when the IpServer adds its first upstream rule, and deleted when the IpServer
+ // deletes its last upstream rule (or clears its upstream rules)
+ // - Each upstream rule in the ArraySet is corresponding to an upstream interface.
+ private final ArrayMap<IpServer, ArraySet<Ipv6UpstreamRule>>
+ mIpv6UpstreamRules = new ArrayMap<>();
+
// Map of downstream client maps. Each of these maps represents the IPv4 clients for a given
// downstream. Needed to build IPv4 forwarding rules when conntrack events are received.
// Each map:
@@ -603,130 +632,173 @@
}
/**
- * Add IPv6 downstream rule. After adding the first rule on a given upstream, must add the data
- * limit on the given upstream.
- * Note that this can be only called on handler thread.
+ * Add IPv6 upstream rule. After adding the first rule on a given upstream, must add the
+ * data limit on the given upstream.
*/
- public void addIpv6DownstreamRule(
- @NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
+ private void addIpv6UpstreamRule(
+ @NonNull final IpServer ipServer, @NonNull final Ipv6UpstreamRule rule) {
if (!isUsingBpf()) return;
- // TODO: Perhaps avoid to add a duplicate rule.
- if (!mBpfCoordinatorShim.addIpv6DownstreamRule(rule)) return;
-
- if (!mIpv6DownstreamRules.containsKey(ipServer)) {
- mIpv6DownstreamRules.put(ipServer, new LinkedHashMap<Inet6Address,
- Ipv6DownstreamRule>());
- }
- LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules = mIpv6DownstreamRules.get(ipServer);
-
// Add upstream and downstream interface index to dev map.
maybeAddDevMap(rule.upstreamIfindex, rule.downstreamIfindex);
// When the first rule is added to an upstream, setup upstream forwarding and data limit.
maybeSetLimit(rule.upstreamIfindex);
- if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
- // TODO: support upstream forwarding on non-point-to-point interfaces.
- // TODO: get the MTU from LinkProperties and update the rules when it changes.
- Ipv6UpstreamRule upstreamRule = new Ipv6UpstreamRule(rule.upstreamIfindex,
- rule.downstreamIfindex, IPV6_ZERO_PREFIX64, rule.srcMac, NULL_MAC_ADDRESS,
- NULL_MAC_ADDRESS);
- if (!mBpfCoordinatorShim.addIpv6UpstreamRule(upstreamRule)) {
- mLog.e("Failed to add upstream IPv6 forwarding rule: " + upstreamRule);
- }
+ // TODO: support upstream forwarding on non-point-to-point interfaces.
+ // TODO: get the MTU from LinkProperties and update the rules when it changes.
+ if (!mBpfCoordinatorShim.addIpv6UpstreamRule(rule)) {
+ return;
}
- // Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to
- // check if it is about adding a first rule for a given upstream.
+ ArraySet<Ipv6UpstreamRule> rules = mIpv6UpstreamRules.computeIfAbsent(
+ ipServer, k -> new ArraySet<Ipv6UpstreamRule>());
+ rules.add(rule);
+ }
+
+ /**
+ * Clear all IPv6 upstream rules for a given downstream. After removing the last rule on a given
+ * upstream, must clear data limit, update the last tether stats and remove the tether stats in
+ * the BPF maps.
+ */
+ private void clearIpv6UpstreamRules(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+
+ final ArraySet<Ipv6UpstreamRule> upstreamRules = mIpv6UpstreamRules.remove(ipServer);
+ if (upstreamRules == null) return;
+
+ int upstreamIfindex = 0;
+ for (Ipv6UpstreamRule rule: upstreamRules) {
+ if (upstreamIfindex != 0 && rule.upstreamIfindex != upstreamIfindex) {
+ Log.wtf(TAG, "BUG: upstream rules point to more than one interface");
+ }
+ upstreamIfindex = rule.upstreamIfindex;
+ mBpfCoordinatorShim.removeIpv6UpstreamRule(rule);
+ }
+ // Clear the limit if there are no more rules on the given upstream.
+ // Using upstreamIfindex outside the loop is fine because all the rules for a given IpServer
+ // will always have the same upstream index (since they are always added all together by
+ // updateAllIpv6Rules).
+ // The upstreamIfindex can't be 0 because we won't add an Ipv6UpstreamRule with
+ // upstreamIfindex == 0 and if there is no Ipv6UpstreamRule for an IpServer, it will be
+ // removed from mIpv6UpstreamRules.
+ if (upstreamIfindex == 0) {
+ Log.wtf(TAG, "BUG: upstream rules have empty Set or rule.upstreamIfindex == 0");
+ return;
+ }
+ maybeClearLimit(upstreamIfindex);
+ }
+
+ /**
+ * Add IPv6 downstream rule.
+ * Note that this can be only called on handler thread.
+ */
+ public void addIpv6DownstreamRule(
+ @NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
+ if (!isUsingBpf()) return;
+
+ // TODO: Perhaps avoid to add a duplicate rule.
+ if (rule.upstreamIfindex != NO_UPSTREAM
+ && !mBpfCoordinatorShim.addIpv6DownstreamRule(rule)) return;
+
+ LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
+ mIpv6DownstreamRules.computeIfAbsent(ipServer,
+ k -> new LinkedHashMap<Inet6Address, Ipv6DownstreamRule>());
rules.put(rule.address, rule);
}
/**
- * Remove IPv6 downstream rule. After removing the last rule on a given upstream, must clear
- * data limit, update the last tether stats and remove the tether stats in the BPF maps.
+ * Remove IPv6 downstream rule.
* Note that this can be only called on handler thread.
*/
public void removeIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
- if (!mBpfCoordinatorShim.removeIpv6DownstreamRule(rule)) return;
+ if (rule.upstreamIfindex != NO_UPSTREAM
+ && !mBpfCoordinatorShim.removeIpv6DownstreamRule(rule)) return;
LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules = mIpv6DownstreamRules.get(ipServer);
if (rules == null) return;
- // Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if
- // the last rule is removed for a given upstream. If no rule is removed, return early.
- // Avoid unnecessary work on a non-existent rule which may have never been added or
- // removed already.
+ // If no rule is removed, return early. Avoid unnecessary work on a non-existent rule which
+ // may have never been added or removed already.
if (rules.remove(rule.address) == null) return;
// Remove the downstream entry if it has no more rule.
if (rules.isEmpty()) {
mIpv6DownstreamRules.remove(ipServer);
}
+ }
- // If no more rules between this upstream and downstream, stop upstream forwarding.
- if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
- Ipv6UpstreamRule upstreamRule = new Ipv6UpstreamRule(rule.upstreamIfindex,
- rule.downstreamIfindex, IPV6_ZERO_PREFIX64, rule.srcMac, NULL_MAC_ADDRESS,
- NULL_MAC_ADDRESS);
- if (!mBpfCoordinatorShim.removeIpv6UpstreamRule(upstreamRule)) {
- mLog.e("Failed to remove upstream IPv6 forwarding rule: " + upstreamRule);
- }
+ /**
+ * Clear all downstream rules for a given IpServer and return a copy of all removed rules.
+ */
+ @Nullable
+ private Collection<Ipv6DownstreamRule> clearIpv6DownstreamRules(
+ @NonNull final IpServer ipServer) {
+ final LinkedHashMap<Inet6Address, Ipv6DownstreamRule> downstreamRules =
+ mIpv6DownstreamRules.remove(ipServer);
+ if (downstreamRules == null) return null;
+
+ final Collection<Ipv6DownstreamRule> removedRules = downstreamRules.values();
+ for (final Ipv6DownstreamRule rule : removedRules) {
+ if (rule.upstreamIfindex == NO_UPSTREAM) continue;
+ mBpfCoordinatorShim.removeIpv6DownstreamRule(rule);
}
-
- // Do cleanup functionality if there is no more rule on the given upstream.
- maybeClearLimit(rule.upstreamIfindex);
+ return removedRules;
}
/**
* Clear all forwarding rules for a given downstream.
* Note that this can be only called on handler thread.
- * TODO: rename to tetherOffloadRuleClear6 because of IPv6 only.
*/
- public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) {
+ public void clearAllIpv6Rules(@NonNull final IpServer ipServer) {
if (!isUsingBpf()) return;
- final LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
- mIpv6DownstreamRules.get(ipServer);
- if (rules == null) return;
-
- // Need to build a rule list because the rule map may be changed in the iteration.
- for (final Ipv6DownstreamRule rule : new ArrayList<Ipv6DownstreamRule>(rules.values())) {
- removeIpv6DownstreamRule(ipServer, rule);
- }
+ // Clear downstream rules first, because clearing upstream rules fetches the stats, and
+ // fetching the stats requires that no rules be forwarding traffic to or from the upstream.
+ clearIpv6DownstreamRules(ipServer);
+ clearIpv6UpstreamRules(ipServer);
}
/**
- * Update existing forwarding rules to new upstream for a given downstream.
+ * Delete all upstream and downstream rules for the passed-in IpServer, and if the new upstream
+ * is nonzero, reapply them to the new upstream.
* Note that this can be only called on handler thread.
*/
- public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) {
+ public void updateAllIpv6Rules(@NonNull final IpServer ipServer,
+ final InterfaceParams interfaceParams, int newUpstreamIfindex) {
if (!isUsingBpf()) return;
- final LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
- mIpv6DownstreamRules.get(ipServer);
- if (rules == null) return;
+ // Remove IPv6 downstream rules. Remove the old ones before adding the new rules, otherwise
+ // we need to keep a copy of the old rules.
+ // We still need to keep the downstream rules even when the upstream goes away because it
+ // may come back with the same prefixes (unlikely, but possible). Neighbor entries won't be
+ // deleted and we're not expected to receive new Neighbor events in this case.
+ // TODO: Add new rule first to reduce the latency which has no rule. But this is okay
+ // because if this is a new upstream, it will probably have different prefixes than
+ // the one these downstream rules are in. If so, they will never see any downstream
+ // traffic before new neighbor entries are created.
+ final Collection<Ipv6DownstreamRule> deletedDownstreamRules =
+ clearIpv6DownstreamRules(ipServer);
- // Need to build a rule list because the rule map may be changed in the iteration.
- // First remove all the old rules, then add all the new rules. This is because the upstream
- // forwarding code in addIpv6DownstreamRule cannot support rules on two upstreams at the
- // same time. Deleting the rules first ensures that upstream forwarding is disabled on the
- // old upstream when the last rule is removed from it, and re-enabled on the new upstream
- // when the first rule is added to it.
- // TODO: Once the IPv6 client processing code has moved from IpServer to BpfCoordinator, do
- // something smarter.
- final ArrayList<Ipv6DownstreamRule> rulesCopy = new ArrayList<>(rules.values());
- for (final Ipv6DownstreamRule rule : rulesCopy) {
- // Remove the old rule before adding the new one because the map uses the same key for
- // both rules. Reversing the processing order causes that the new rule is removed as
- // unexpected.
- // TODO: Add new rule first to reduce the latency which has no rule.
- removeIpv6DownstreamRule(ipServer, rule);
+ // Remove IPv6 upstream rules. Downstream rules must be removed first because
+ // BpfCoordinatorShimImpl#tetherOffloadGetAndClearStats will be called after the removal of
+ // the last upstream rule and it requires that no rules be forwarding traffic to or from
+ // that upstream.
+ clearIpv6UpstreamRules(ipServer);
+
+ // Add new upstream rules.
+ if (newUpstreamIfindex != 0 && interfaceParams != null && interfaceParams.macAddr != null) {
+ addIpv6UpstreamRule(ipServer, new Ipv6UpstreamRule(
+ newUpstreamIfindex, interfaceParams.index, IPV6_ZERO_PREFIX64,
+ interfaceParams.macAddr, NULL_MAC_ADDRESS, NULL_MAC_ADDRESS));
}
- for (final Ipv6DownstreamRule rule : rulesCopy) {
+
+ // Add updated downstream rules.
+ if (deletedDownstreamRules == null) return;
+ for (final Ipv6DownstreamRule rule : deletedDownstreamRules) {
addIpv6DownstreamRule(ipServer, rule.onNewUpstream(newUpstreamIfindex));
}
}
@@ -737,7 +809,7 @@
* expects the interface name in NetworkStats object.
* Note that this can be only called on handler thread.
*/
- public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) {
+ public void maybeAddUpstreamToLookupTable(int upstreamIfindex, @Nullable String upstreamIface) {
if (!isUsingBpf()) return;
if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return;
@@ -1007,7 +1079,7 @@
* TODO: consider error handling if the attach program failed.
*/
public void maybeAttachProgram(@NonNull String intIface, @NonNull String extIface) {
- if (isVcnInterface(extIface)) return;
+ if (!isUsingBpf() || isVcnInterface(extIface)) return;
if (forwardingPairExists(intIface, extIface)) return;
@@ -1031,6 +1103,8 @@
* Detach BPF program
*/
public void maybeDetachProgram(@NonNull String intIface, @NonNull String extIface) {
+ if (!isUsingBpf()) return;
+
forwardingPairRemove(intIface, extIface);
// Detaching program may fail because the interface has been removed already.
@@ -1967,9 +2041,8 @@
}
private int getInterfaceIndexFromRules(@NonNull String ifName) {
- for (LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules :
- mIpv6DownstreamRules.values()) {
- for (Ipv6DownstreamRule rule : rules.values()) {
+ for (ArraySet<Ipv6UpstreamRule> rules : mIpv6UpstreamRules.values()) {
+ for (Ipv6UpstreamRule rule : rules) {
final int upstreamIfindex = rule.upstreamIfindex;
if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) {
return upstreamIfindex;
@@ -1987,6 +2060,7 @@
}
private boolean sendDataLimitToBpfMap(int ifIndex, long quotaBytes) {
+ if (!isUsingBpf()) return false;
if (ifIndex == 0) {
Log.wtf(TAG, "Invalid interface index.");
return false;
@@ -2060,28 +2134,14 @@
// TODO: Rename to isAnyIpv6RuleOnUpstream and define an isAnyRuleOnUpstream method that called
// both isAnyIpv6RuleOnUpstream and mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream.
private boolean isAnyRuleOnUpstream(int upstreamIfindex) {
- for (LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules :
- mIpv6DownstreamRules.values()) {
- for (Ipv6DownstreamRule rule : rules.values()) {
+ for (ArraySet<Ipv6UpstreamRule> rules : mIpv6UpstreamRules.values()) {
+ for (Ipv6UpstreamRule rule : rules) {
if (upstreamIfindex == rule.upstreamIfindex) return true;
}
}
return false;
}
- private boolean isAnyRuleFromDownstreamToUpstream(int downstreamIfindex, int upstreamIfindex) {
- for (LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules :
- mIpv6DownstreamRules.values()) {
- for (Ipv6DownstreamRule rule : rules.values()) {
- if (downstreamIfindex == rule.downstreamIfindex
- && upstreamIfindex == rule.upstreamIfindex) {
- return true;
- }
- }
- }
- return false;
- }
-
// TODO: remove the index from map while the interface has been removed because the map size
// is 64 entries. See packages\modules\Connectivity\Tethering\bpf_progs\offload.c.
private void maybeAddDevMap(int upstreamIfindex, int downstreamIfindex) {
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index c0718d1..d497a4d 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -158,6 +158,7 @@
private static final String UPSTREAM_IFACE = "upstream0";
private static final String UPSTREAM_IFACE2 = "upstream1";
private static final String IPSEC_IFACE = "ipsec0";
+ private static final int NO_UPSTREAM = 0;
private static final int UPSTREAM_IFINDEX = 101;
private static final int UPSTREAM_IFINDEX2 = 102;
private static final int IPSEC_IFINDEX = 103;
@@ -274,8 +275,18 @@
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(upstreamIface);
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
+ if (usingBpfOffload) {
+ InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface);
+ assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams);
+ verify(mBpfCoordinator).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, interfaceParams.index);
+ verifyStartUpstreamIpv6Forwarding(null, interfaceParams.index);
+ } else {
+ verifyNoUpstreamIpv6ForwardingChange(null);
+ }
}
- reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
+ reset(mCallback, mAddressCoordinator);
+ resetNetdBpfMapAndCoordinator();
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
anyBoolean())).thenReturn(mTestAddress);
}
@@ -531,7 +542,7 @@
InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
- inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX,
+ inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX,
UPSTREAM_IFACE);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
@@ -553,7 +564,7 @@
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>.
- inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2,
+ inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
@@ -578,7 +589,7 @@
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
// tetherAddForward.
- inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2,
+ inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
@@ -606,7 +617,7 @@
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
// ipfwdAddInterfaceForward.
- inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2,
+ inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
@@ -627,7 +638,15 @@
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
+ inOrder.verify(mBpfCoordinator).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM);
+ if (!mBpfDeps.isAtLeastS()) {
+ inOrder.verify(mNetd).tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX);
+ }
+ // When tethering stops, upstream interface is set to zero and thus clearing all upstream
+ // rules. Downstream rules are needed to be cleared explicitly by calling
+ // BpfCoordinator#clearAllIpv6Rules in TetheredState#exit.
+ inOrder.verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
@@ -1064,13 +1083,12 @@
recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA);
verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
- // Events on this interface are received and sent to netd.
+ // Events on this interface are received and sent to BpfCoordinator.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
verify(mBpfCoordinator).addIpv6DownstreamRule(
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
@@ -1078,7 +1096,6 @@
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
- verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
// Link-local and multicast neighbors are ignored.
@@ -1094,7 +1111,6 @@
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macNull));
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macNull);
- verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
// A neighbor that is deleted causes the rule to be removed.
@@ -1103,12 +1119,10 @@
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macNull));
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull);
- verifyStopUpstreamIpv6Forwarding(null);
resetNetdBpfMapAndCoordinator();
// Upstream changes result in updating the rules.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
resetNetdBpfMapAndCoordinator();
@@ -1116,89 +1130,100 @@
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
- verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2);
+ verify(mBpfCoordinator).updateAllIpv6Rules(mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2);
verifyTetherOffloadRuleRemove(inOrder,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
verifyTetherOffloadRuleRemove(inOrder,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
verifyStopUpstreamIpv6Forwarding(inOrder);
- verifyTetherOffloadRuleAdd(inOrder,
- UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2);
verifyTetherOffloadRuleAdd(inOrder,
+ UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
+ verifyTetherOffloadRuleAdd(inOrder,
UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB);
- verifyNoUpstreamIpv6ForwardingChange(inOrder);
resetNetdBpfMapAndCoordinator();
// When the upstream is lost, rules are removed.
dispatchTetherConnectionChanged(null, null, 0);
- // Clear function is called two times by:
+ // Upstream clear function is called two times by:
// - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost.
// - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost.
// See dispatchTetherConnectionChanged.
- verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer);
+ verify(mBpfCoordinator, times(2)).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM);
+ verifyStopUpstreamIpv6Forwarding(inOrder);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB);
- verifyStopUpstreamIpv6Forwarding(inOrder);
+ // Upstream lost doesn't clear the downstream rules from BpfCoordinator.
+ // Do that here.
+ recvDelNeigh(myIfindex, neighA, NUD_STALE, macA);
+ recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
+ verify(mBpfCoordinator).removeIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(NO_UPSTREAM, neighA, macNull));
+ verify(mBpfCoordinator).removeIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(NO_UPSTREAM, neighB, macNull));
resetNetdBpfMapAndCoordinator();
- // If the upstream is IPv4-only, no rules are added.
+ // If the upstream is IPv4-only, no IPv6 rules are added to BPF map.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
- // Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost.
- verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
verifyNoUpstreamIpv6ForwardingChange(null);
+ // Downstream rules are only added to BpfCoordinator but not BPF map.
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(NO_UPSTREAM, neighA, macA));
+ verifyNeverTetherOffloadRuleAdd();
verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
- // Rules can be added again once upstream IPv6 connectivity is available.
+ // Rules can be added again once upstream IPv6 connectivity is available. The existing rules
+ // with an upstream of NO_UPSTREAM are reapplied.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
+ verifyTetherOffloadRuleAdd(null,
+ UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
verify(mBpfCoordinator).addIpv6DownstreamRule(
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
- verify(mBpfCoordinator, never()).addIpv6DownstreamRule(
- mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
- verifyNeverTetherOffloadRuleAdd(
- UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
// If upstream IPv6 connectivity is lost, rules are removed.
resetNetdBpfMapAndCoordinator();
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
- verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
+ verify(mBpfCoordinator).updateAllIpv6Rules(mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
verifyStopUpstreamIpv6Forwarding(null);
- // When the interface goes down, rules are removed.
+ // When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules
+ // are reapplied.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
- recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
verify(mBpfCoordinator).addIpv6DownstreamRule(
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
verify(mBpfCoordinator).addIpv6DownstreamRule(
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
resetNetdBpfMapAndCoordinator();
+ // When the downstream interface goes down, rules are removed.
mIpServer.stop();
mLooper.dispatchAll();
- verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
+ verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
+ verifyStopUpstreamIpv6Forwarding(null);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
- verifyStopUpstreamIpv6Forwarding(null);
verify(mIpNeighborMonitor).stop();
resetNetdBpfMapAndCoordinator();
}
@@ -1228,7 +1253,6 @@
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neigh, macA));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macA);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
@@ -1236,7 +1260,14 @@
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neigh, macNull));
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macNull);
- verifyStopUpstreamIpv6Forwarding(null);
+ resetNetdBpfMapAndCoordinator();
+
+ // Upstream IPv6 connectivity change causes upstream rules change.
+ LinkProperties lp2 = new LinkProperties();
+ lp2.setInterfaceName(UPSTREAM_IFACE2);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0);
+ verify(mBpfCoordinator).updateAllIpv6Rules(mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2);
resetNetdBpfMapAndCoordinator();
// [2] Disable BPF offload.
@@ -1247,12 +1278,16 @@
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
verifyNeverTetherOffloadRuleAdd();
- verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
verifyNeverTetherOffloadRuleRemove();
+ resetNetdBpfMapAndCoordinator();
+
+ // Upstream IPv6 connectivity change doesn't cause the rule to be added or removed.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0);
verifyNoUpstreamIpv6ForwardingChange(null);
+ verifyNeverTetherOffloadRuleRemove();
resetNetdBpfMapAndCoordinator();
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 04eb430..601f587 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -169,6 +169,8 @@
private static final String UPSTREAM_IFACE = "rmnet0";
private static final String UPSTREAM_XLAT_IFACE = "v4-rmnet0";
private static final String UPSTREAM_IFACE2 = "wlan0";
+ private static final String DOWNSTREAM_IFACE = "downstream1";
+ private static final String DOWNSTREAM_IFACE2 = "downstream2";
private static final MacAddress DOWNSTREAM_MAC = MacAddress.fromString("12:34:56:78:90:ab");
private static final MacAddress DOWNSTREAM_MAC2 = MacAddress.fromString("ab:90:78:56:34:12");
@@ -213,6 +215,11 @@
private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams(
UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.fromString("44:55:66:00:00:0c"),
NetworkStackConstants.ETHER_MTU);
+ private static final InterfaceParams DOWNSTREAM_IFACE_PARAMS = new InterfaceParams(
+ DOWNSTREAM_IFACE, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, NetworkStackConstants.ETHER_MTU);
+ private static final InterfaceParams DOWNSTREAM_IFACE_PARAMS2 = new InterfaceParams(
+ DOWNSTREAM_IFACE2, DOWNSTREAM_IFINDEX2, DOWNSTREAM_MAC2,
+ NetworkStackConstants.ETHER_MTU);
private static final Map<Integer, UpstreamInformation> UPSTREAM_INFORMATIONS = Map.of(
UPSTREAM_IFINDEX, new UpstreamInformation(UPSTREAM_IFACE_PARAMS,
@@ -640,24 +647,6 @@
}
}
- private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex,
- MacAddress downstreamMac, int upstreamIfindex) throws Exception {
- if (!mDeps.isAtLeastS()) return;
- final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac, 0);
- final Tether6Value value = new Tether6Value(upstreamIfindex,
- MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
- ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
- verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value);
- }
-
- private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex,
- MacAddress downstreamMac)
- throws Exception {
- if (!mDeps.isAtLeastS()) return;
- final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac, 0);
- verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key);
- }
-
private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception {
if (!mDeps.isAtLeastS()) return;
if (inOrder != null) {
@@ -671,6 +660,13 @@
}
}
+ private void verifyAddUpstreamRule(@Nullable InOrder inOrder,
+ @NonNull Ipv6UpstreamRule rule) throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(
+ rule.makeTetherUpstream6Key(), rule.makeTether6Value());
+ }
+
private void verifyAddDownstreamRule(@Nullable InOrder inOrder,
@NonNull Ipv6DownstreamRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
@@ -681,6 +677,11 @@
}
}
+ private void verifyNeverAddUpstreamRule() throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ verify(mBpfUpstream6Map, never()).insertEntry(any(), any());
+ }
+
private void verifyNeverAddDownstreamRule() throws Exception {
if (mDeps.isAtLeastS()) {
verify(mBpfDownstream6Map, never()).updateEntry(any(), any());
@@ -689,6 +690,13 @@
}
}
+ private void verifyRemoveUpstreamRule(@Nullable InOrder inOrder,
+ @NonNull final Ipv6UpstreamRule rule) throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(
+ rule.makeTetherUpstream6Key());
+ }
+
private void verifyRemoveDownstreamRule(@Nullable InOrder inOrder,
@NonNull final Ipv6DownstreamRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
@@ -699,6 +707,11 @@
}
}
+ private void verifyNeverRemoveUpstreamRule() throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ verify(mBpfUpstream6Map, never()).deleteEntry(any());
+ }
+
private void verifyNeverRemoveDownstreamRule() throws Exception {
if (mDeps.isAtLeastS()) {
verify(mBpfDownstream6Map, never()).deleteEntry(any());
@@ -763,24 +776,31 @@
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// InOrder is required because mBpfStatsMap may be accessed by both
// BpfCoordinator#tetherOffloadRuleAdd and BpfCoordinator#tetherOffloadGetAndClearStats.
// The #verifyTetherOffloadGetAndClearStats can't distinguish who has ever called
// mBpfStatsMap#getValue and get a wrong calling count which counts all.
- final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
- final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.addIpv6DownstreamRule(mIpServer, rule);
- verifyAddDownstreamRule(inOrder, rule);
+ final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfDownstream6Map, mBpfLimitMap,
+ mBpfStatsMap);
+ final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule(
+ mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(
+ mobileIfIndex, NEIGH_A, MAC_A);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
+ verifyAddUpstreamRule(inOrder, upstreamRule);
+ coordinator.addIpv6DownstreamRule(mIpServer, downstreamRule);
+ verifyAddDownstreamRule(inOrder, downstreamRule);
- // Removing the last rule on current upstream immediately sends the cleanup stuff to netd.
+ // Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.removeIpv6DownstreamRule(mIpServer, rule);
- verifyRemoveDownstreamRule(inOrder, rule);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, 0);
+ verifyRemoveDownstreamRule(inOrder, downstreamRule);
+ verifyRemoveUpstreamRule(inOrder, upstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
}
@@ -806,7 +826,7 @@
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
updateStatsEntriesAndWaitForUpdate(new TetherStatsParcel[] {
buildTestTetherStatsParcel(mobileIfIndex, 1000, 100, 2000, 200)});
@@ -847,8 +867,8 @@
// Add interface name to lookup table. In realistic case, the upstream interface name will
// be added by IpServer when IpServer has received with a new IPv6 upstream update event.
- coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface);
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(wlanIfIndex, wlanIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// [1] Both interface stats are changed.
// Setup the tether stats of wlan and mobile interface. Note that move forward the time of
@@ -912,7 +932,7 @@
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// Verify that set quota to 0 will immediately triggers a callback.
mTetherStatsProvider.onSetAlert(0);
@@ -978,9 +998,10 @@
}
@NonNull
- private static Ipv6UpstreamRule buildTestUpstreamRule(int upstreamIfindex) {
- return new Ipv6UpstreamRule(upstreamIfindex, DOWNSTREAM_IFINDEX,
- IPV6_ZERO_PREFIX, DOWNSTREAM_MAC, MacAddress.ALL_ZEROS_ADDRESS,
+ private static Ipv6UpstreamRule buildTestUpstreamRule(
+ int upstreamIfindex, int downstreamIfindex, @NonNull MacAddress inDstMac) {
+ return new Ipv6UpstreamRule(upstreamIfindex, downstreamIfindex,
+ IPV6_ZERO_PREFIX, inDstMac, MacAddress.ALL_ZEROS_ADDRESS,
MacAddress.ALL_ZEROS_ADDRESS);
}
@@ -1027,17 +1048,18 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// [1] Default limit.
// Set the unlimited quota as default if the service has never applied a data limit for a
// given upstream. Note that the data limit only be applied on an upstream which has rules.
- final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
- final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
- coordinator.addIpv6DownstreamRule(mIpServer, rule);
- verifyAddDownstreamRule(inOrder, rule);
+ final Ipv6UpstreamRule rule = buildTestUpstreamRule(
+ mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
+ verifyAddUpstreamRule(inOrder, rule);
inOrder.verifyNoMoreInteractions();
// [2] Specific limit.
@@ -1062,7 +1084,6 @@
}
}
- // TODO: Test the case in which the rules are changed from different IpServer objects.
@Test
public void testSetDataLimitOnRule6Change() throws Exception {
setupFunctioningNetdInterface();
@@ -1071,39 +1092,41 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
final long limit = 12345;
- final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
+ final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap);
mTetherStatsProvider.onSetLimit(mobileIface, limit);
waitForIdle();
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
- // Adding the first rule on current upstream immediately sends the quota to netd.
- final Ipv6DownstreamRule ruleA = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.addIpv6DownstreamRule(mIpServer, ruleA);
- verifyAddDownstreamRule(inOrder, ruleA);
+ // Adding the first rule on current upstream immediately sends the quota to BPF.
+ final Ipv6UpstreamRule ruleA = buildTestUpstreamRule(
+ mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */);
+ verifyAddUpstreamRule(inOrder, ruleA);
inOrder.verifyNoMoreInteractions();
- // Adding the second rule on current upstream does not send the quota to netd.
- final Ipv6DownstreamRule ruleB = buildTestDownstreamRule(mobileIfIndex, NEIGH_B, MAC_B);
- coordinator.addIpv6DownstreamRule(mIpServer, ruleB);
- verifyAddDownstreamRule(inOrder, ruleB);
+ // Adding the second rule on current upstream does not send the quota to BPF.
+ final Ipv6UpstreamRule ruleB = buildTestUpstreamRule(
+ mobileIfIndex, DOWNSTREAM_IFINDEX2, DOWNSTREAM_MAC2);
+ coordinator.updateAllIpv6Rules(mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex);
+ verifyAddUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
- // Removing the second rule on current upstream does not send the quota to netd.
- coordinator.removeIpv6DownstreamRule(mIpServer, ruleB);
- verifyRemoveDownstreamRule(inOrder, ruleB);
+ // Removing the second rule on current upstream does not send the quota to BPF.
+ coordinator.updateAllIpv6Rules(mIpServer2, DOWNSTREAM_IFACE_PARAMS2, 0);
+ verifyRemoveUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
- // Removing the last rule on current upstream immediately sends the cleanup stuff to netd.
+ // Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.removeIpv6DownstreamRule(mIpServer, ruleA);
- verifyRemoveDownstreamRule(inOrder, ruleA);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, 0);
+ verifyRemoveUpstreamRule(inOrder, ruleA);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
inOrder.verifyNoMoreInteractions();
}
@@ -1118,8 +1141,8 @@
final String mobileIface = "rmnet_data0";
final Integer ethIfIndex = 100;
final Integer mobileIfIndex = 101;
- coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface);
- coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+ coordinator.maybeAddUpstreamToLookupTable(ethIfIndex, ethIface);
+ coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfLimitMap,
mBpfStatsMap);
@@ -1133,20 +1156,25 @@
// [1] Adding rules on the upstream Ethernet.
// Note that the default data limit is applied after the first rule is added.
+ final Ipv6UpstreamRule ethernetUpstreamRule = buildTestUpstreamRule(
+ ethIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule ethernetRuleA = buildTestDownstreamRule(
ethIfIndex, NEIGH_A, MAC_A);
final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule(
ethIfIndex, NEIGH_B, MAC_B);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA);
- verifyAddDownstreamRule(inOrder, ethernetRuleA);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
- verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, ethIfIndex);
+ verifyAddUpstreamRule(inOrder, ethernetUpstreamRule);
+ coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA);
+ verifyAddDownstreamRule(inOrder, ethernetRuleA);
coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleB);
verifyAddDownstreamRule(inOrder, ethernetRuleB);
// [2] Update the existing rules from Ethernet to cellular.
+ final Ipv6UpstreamRule mobileUpstreamRule = buildTestUpstreamRule(
+ mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule mobileRuleA = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
final Ipv6DownstreamRule mobileRuleB = buildTestDownstreamRule(
@@ -1156,25 +1184,24 @@
// Update the existing rules for upstream changes. The rules are removed and re-added one
// by one for updating upstream interface index by #tetherOffloadRuleUpdate.
- coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
verifyRemoveDownstreamRule(inOrder, ethernetRuleA);
verifyRemoveDownstreamRule(inOrder, ethernetRuleB);
- verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ verifyRemoveUpstreamRule(inOrder, ethernetUpstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex);
- verifyAddDownstreamRule(inOrder, mobileRuleA);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
- verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC,
- mobileIfIndex);
+ verifyAddUpstreamRule(inOrder, mobileUpstreamRule);
+ verifyAddDownstreamRule(inOrder, mobileRuleA);
verifyAddDownstreamRule(inOrder, mobileRuleB);
// [3] Clear all rules for a given IpServer.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 50, 60, 70, 80));
- coordinator.tetherOffloadRuleClear(mIpServer);
+ coordinator.clearAllIpv6Rules(mIpServer);
verifyRemoveDownstreamRule(inOrder, mobileRuleA);
verifyRemoveDownstreamRule(inOrder, mobileRuleB);
- verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ verifyRemoveUpstreamRule(inOrder, mobileUpstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
// [4] Force pushing stats update to verify that the last diff of stats is reported on all
@@ -1204,7 +1231,7 @@
// The interface name lookup table can't be added.
final String iface = "rmnet_data0";
final Integer ifIndex = 100;
- coordinator.addUpstreamNameToLookupTable(ifIndex, iface);
+ coordinator.maybeAddUpstreamToLookupTable(ifIndex, iface);
assertEquals(0, coordinator.getInterfaceNamesForTesting().size());
// The rule can't be added.
@@ -1230,14 +1257,15 @@
assertEquals(1, rules.size());
// The rule can't be cleared.
- coordinator.tetherOffloadRuleClear(mIpServer);
+ coordinator.clearAllIpv6Rules(mIpServer);
verifyNeverRemoveDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
assertEquals(1, rules.size());
// The rule can't be updated.
- coordinator.tetherOffloadRuleUpdate(mIpServer, rule.upstreamIfindex + 1 /* new */);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, rule.upstreamIfindex + 1 /* new */);
verifyNeverRemoveDownstreamRule();
verifyNeverAddDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1552,7 +1580,7 @@
// interface index.
doReturn(upstreamInfo.interfaceParams).when(mDeps).getInterfaceParams(
upstreamInfo.interfaceParams.name);
- coordinator.addUpstreamNameToLookupTable(upstreamInfo.interfaceParams.index,
+ coordinator.maybeAddUpstreamToLookupTable(upstreamInfo.interfaceParams.index,
upstreamInfo.interfaceParams.name);
final LinkProperties lp = new LinkProperties();
@@ -1677,19 +1705,21 @@
public void testAddDevMapRule6() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- final Ipv6DownstreamRule ruleA = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
- final Ipv6DownstreamRule ruleB = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B);
-
- coordinator.addIpv6DownstreamRule(mIpServer, ruleA);
+ coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX)));
clearInvocations(mBpfDevMap);
- coordinator.addIpv6DownstreamRule(mIpServer, ruleB);
- verify(mBpfDevMap, never()).updateEntry(any(), any());
+ // Adding the second downstream, only the second downstream ifindex is added to DevMap,
+ // the existing upstream ifindex won't be added again.
+ coordinator.updateAllIpv6Rules(mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX);
+ verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX2)),
+ eq(new TetherDevValue(DOWNSTREAM_IFINDEX2)));
+ verify(mBpfDevMap, never()).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
+ eq(new TetherDevValue(UPSTREAM_IFINDEX)));
}
@Test
@@ -2153,7 +2183,8 @@
assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8::1, "
+ "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a",
downstreamRule.toString());
- final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule(UPSTREAM_IFINDEX);
+ final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule(
+ UPSTREAM_IFINDEX, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, sourcePrefix: ::/64, "
+ "inDstMac: 12:34:56:78:90:ab, outSrcMac: 00:00:00:00:00:00, "
+ "outDstMac: 00:00:00:00:00:00", upstreamRule.toString());
@@ -2221,7 +2252,7 @@
0L /* txPackets */, 0L /* txBytes */, 0L /* txErrors */));
// dumpDevmap
- coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
+ coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
mBpfDevMap.insertEntry(
new TetherDevKey(UPSTREAM_IFINDEX),
new TetherDevValue(UPSTREAM_IFINDEX));
@@ -2390,7 +2421,7 @@
// +-------+-------+-------+-------+-------+
// [1] Mobile IPv4 only
- coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
+ coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
doReturn(UPSTREAM_IFACE_PARAMS).when(mDeps).getInterfaceParams(UPSTREAM_IFACE);
final UpstreamNetworkState mobileIPv4UpstreamState = new UpstreamNetworkState(
buildUpstreamLinkProperties(UPSTREAM_IFACE,
@@ -2442,7 +2473,7 @@
verifyIpv4Upstream(ipv4UpstreamIndices, interfaceNames);
// Mobile IPv6 and xlat
- // IpServer doesn't add xlat interface mapping via #addUpstreamNameToLookupTable on
+ // IpServer doesn't add xlat interface mapping via #maybeAddUpstreamToLookupTable on
// S and T devices.
coordinator.updateUpstreamNetworkState(mobile464xlatUpstreamState);
// Upstream IPv4 address mapping is removed because xlat interface is not supported.
@@ -2457,7 +2488,7 @@
// [6] Wifi IPv4 and IPv6
// Expect that upstream index map is cleared because ether ip is not supported.
- coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2);
+ coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2);
doReturn(UPSTREAM_IFACE_PARAMS2).when(mDeps).getInterfaceParams(UPSTREAM_IFACE2);
final UpstreamNetworkState wifiDualStackUpstreamState = new UpstreamNetworkState(
buildUpstreamLinkProperties(UPSTREAM_IFACE2,
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index ed33cc9..bb5e330 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -87,6 +87,14 @@
if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
}
+// constants for passing in to 'bool shared' (for maps)
+static const bool PRIVATE = false;
+static const bool SHARED = true;
+
+// constants for passing in to 'bool optional' (for programs)
+static const bool MANDATORY = false;
+static const bool OPTIONAL = true;
+
// constants for passing in to 'bool egress'
static const bool INGRESS = false;
static const bool EGRESS = true;
@@ -104,12 +112,11 @@
static const bool UPDATETIME = true;
// constants for passing in to ignore_on_eng / ignore_on_user / ignore_on_userdebug
-// define's instead of static const due to tm-mainline-prod compiler static_assert limitations
-#define LOAD_ON_ENG false
-#define LOAD_ON_USER false
-#define LOAD_ON_USERDEBUG false
-#define IGNORE_ON_ENG true
-#define IGNORE_ON_USER true
-#define IGNORE_ON_USERDEBUG true
+static const bool LOAD_ON_ENG = false;
+static const bool LOAD_ON_USER = false;
+static const bool LOAD_ON_USERDEBUG = false;
+static const bool IGNORE_ON_ENG = true;
+static const bool IGNORE_ON_USER = true;
+static const bool IGNORE_ON_USERDEBUG = true;
#define KVER_4_14 KVER(4, 14, 0)
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 256dd6a..7a48e8c 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -59,18 +59,18 @@
#define TCP_FLAG8_OFF (TCP_FLAG32_OFF + 1)
// For maps netd does not need to access
-#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
- DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \
- AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", false, \
- BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG, \
- LOAD_ON_USER, LOAD_ON_USERDEBUG)
+#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
+ DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \
+ AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", \
+ PRIVATE, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, \
+ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
// For maps netd only needs read only access to
-#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
- DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \
- AID_ROOT, AID_NET_BW_ACCT, 0460, "fs_bpf_netd_readonly", "", false, \
- BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG, \
- LOAD_ON_USER, LOAD_ON_USERDEBUG)
+#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
+ DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \
+ AID_ROOT, AID_NET_BW_ACCT, 0460, "fs_bpf_netd_readonly", "", \
+ PRIVATE, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, \
+ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
// For maps netd needs to be able to read and write
#define DEFINE_BPF_MAP_RW_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
@@ -102,13 +102,13 @@
// A single-element configuration array, packet tracing is enabled when 'true'.
DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
- AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+ AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG)
// A ring buffer on which packet information is pushed.
DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
- AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+ AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
@@ -127,8 +127,8 @@
// which is loaded into netd and thus runs as netd uid/gid/selinux context)
#define DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, maxKV) \
DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
- minKV, maxKV, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, \
- "fs_bpf_netd_readonly", "", false, false, false)
+ minKV, maxKV, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \
+ "fs_bpf_netd_readonly", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
#define DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \
DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF)
@@ -139,8 +139,8 @@
// programs that only need to be usable by the system server
#define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
- BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, "fs_bpf_net_shared", \
- "", false, false, false)
+ BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \
+ "fs_bpf_net_shared", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
static __always_inline int is_system_uid(uint32_t uid) {
// MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
@@ -506,8 +506,9 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace_user, KVER(5, 8, 0), KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, true,
- "fs_bpf_netd_readonly", "", true, false, true)
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ "fs_bpf_netd_readonly", "",
+ IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
}
@@ -515,8 +516,9 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace, KVER(5, 8, 0), KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
- "fs_bpf_netd_readonly", "", false, true, false)
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ "fs_bpf_netd_readonly", "",
+ LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
}
@@ -536,8 +538,9 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace_user, KVER(5, 8, 0), KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, true,
- "fs_bpf_netd_readonly", "", true, false, true)
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ "fs_bpf_netd_readonly", "",
+ LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
}
@@ -545,8 +548,9 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace, KVER(5, 8, 0), KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
- "fs_bpf_netd_readonly", "", false, true, false)
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ "fs_bpf_netd_readonly", "",
+ LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
new file mode 100644
index 0000000..8e47ea8
--- /dev/null
+++ b/netbpfload/NetBpfLoad.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017-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.
+ */
+
+#ifndef LOG_TAG
+#define LOG_TAG "NetBpfLoad"
+#endif
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <elf.h>
+#include <error.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/bpf.h>
+#include <linux/unistd.h>
+#include <net/if.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <libbpf_android.h>
+#include <log/log.h>
+#include <netdutils/Misc.h>
+#include <netdutils/Slice.h>
+#include "BpfSyscallWrappers.h"
+#include "bpf/BpfUtils.h"
+
+using android::base::EndsWith;
+using android::bpf::domain;
+using std::string;
+
+bool exists(const char* const path) {
+ int v = access(path, F_OK);
+ if (!v) {
+ ALOGI("%s exists.", path);
+ return true;
+ }
+ if (errno == ENOENT) return false;
+ ALOGE("FATAL: access(%s, F_OK) -> %d [%d:%s]", path, v, errno, strerror(errno));
+ abort(); // can only hit this if permissions (likely selinux) are screwed up
+}
+
+constexpr unsigned long long kTetheringApexDomainBitmask =
+ domainToBitmask(domain::tethering) |
+ domainToBitmask(domain::net_private) |
+ domainToBitmask(domain::net_shared) |
+ domainToBitmask(domain::netd_readonly) |
+ domainToBitmask(domain::netd_shared);
+
+// Programs shipped inside the tethering apex should be limited to networking stuff,
+// as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
+// since they are less stable abi/api and may conflict with platform uses of bpf.
+constexpr bpf_prog_type kTetheringApexAllowedProgTypes[] = {
+ BPF_PROG_TYPE_CGROUP_SKB,
+ BPF_PROG_TYPE_CGROUP_SOCK,
+ BPF_PROG_TYPE_CGROUP_SOCKOPT,
+ BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
+ BPF_PROG_TYPE_CGROUP_SYSCTL,
+ BPF_PROG_TYPE_LWT_IN,
+ BPF_PROG_TYPE_LWT_OUT,
+ BPF_PROG_TYPE_LWT_SEG6LOCAL,
+ BPF_PROG_TYPE_LWT_XMIT,
+ BPF_PROG_TYPE_SCHED_ACT,
+ BPF_PROG_TYPE_SCHED_CLS,
+ BPF_PROG_TYPE_SOCKET_FILTER,
+ BPF_PROG_TYPE_SOCK_OPS,
+ BPF_PROG_TYPE_XDP,
+};
+
+
+const android::bpf::Location locations[] = {
+ // S+ Tethering mainline module (network_stack): tether offload
+ {
+ .dir = "/apex/com.android.tethering/etc/bpf/",
+ .prefix = "tethering/",
+ .allowedDomainBitmask = kTetheringApexDomainBitmask,
+ .allowedProgTypes = kTetheringApexAllowedProgTypes,
+ .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
+ },
+ // T+ Tethering mainline module (shared with netd & system server)
+ // netutils_wrapper (for iptables xt_bpf) has access to programs
+ {
+ .dir = "/apex/com.android.tethering/etc/bpf/netd_shared/",
+ .prefix = "netd_shared/",
+ .allowedDomainBitmask = kTetheringApexDomainBitmask,
+ .allowedProgTypes = kTetheringApexAllowedProgTypes,
+ .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
+ },
+ // T+ Tethering mainline module (shared with netd & system server)
+ // netutils_wrapper has no access, netd has read only access
+ {
+ .dir = "/apex/com.android.tethering/etc/bpf/netd_readonly/",
+ .prefix = "netd_readonly/",
+ .allowedDomainBitmask = kTetheringApexDomainBitmask,
+ .allowedProgTypes = kTetheringApexAllowedProgTypes,
+ .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
+ },
+ // T+ Tethering mainline module (shared with system server)
+ {
+ .dir = "/apex/com.android.tethering/etc/bpf/net_shared/",
+ .prefix = "net_shared/",
+ .allowedDomainBitmask = kTetheringApexDomainBitmask,
+ .allowedProgTypes = kTetheringApexAllowedProgTypes,
+ .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
+ },
+ // T+ Tethering mainline module (not shared, just network_stack)
+ {
+ .dir = "/apex/com.android.tethering/etc/bpf/net_private/",
+ .prefix = "net_private/",
+ .allowedDomainBitmask = kTetheringApexDomainBitmask,
+ .allowedProgTypes = kTetheringApexAllowedProgTypes,
+ .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
+ },
+};
+
+int loadAllElfObjects(const android::bpf::Location& location) {
+ int retVal = 0;
+ DIR* dir;
+ struct dirent* ent;
+
+ if ((dir = opendir(location.dir)) != NULL) {
+ while ((ent = readdir(dir)) != NULL) {
+ string s = ent->d_name;
+ if (!EndsWith(s, ".o")) continue;
+
+ string progPath(location.dir);
+ progPath += s;
+
+ bool critical;
+ int ret = android::bpf::loadProg(progPath.c_str(), &critical, location);
+ if (ret) {
+ if (critical) retVal = ret;
+ ALOGE("Failed to load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret));
+ } else {
+ ALOGI("Loaded object: %s", progPath.c_str());
+ }
+ }
+ closedir(dir);
+ }
+ return retVal;
+}
+
+int createSysFsBpfSubDir(const char* const prefix) {
+ if (*prefix) {
+ mode_t prevUmask = umask(0);
+
+ string s = "/sys/fs/bpf/";
+ s += prefix;
+
+ errno = 0;
+ int ret = mkdir(s.c_str(), S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
+ if (ret && errno != EEXIST) {
+ const int err = errno;
+ ALOGE("Failed to create directory: %s, ret: %s", s.c_str(), std::strerror(err));
+ return -err;
+ }
+
+ umask(prevUmask);
+ }
+ return 0;
+}
+
+// Technically 'value' doesn't need to be newline terminated, but it's best
+// to include a newline to match 'echo "value" > /proc/sys/...foo' behaviour,
+// which is usually how kernel devs test the actual sysctl interfaces.
+int writeProcSysFile(const char *filename, const char *value) {
+ android::base::unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC));
+ if (fd < 0) {
+ const int err = errno;
+ ALOGE("open('%s', O_WRONLY | O_CLOEXEC) -> %s", filename, strerror(err));
+ return -err;
+ }
+ int len = strlen(value);
+ int v = write(fd, value, len);
+ if (v < 0) {
+ const int err = errno;
+ ALOGE("write('%s', '%s', %d) -> %s", filename, value, len, strerror(err));
+ return -err;
+ }
+ if (v != len) {
+ // In practice, due to us only using this for /proc/sys/... files, this can't happen.
+ ALOGE("write('%s', '%s', %d) -> short write [%d]", filename, value, len, v);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ (void)argc;
+ android::base::InitLogging(argv, &android::base::KernelLogger);
+
+ if (!android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ ALOGE("Android U QPR2 requires kernel 4.19.");
+ return 1;
+ }
+
+ if (android::bpf::isUserspace32bit() && android::bpf::isAtLeastKernelVersion(6, 2, 0)) {
+ /* Android 14/U should only launch on 64-bit kernels
+ * T launches on 5.10/5.15
+ * U launches on 5.15/6.1
+ * So >=5.16 implies isKernel64Bit()
+ *
+ * We thus added a test to V VTS which requires 5.16+ devices to use 64-bit kernels.
+ *
+ * Starting with Android V, which is the first to support a post 6.1 Linux Kernel,
+ * we also require 64-bit userspace.
+ *
+ * There are various known issues with 32-bit userspace talking to various
+ * kernel interfaces (especially CAP_NET_ADMIN ones) on a 64-bit kernel.
+ * Some of these have userspace or kernel workarounds/hacks.
+ * Some of them don't...
+ * We're going to be removing the hacks.
+ *
+ * Additionally the 32-bit kernel jit support is poor,
+ * and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
+ */
+ ALOGE("64-bit userspace required on 6.2+ kernels.");
+ return 1;
+ }
+
+ // Ensure we can determine the Android build type.
+ if (!android::bpf::isEng() && !android::bpf::isUser() && !android::bpf::isUserdebug()) {
+ ALOGE("Failed to determine the build type: got %s, want 'eng', 'user', or 'userdebug'",
+ android::bpf::getBuildType().c_str());
+ return 1;
+ }
+
+ // Linux 5.16-rc1 changed the default to 2 (disabled but changeable), but we need 0 (enabled)
+ // (this writeFile is known to fail on at least 4.19, but always defaults to 0 on pre-5.13,
+ // on 5.13+ it depends on CONFIG_BPF_UNPRIV_DEFAULT_OFF)
+ if (writeProcSysFile("/proc/sys/kernel/unprivileged_bpf_disabled", "0\n") &&
+ android::bpf::isAtLeastKernelVersion(5, 13, 0)) return 1;
+
+ // Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
+ // already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
+ // (Note: this (open) will fail with ENOENT 'No such file or directory' if
+ // kernel does not have CONFIG_BPF_JIT=y)
+ // BPF_JIT is required by R VINTF (which means 4.14/4.19/5.4 kernels),
+ // but 4.14/4.19 were released with P & Q, and only 5.4 is new in R+.
+ if (writeProcSysFile("/proc/sys/net/core/bpf_jit_enable", "1\n")) return 1;
+
+ // Enable JIT kallsyms export for privileged users only
+ // (Note: this (open) will fail with ENOENT 'No such file or directory' if
+ // kernel does not have CONFIG_HAVE_EBPF_JIT=y)
+ if (writeProcSysFile("/proc/sys/net/core/bpf_jit_kallsyms", "1\n")) return 1;
+
+ // Create all the pin subdirectories
+ // (this must be done first to allow selinux_context and pin_subdir functionality,
+ // which could otherwise fail with ENOENT during object pinning or renaming,
+ // due to ordering issues)
+ for (const auto& location : locations) {
+ if (createSysFsBpfSubDir(location.prefix)) return 1;
+ }
+
+ // Note: there's no actual src dir for fs_bpf_loader .o's,
+ // so it is not listed in 'locations[].prefix'.
+ // This is because this is primarily meant for triggering genfscon rules,
+ // and as such this will likely always be the case.
+ // Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
+ if (createSysFsBpfSubDir("loader")) return 1;
+
+ // Load all ELF objects, create programs and maps, and pin them
+ for (const auto& location : locations) {
+ if (loadAllElfObjects(location) != 0) {
+ ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS FROM %s ===", location.dir);
+ ALOGE("If this triggers reliably, you're probably missing kernel options or patches.");
+ ALOGE("If this triggers randomly, you might be hitting some memory allocation "
+ "problems or startup script race.");
+ ALOGE("--- DO NOT EXPECT SYSTEM TO BOOT SUCCESSFULLY ---");
+ sleep(20);
+ return 2;
+ }
+ }
+
+ int key = 1;
+ int value = 123;
+ android::base::unique_fd map(
+ android::bpf::createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0));
+ if (android::bpf::writeToMapEntry(map, &key, &value, BPF_ANY)) {
+ ALOGE("Critical kernel bug - failure to write into index 1 of 2 element bpf map array.");
+ return 1;
+ }
+
+ if (android::base::SetProperty("bpf.progs_loaded", "1") == false) {
+ ALOGE("Failed to set bpf.progs_loaded property");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
new file mode 100644
index 0000000..3bb758b
--- /dev/null
+++ b/netbpfload/loader.cpp
@@ -0,0 +1,1243 @@
+/*
+ * Copyright (C) 2018-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.
+ */
+
+#define LOG_TAG "NetBpfLoader"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/bpf.h>
+#include <linux/elf.h>
+#include <log/log.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+// This is BpfLoader v0.41
+// WARNING: If you ever hit cherrypick conflicts here you're doing it wrong:
+// You are NOT allowed to cherrypick bpfloader related patches out of order.
+// (indeed: cherrypicking is probably a bad idea and you should merge instead)
+// Mainline supports ONLY the published versions of the bpfloader for each Android release.
+#define BPFLOADER_VERSION_MAJOR 0u
+#define BPFLOADER_VERSION_MINOR 41u
+#define BPFLOADER_VERSION ((BPFLOADER_VERSION_MAJOR << 16) | BPFLOADER_VERSION_MINOR)
+
+#include "BpfSyscallWrappers.h"
+#include "bpf/BpfUtils.h"
+#include "bpf/bpf_map_def.h"
+#include "include/libbpf_android.h"
+
+#if BPFLOADER_VERSION < COMPILE_FOR_BPFLOADER_VERSION
+#error "BPFLOADER_VERSION is less than COMPILE_FOR_BPFLOADER_VERSION"
+#endif
+
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/cmsg.h>
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <cutils/properties.h>
+
+#define BPF_FS_PATH "/sys/fs/bpf/"
+
+// Size of the BPF log buffer for verifier logging
+#define BPF_LOAD_LOG_SZ 0xfffff
+
+// Unspecified attach type is 0 which is BPF_CGROUP_INET_INGRESS.
+#define BPF_ATTACH_TYPE_UNSPEC BPF_CGROUP_INET_INGRESS
+
+using android::base::StartsWith;
+using android::base::unique_fd;
+using std::ifstream;
+using std::ios;
+using std::optional;
+using std::string;
+using std::vector;
+
+static std::string getBuildTypeInternal() {
+ char value[PROPERTY_VALUE_MAX] = {};
+ (void)property_get("ro.build.type", value, "unknown"); // ignore length
+ return value;
+}
+
+namespace android {
+namespace bpf {
+
+const std::string& getBuildType() {
+ static std::string t = getBuildTypeInternal();
+ return t;
+}
+
+static unsigned int page_size = static_cast<unsigned int>(getpagesize());
+
+constexpr const char* lookupSelinuxContext(const domain d, const char* const unspecified = "") {
+ switch (d) {
+ case domain::unspecified: return unspecified;
+ case domain::tethering: return "fs_bpf_tethering";
+ case domain::net_private: return "fs_bpf_net_private";
+ case domain::net_shared: return "fs_bpf_net_shared";
+ case domain::netd_readonly: return "fs_bpf_netd_readonly";
+ case domain::netd_shared: return "fs_bpf_netd_shared";
+ default: return "(unrecognized)";
+ }
+}
+
+domain getDomainFromSelinuxContext(const char s[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]) {
+ for (domain d : AllDomains) {
+ // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
+ if (strlen(lookupSelinuxContext(d)) >= BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE) abort();
+ if (!strncmp(s, lookupSelinuxContext(d), BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE)) return d;
+ }
+ ALOGW("ignoring unrecognized selinux_context '%-32s'", s);
+ // We should return 'unrecognized' here, however: returning unspecified will
+ // result in the system simply using the default context, which in turn
+ // will allow future expansion by adding more restrictive selinux types.
+ // Older bpfloader will simply ignore that, and use the less restrictive default.
+ // This does mean you CANNOT later add a *less* restrictive type than the default.
+ //
+ // Note: we cannot just abort() here as this might be a mainline module shipped optional update
+ return domain::unspecified;
+}
+
+constexpr const char* lookupPinSubdir(const domain d, const char* const unspecified = "") {
+ switch (d) {
+ case domain::unspecified: return unspecified;
+ case domain::tethering: return "tethering/";
+ case domain::net_private: return "net_private/";
+ case domain::net_shared: return "net_shared/";
+ case domain::netd_readonly: return "netd_readonly/";
+ case domain::netd_shared: return "netd_shared/";
+ default: return "(unrecognized)";
+ }
+};
+
+domain getDomainFromPinSubdir(const char s[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]) {
+ for (domain d : AllDomains) {
+ // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
+ if (strlen(lookupPinSubdir(d)) >= BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE) abort();
+ if (!strncmp(s, lookupPinSubdir(d), BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE)) return d;
+ }
+ ALOGE("unrecognized pin_subdir '%-32s'", s);
+ // pin_subdir affects the object's full pathname,
+ // and thus using the default would change the location and thus our code's ability to find it,
+ // hence this seems worth treating as a true error condition.
+ //
+ // Note: we cannot just abort() here as this might be a mainline module shipped optional update
+ // However, our callers will treat this as an error, and stop loading the specific .o,
+ // which will fail bpfloader if the .o is marked critical.
+ return domain::unrecognized;
+}
+
+static string pathToObjName(const string& path) {
+ // extract everything after the final slash, ie. this is the filename 'foo@1.o' or 'bar.o'
+ string filename = android::base::Split(path, "/").back();
+ // strip off everything from the final period onwards (strip '.o' suffix), ie. 'foo@1' or 'bar'
+ string name = filename.substr(0, filename.find_last_of('.'));
+ // strip any potential @1 suffix, this will leave us with just 'foo' or 'bar'
+ // this can be used to provide duplicate programs (mux based on the bpfloader version)
+ return name.substr(0, name.find_last_of('@'));
+}
+
+typedef struct {
+ const char* name;
+ enum bpf_prog_type type;
+ enum bpf_attach_type expected_attach_type;
+} sectionType;
+
+/*
+ * Map section name prefixes to program types, the section name will be:
+ * SECTION(<prefix>/<name-of-program>)
+ * For example:
+ * SECTION("tracepoint/sched_switch_func") where sched_switch_funcs
+ * is the name of the program, and tracepoint is the type.
+ *
+ * However, be aware that you should not be directly using the SECTION() macro.
+ * Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE/CRITICAL macros.
+ */
+sectionType sectionNameTypes[] = {
+ {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
+ {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
+ {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
+ {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
+ {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
+ {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
+ {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
+ {"kprobe/", BPF_PROG_TYPE_KPROBE, BPF_ATTACH_TYPE_UNSPEC},
+ {"kretprobe/", BPF_PROG_TYPE_KPROBE, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
+ {"perf_event/", BPF_PROG_TYPE_PERF_EVENT, BPF_ATTACH_TYPE_UNSPEC},
+ {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
+ {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
+ {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
+ {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
+ {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
+ {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
+ {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
+ {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
+ {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
+ {"tracepoint/", BPF_PROG_TYPE_TRACEPOINT, BPF_ATTACH_TYPE_UNSPEC},
+ {"uprobe/", BPF_PROG_TYPE_KPROBE, BPF_ATTACH_TYPE_UNSPEC},
+ {"uretprobe/", BPF_PROG_TYPE_KPROBE, BPF_ATTACH_TYPE_UNSPEC},
+ {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
+};
+
+typedef struct {
+ enum bpf_prog_type type;
+ enum bpf_attach_type expected_attach_type;
+ string name;
+ vector<char> data;
+ vector<char> rel_data;
+ optional<struct bpf_prog_def> prog_def;
+
+ unique_fd prog_fd; /* fd after loading */
+} codeSection;
+
+static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
+ elfFile.seekg(0);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
+
+ return 0;
+}
+
+/* Reads all section header tables into an Shdr array */
+static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
+ Elf64_Ehdr eh;
+ int ret = 0;
+
+ ret = readElfHeader(elfFile, &eh);
+ if (ret) return ret;
+
+ elfFile.seekg(eh.e_shoff);
+ if (elfFile.fail()) return -1;
+
+ /* Read shdr table entries */
+ shTable.resize(eh.e_shnum);
+
+ if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
+
+ return 0;
+}
+
+/* Read a section by its index - for ex to get sec hdr strtab blob */
+static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
+ vector<Elf64_Shdr> shTable;
+ int ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ elfFile.seekg(shTable[id].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ sec.resize(shTable[id].sh_size);
+ if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
+
+ return 0;
+}
+
+/* Read whole section header string table */
+static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
+ Elf64_Ehdr eh;
+ int ret = readElfHeader(elfFile, &eh);
+ if (ret) return ret;
+
+ ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
+ if (ret) return ret;
+
+ return 0;
+}
+
+/* Get name from offset in strtab */
+static int getSymName(ifstream& elfFile, int nameOff, string& name) {
+ int ret;
+ vector<char> secStrTab;
+
+ ret = readSectionHeaderStrtab(elfFile, secStrTab);
+ if (ret) return ret;
+
+ if (nameOff >= (int)secStrTab.size()) return -1;
+
+ name = string((char*)secStrTab.data() + nameOff);
+ return 0;
+}
+
+/* Reads a full section by name - example to get the GPL license */
+static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& data) {
+ vector<char> secStrTab;
+ vector<Elf64_Shdr> shTable;
+ int ret;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ ret = readSectionHeaderStrtab(elfFile, secStrTab);
+ if (ret) return ret;
+
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ char* secname = secStrTab.data() + shTable[i].sh_name;
+ if (!secname) continue;
+
+ if (!strcmp(secname, name)) {
+ vector<char> dataTmp;
+ dataTmp.resize(shTable[i].sh_size);
+
+ elfFile.seekg(shTable[i].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
+
+ data = dataTmp;
+ return 0;
+ }
+ }
+ return -2;
+}
+
+unsigned int readSectionUint(const char* name, ifstream& elfFile, unsigned int defVal) {
+ vector<char> theBytes;
+ int ret = readSectionByName(name, elfFile, theBytes);
+ if (ret) {
+ ALOGD("Couldn't find section %s (defaulting to %u [0x%x]).", name, defVal, defVal);
+ return defVal;
+ } else if (theBytes.size() < sizeof(unsigned int)) {
+ ALOGE("Section %s too short (defaulting to %u [0x%x]).", name, defVal, defVal);
+ return defVal;
+ } else {
+ // decode first 4 bytes as LE32 uint, there will likely be more bytes due to alignment.
+ unsigned int value = static_cast<unsigned char>(theBytes[3]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[2]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[1]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[0]);
+ ALOGI("Section %s value is %u [0x%x]", name, value, value);
+ return value;
+ }
+}
+
+static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
+ int ret;
+ vector<Elf64_Shdr> shTable;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ if ((int)shTable[i].sh_type != type) continue;
+
+ vector<char> dataTmp;
+ dataTmp.resize(shTable[i].sh_size);
+
+ elfFile.seekg(shTable[i].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
+
+ data = dataTmp;
+ return 0;
+ }
+ return -2;
+}
+
+static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
+ return (a.st_value < b.st_value);
+}
+
+static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
+ int ret, numElems;
+ Elf64_Sym* buf;
+ vector<char> secData;
+
+ ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
+ if (ret) return ret;
+
+ buf = (Elf64_Sym*)secData.data();
+ numElems = (secData.size() / sizeof(Elf64_Sym));
+ data.assign(buf, buf + numElems);
+
+ if (sort) std::sort(data.begin(), data.end(), symCompare);
+ return 0;
+}
+
+static enum bpf_prog_type getFuseProgType() {
+ int result = BPF_PROG_TYPE_UNSPEC;
+ ifstream("/sys/fs/fuse/bpf_prog_type_fuse") >> result;
+ return static_cast<bpf_prog_type>(result);
+}
+
+static enum bpf_prog_type getSectionType(string& name) {
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) return snt.type;
+
+ // TODO Remove this code when fuse-bpf is upstream and this BPF_PROG_TYPE_FUSE is fixed
+ if (StartsWith(name, "fuse/")) return getFuseProgType();
+
+ return BPF_PROG_TYPE_UNSPEC;
+}
+
+static enum bpf_attach_type getExpectedAttachType(string& name) {
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) return snt.expected_attach_type;
+ return BPF_ATTACH_TYPE_UNSPEC;
+}
+
+static string getSectionName(enum bpf_prog_type type)
+{
+ for (auto& snt : sectionNameTypes)
+ if (snt.type == type)
+ return string(snt.name);
+
+ return "UNKNOWN SECTION NAME " + std::to_string(type);
+}
+
+static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd,
+ size_t sizeOfBpfProgDef) {
+ vector<char> pdData;
+ int ret = readSectionByName("progs", elfFile, pdData);
+ // Older file formats do not require a 'progs' section at all.
+ // (We should probably figure out whether this is behaviour which is safe to remove now.)
+ if (ret == -2) return 0;
+ if (ret) return ret;
+
+ if (pdData.size() % sizeOfBpfProgDef) {
+ ALOGE("readProgDefs failed due to improper sized progs section, %zu %% %zu != 0",
+ pdData.size(), sizeOfBpfProgDef);
+ return -1;
+ };
+
+ int progCount = pdData.size() / sizeOfBpfProgDef;
+ pd.resize(progCount);
+ size_t trimmedSize = std::min(sizeOfBpfProgDef, sizeof(struct bpf_prog_def));
+
+ const char* dataPtr = pdData.data();
+ for (auto& p : pd) {
+ // First we zero initialize
+ memset(&p, 0, sizeof(p));
+ // Then we set non-zero defaults
+ p.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
+ // Then we copy over the structure prefix from the ELF file.
+ memcpy(&p, dataPtr, trimmedSize);
+ // Move to next struct in the ELF file
+ dataPtr += sizeOfBpfProgDef;
+ }
+ return 0;
+}
+
+static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
+ optional<unsigned> symbolType = std::nullopt) {
+ int ret;
+ string name;
+ vector<Elf64_Sym> symtab;
+ vector<Elf64_Shdr> shTable;
+
+ ret = readSymTab(elfFile, 1 /* sort */, symtab);
+ if (ret) return ret;
+
+ /* Get index of section */
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ int sec_idx = -1;
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ ret = getSymName(elfFile, shTable[i].sh_name, name);
+ if (ret) return ret;
+
+ if (!name.compare(sectionName)) {
+ sec_idx = i;
+ break;
+ }
+ }
+
+ /* No section found with matching name*/
+ if (sec_idx == -1) {
+ ALOGW("No %s section could be found in elf object", sectionName.c_str());
+ return -1;
+ }
+
+ for (int i = 0; i < (int)symtab.size(); i++) {
+ if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
+
+ if (symtab[i].st_shndx == sec_idx) {
+ string s;
+ ret = getSymName(elfFile, symtab[i].st_name, s);
+ if (ret) return ret;
+ names.push_back(s);
+ }
+ }
+
+ return 0;
+}
+
+static bool IsAllowed(bpf_prog_type type, const bpf_prog_type* allowed, size_t numAllowed) {
+ if (allowed == nullptr) return true;
+
+ for (size_t i = 0; i < numAllowed; i++) {
+ if (allowed[i] == BPF_PROG_TYPE_UNSPEC) {
+ if (type == getFuseProgType()) return true;
+ } else if (type == allowed[i])
+ return true;
+ }
+
+ return false;
+}
+
+/* Read a section by its index - for ex to get sec hdr strtab blob */
+static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef,
+ const bpf_prog_type* allowed, size_t numAllowed) {
+ vector<Elf64_Shdr> shTable;
+ int entries, ret = 0;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+ entries = shTable.size();
+
+ vector<struct bpf_prog_def> pd;
+ ret = readProgDefs(elfFile, pd, sizeOfBpfProgDef);
+ if (ret) return ret;
+ vector<string> progDefNames;
+ ret = getSectionSymNames(elfFile, "progs", progDefNames);
+ if (!pd.empty() && ret) return ret;
+
+ for (int i = 0; i < entries; i++) {
+ string name;
+ codeSection cs_temp;
+ cs_temp.type = BPF_PROG_TYPE_UNSPEC;
+
+ ret = getSymName(elfFile, shTable[i].sh_name, name);
+ if (ret) return ret;
+
+ enum bpf_prog_type ptype = getSectionType(name);
+
+ if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
+
+ if (!IsAllowed(ptype, allowed, numAllowed)) {
+ ALOGE("Program type %s not permitted here", getSectionName(ptype).c_str());
+ return -1;
+ }
+
+ // This must be done before '/' is replaced with '_'.
+ cs_temp.expected_attach_type = getExpectedAttachType(name);
+
+ string oldName = name;
+
+ // convert all slashes to underscores
+ std::replace(name.begin(), name.end(), '/', '_');
+
+ cs_temp.type = ptype;
+ cs_temp.name = name;
+
+ ret = readSectionByIdx(elfFile, i, cs_temp.data);
+ if (ret) return ret;
+ ALOGD("Loaded code section %d (%s)", i, name.c_str());
+
+ vector<string> csSymNames;
+ ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
+ if (ret || !csSymNames.size()) return ret;
+ for (size_t i = 0; i < progDefNames.size(); ++i) {
+ if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
+ cs_temp.prog_def = pd[i];
+ break;
+ }
+ }
+
+ /* Check for rel section */
+ if (cs_temp.data.size() > 0 && i < entries) {
+ ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
+ if (ret) return ret;
+
+ if (name == (".rel" + oldName)) {
+ ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
+ if (ret) return ret;
+ ALOGD("Loaded relo section %d (%s)", i, name.c_str());
+ }
+ }
+
+ if (cs_temp.data.size() > 0) {
+ cs.push_back(std::move(cs_temp));
+ ALOGD("Adding section %d to cs list", i);
+ }
+ }
+ return 0;
+}
+
+static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
+ vector<Elf64_Sym> symtab;
+ int ret = 0;
+
+ ret = readSymTab(elfFile, 0 /* !sort */, symtab);
+ if (ret) return ret;
+
+ if (index >= (int)symtab.size()) return -1;
+
+ return getSymName(elfFile, symtab[index].st_name, name);
+}
+
+static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
+ const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
+ // Assuming fd is a valid Bpf Map file descriptor then
+ // all the following should always succeed on a 4.14+ kernel.
+ // If they somehow do fail, they'll return -1 (and set errno),
+ // which should then cause (among others) a key_size mismatch.
+ int fd_type = bpfGetFdMapType(fd);
+ int fd_key_size = bpfGetFdKeySize(fd);
+ int fd_value_size = bpfGetFdValueSize(fd);
+ int fd_max_entries = bpfGetFdMaxEntries(fd);
+ int fd_map_flags = bpfGetFdMapFlags(fd);
+
+ // DEVMAPs are readonly from the bpf program side's point of view, as such
+ // the kernel in kernel/bpf/devmap.c dev_map_init_map() will set the flag
+ int desired_map_flags = (int)mapDef.map_flags;
+ if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
+ desired_map_flags |= BPF_F_RDONLY_PROG;
+
+ // The .h file enforces that this is a power of two, and page size will
+ // also always be a power of two, so this logic is actually enough to
+ // force it to be a multiple of the page size, as required by the kernel.
+ unsigned int desired_max_entries = mapDef.max_entries;
+ if (type == BPF_MAP_TYPE_RINGBUF) {
+ if (desired_max_entries < page_size) desired_max_entries = page_size;
+ }
+
+ // The following checks should *never* trigger, if one of them somehow does,
+ // it probably means a bpf .o file has been changed/replaced at runtime
+ // and bpfloader was manually rerun (normally it should only run *once*
+ // early during the boot process).
+ // Another possibility is that something is misconfigured in the code:
+ // most likely a shared map is declared twice differently.
+ // But such a change should never be checked into the source tree...
+ if ((fd_type == type) &&
+ (fd_key_size == (int)mapDef.key_size) &&
+ (fd_value_size == (int)mapDef.value_size) &&
+ (fd_max_entries == (int)desired_max_entries) &&
+ (fd_map_flags == desired_map_flags)) {
+ return true;
+ }
+
+ ALOGE("bpf map name %s mismatch: desired/found: "
+ "type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d",
+ mapName.c_str(), type, fd_type, mapDef.key_size, fd_key_size, mapDef.value_size,
+ fd_value_size, mapDef.max_entries, fd_max_entries, desired_map_flags, fd_map_flags);
+ return false;
+}
+
+static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
+ const char* prefix, const unsigned long long allowedDomainBitmask,
+ const size_t sizeOfBpfMapDef) {
+ int ret;
+ vector<char> mdData;
+ vector<struct bpf_map_def> md;
+ vector<string> mapNames;
+ string objName = pathToObjName(string(elfPath));
+
+ ret = readSectionByName("maps", elfFile, mdData);
+ if (ret == -2) return 0; // no maps to read
+ if (ret) return ret;
+
+ if (mdData.size() % sizeOfBpfMapDef) {
+ ALOGE("createMaps failed due to improper sized maps section, %zu %% %zu != 0",
+ mdData.size(), sizeOfBpfMapDef);
+ return -1;
+ };
+
+ int mapCount = mdData.size() / sizeOfBpfMapDef;
+ md.resize(mapCount);
+ size_t trimmedSize = std::min(sizeOfBpfMapDef, sizeof(struct bpf_map_def));
+
+ const char* dataPtr = mdData.data();
+ for (auto& m : md) {
+ // First we zero initialize
+ memset(&m, 0, sizeof(m));
+ // Then we set non-zero defaults
+ m.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
+ m.max_kver = 0xFFFFFFFFu; // matches KVER_INF from bpf_helpers.h
+ // Then we copy over the structure prefix from the ELF file.
+ memcpy(&m, dataPtr, trimmedSize);
+ // Move to next struct in the ELF file
+ dataPtr += sizeOfBpfMapDef;
+ }
+
+ ret = getSectionSymNames(elfFile, "maps", mapNames);
+ if (ret) return ret;
+
+ unsigned kvers = kernelVersion();
+
+ for (int i = 0; i < (int)mapNames.size(); i++) {
+ if (md[i].zero != 0) abort();
+
+ if (BPFLOADER_VERSION < md[i].bpfloader_min_ver) {
+ ALOGI("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
+ md[i].bpfloader_min_ver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (BPFLOADER_VERSION >= md[i].bpfloader_max_ver) {
+ ALOGI("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
+ md[i].bpfloader_max_ver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (kvers < md[i].min_kver) {
+ ALOGI("skipping map %s which requires kernel version 0x%x >= 0x%x",
+ mapNames[i].c_str(), kvers, md[i].min_kver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (kvers >= md[i].max_kver) {
+ ALOGI("skipping map %s which requires kernel version 0x%x < 0x%x",
+ mapNames[i].c_str(), kvers, md[i].max_kver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if ((md[i].ignore_on_eng && isEng()) || (md[i].ignore_on_user && isUser()) ||
+ (md[i].ignore_on_userdebug && isUserdebug())) {
+ ALOGI("skipping map %s which is ignored on %s builds", mapNames[i].c_str(),
+ getBuildType().c_str());
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if ((isArm() && isKernel32Bit() && md[i].ignore_on_arm32) ||
+ (isArm() && isKernel64Bit() && md[i].ignore_on_aarch64) ||
+ (isX86() && isKernel32Bit() && md[i].ignore_on_x86_32) ||
+ (isX86() && isKernel64Bit() && md[i].ignore_on_x86_64) ||
+ (isRiscV() && md[i].ignore_on_riscv64)) {
+ ALOGI("skipping map %s which is ignored on %s", mapNames[i].c_str(),
+ describeArch());
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ enum bpf_map_type type = md[i].type;
+ if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
+ // On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
+ // of be approximated: HASH has the same userspace visible api.
+ // However it cannot be used by ebpf programs in the same way.
+ // Since bpf_redirect_map() only requires 4.14, a program using a DEVMAP_HASH map
+ // would fail to load (due to trying to redirect to a HASH instead of DEVMAP_HASH).
+ // One must thus tag any BPF_MAP_TYPE_DEVMAP_HASH + bpf_redirect_map() using
+ // programs as being 5.4+...
+ type = BPF_MAP_TYPE_HASH;
+ }
+
+ // The .h file enforces that this is a power of two, and page size will
+ // also always be a power of two, so this logic is actually enough to
+ // force it to be a multiple of the page size, as required by the kernel.
+ unsigned int max_entries = md[i].max_entries;
+ if (type == BPF_MAP_TYPE_RINGBUF) {
+ if (max_entries < page_size) max_entries = page_size;
+ }
+
+ domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
+ if (specified(selinux_context)) {
+ if (!inDomainBitmask(selinux_context, allowedDomainBitmask)) {
+ ALOGE("map %s has invalid selinux_context of %d (allowed bitmask 0x%llx)",
+ mapNames[i].c_str(), selinux_context, allowedDomainBitmask);
+ return -EINVAL;
+ }
+ ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
+ md[i].selinux_context, selinux_context, lookupSelinuxContext(selinux_context),
+ lookupPinSubdir(selinux_context));
+ }
+
+ domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
+ if (unrecognized(pin_subdir)) return -ENOTDIR;
+ if (specified(pin_subdir)) {
+ if (!inDomainBitmask(pin_subdir, allowedDomainBitmask)) {
+ ALOGE("map %s has invalid pin_subdir of %d (allowed bitmask 0x%llx)",
+ mapNames[i].c_str(), pin_subdir, allowedDomainBitmask);
+ return -EINVAL;
+ }
+ ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
+ pin_subdir, lookupPinSubdir(pin_subdir));
+ }
+
+ // Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
+ // except that maps shared across .o's have empty <objName>
+ // Note: <objName> refers to the extension-less basename of the .o file (without @ suffix).
+ string mapPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "map_" +
+ (md[i].shared ? "" : objName) + "_" + mapNames[i];
+ bool reuse = false;
+ unique_fd fd;
+ int saved_errno;
+
+ if (access(mapPinLoc.c_str(), F_OK) == 0) {
+ fd.reset(mapRetrieveRO(mapPinLoc.c_str()));
+ saved_errno = errno;
+ ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get());
+ reuse = true;
+ } else {
+ union bpf_attr req = {
+ .map_type = type,
+ .key_size = md[i].key_size,
+ .value_size = md[i].value_size,
+ .max_entries = max_entries,
+ .map_flags = md[i].map_flags,
+ };
+ strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
+ fd.reset(bpf(BPF_MAP_CREATE, req));
+ saved_errno = errno;
+ ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
+ }
+
+ if (!fd.ok()) return -saved_errno;
+
+ // When reusing a pinned map, we need to check the map type/sizes/etc match, but for
+ // safety (since reuse code path is rare) run these checks even if we just created it.
+ // We assume failure is due to pinned map mismatch, hence the 'NOT UNIQUE' return code.
+ if (!mapMatchesExpectations(fd, mapNames[i], md[i], type)) return -ENOTUNIQ;
+
+ if (!reuse) {
+ if (specified(selinux_context)) {
+ string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
+ "tmp_map_" + objName + "_" + mapNames[i];
+ ret = bpfFdPin(fd, createLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ ret = renameat2(AT_FDCWD, createLoc.c_str(),
+ AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
+ if (ret) {
+ int err = errno;
+ ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), mapPinLoc.c_str(), ret,
+ err, strerror(err));
+ return -err;
+ }
+ } else {
+ ret = bpfFdPin(fd, mapPinLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ }
+ ret = chmod(mapPinLoc.c_str(), md[i].mode);
+ if (ret) {
+ int err = errno;
+ ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), md[i].mode, ret, err,
+ strerror(err));
+ return -err;
+ }
+ ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
+ if (ret) {
+ int err = errno;
+ ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), md[i].uid, md[i].gid,
+ ret, err, strerror(err));
+ return -err;
+ }
+ }
+
+ int mapId = bpfGetFdMapId(fd);
+ if (mapId == -1) {
+ ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
+ } else {
+ ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
+ }
+
+ mapFds.push_back(std::move(fd));
+ }
+
+ return ret;
+}
+
+/* For debugging, dump all instructions */
+static void dumpIns(char* ins, int size) {
+ for (int row = 0; row < size / 8; row++) {
+ ALOGE("%d: ", row);
+ for (int j = 0; j < 8; j++) {
+ ALOGE("%3x ", ins[(row * 8) + j]);
+ }
+ ALOGE("\n");
+ }
+}
+
+/* For debugging, dump all code sections from cs list */
+static void dumpAllCs(vector<codeSection>& cs) {
+ for (int i = 0; i < (int)cs.size(); i++) {
+ ALOGE("Dumping cs %d, name %s", int(i), cs[i].name.c_str());
+ dumpIns((char*)cs[i].data.data(), cs[i].data.size());
+ ALOGE("-----------");
+ }
+}
+
+static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
+ int insnIndex;
+ struct bpf_insn *insn, *insns;
+
+ insns = (struct bpf_insn*)(insnsPtr);
+
+ insnIndex = offset / sizeof(struct bpf_insn);
+ insn = &insns[insnIndex];
+
+ // Occasionally might be useful for relocation debugging, but pretty spammy
+ if (0) {
+ ALOGD("applying relo to instruction at byte offset: %llu, "
+ "insn offset %d, insn %llx",
+ (unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
+ }
+
+ if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
+ ALOGE("Dumping all instructions till ins %d", insnIndex);
+ ALOGE("invalid relo for insn %d: code 0x%x", insnIndex, insn->code);
+ dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
+ return;
+ }
+
+ insn->imm = fd;
+ insn->src_reg = BPF_PSEUDO_MAP_FD;
+}
+
+static void applyMapRelo(ifstream& elfFile, vector<unique_fd> &mapFds, vector<codeSection>& cs) {
+ vector<string> mapNames;
+
+ int ret = getSectionSymNames(elfFile, "maps", mapNames);
+ if (ret) return;
+
+ for (int k = 0; k != (int)cs.size(); k++) {
+ Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
+ int n_rel = cs[k].rel_data.size() / sizeof(*rel);
+
+ for (int i = 0; i < n_rel; i++) {
+ int symIndex = ELF64_R_SYM(rel[i].r_info);
+ string symName;
+
+ ret = getSymNameByIdx(elfFile, symIndex, symName);
+ if (ret) return;
+
+ /* Find the map fd and apply relo */
+ for (int j = 0; j < (int)mapNames.size(); j++) {
+ if (!mapNames[j].compare(symName)) {
+ applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
+ const char* prefix, const unsigned long long allowedDomainBitmask) {
+ unsigned kvers = kernelVersion();
+
+ if (!kvers) {
+ ALOGE("unable to get kernel version");
+ return -EINVAL;
+ }
+
+ string objName = pathToObjName(string(elfPath));
+
+ for (int i = 0; i < (int)cs.size(); i++) {
+ unique_fd& fd = cs[i].prog_fd;
+ int ret;
+ string name = cs[i].name;
+
+ if (!cs[i].prog_def.has_value()) {
+ ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
+ return -EINVAL;
+ }
+
+ unsigned min_kver = cs[i].prog_def->min_kver;
+ unsigned max_kver = cs[i].prog_def->max_kver;
+ ALOGD("cs[%d].name:%s min_kver:%x .max_kver:%x (kvers:%x)", i, name.c_str(), min_kver,
+ max_kver, kvers);
+ if (kvers < min_kver) continue;
+ if (kvers >= max_kver) continue;
+
+ unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
+ unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
+ domain selinux_context = getDomainFromSelinuxContext(cs[i].prog_def->selinux_context);
+ domain pin_subdir = getDomainFromPinSubdir(cs[i].prog_def->pin_subdir);
+ // Note: make sure to only check for unrecognized *after* verifying bpfloader
+ // version limits include this bpfloader's version.
+
+ ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
+ bpfMinVer, bpfMaxVer);
+ if (BPFLOADER_VERSION < bpfMinVer) continue;
+ if (BPFLOADER_VERSION >= bpfMaxVer) continue;
+
+ if ((cs[i].prog_def->ignore_on_eng && isEng()) ||
+ (cs[i].prog_def->ignore_on_user && isUser()) ||
+ (cs[i].prog_def->ignore_on_userdebug && isUserdebug())) {
+ ALOGD("cs[%d].name:%s is ignored on %s builds", i, name.c_str(),
+ getBuildType().c_str());
+ continue;
+ }
+
+ if ((isArm() && isKernel32Bit() && cs[i].prog_def->ignore_on_arm32) ||
+ (isArm() && isKernel64Bit() && cs[i].prog_def->ignore_on_aarch64) ||
+ (isX86() && isKernel32Bit() && cs[i].prog_def->ignore_on_x86_32) ||
+ (isX86() && isKernel64Bit() && cs[i].prog_def->ignore_on_x86_64) ||
+ (isRiscV() && cs[i].prog_def->ignore_on_riscv64)) {
+ ALOGD("cs[%d].name:%s is ignored on %s", i, name.c_str(), describeArch());
+ continue;
+ }
+
+ if (unrecognized(pin_subdir)) return -ENOTDIR;
+
+ if (specified(selinux_context)) {
+ if (!inDomainBitmask(selinux_context, allowedDomainBitmask)) {
+ ALOGE("prog %s has invalid selinux_context of %d (allowed bitmask 0x%llx)",
+ name.c_str(), selinux_context, allowedDomainBitmask);
+ return -EINVAL;
+ }
+ ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
+ cs[i].prog_def->selinux_context, selinux_context,
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
+ }
+
+ if (specified(pin_subdir)) {
+ if (!inDomainBitmask(pin_subdir, allowedDomainBitmask)) {
+ ALOGE("prog %s has invalid pin_subdir of %d (allowed bitmask 0x%llx)", name.c_str(),
+ pin_subdir, allowedDomainBitmask);
+ return -EINVAL;
+ }
+ ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
+ cs[i].prog_def->pin_subdir, pin_subdir, lookupPinSubdir(pin_subdir));
+ }
+
+ // strip any potential $foo suffix
+ // this can be used to provide duplicate programs
+ // conditionally loaded based on running kernel version
+ name = name.substr(0, name.find_last_of('$'));
+
+ bool reuse = false;
+ // Format of pin location is
+ // /sys/fs/bpf/<prefix>prog_<objName>_<progName>
+ string progPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "prog_" +
+ objName + '_' + string(name);
+ if (access(progPinLoc.c_str(), F_OK) == 0) {
+ fd.reset(retrieveProgram(progPinLoc.c_str()));
+ ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
+ (!fd.ok() ? std::strerror(errno) : "no error"));
+ reuse = true;
+ } else {
+ vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
+
+ union bpf_attr req = {
+ .prog_type = cs[i].type,
+ .kern_version = kvers,
+ .license = ptr_to_u64(license.c_str()),
+ .insns = ptr_to_u64(cs[i].data.data()),
+ .insn_cnt = static_cast<__u32>(cs[i].data.size() / sizeof(struct bpf_insn)),
+ .log_level = 1,
+ .log_buf = ptr_to_u64(log_buf.data()),
+ .log_size = static_cast<__u32>(log_buf.size()),
+ .expected_attach_type = cs[i].expected_attach_type,
+ };
+ strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
+ fd.reset(bpf(BPF_PROG_LOAD, req));
+
+ ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
+ cs[i].name.c_str(), fd.get(), (!fd.ok() ? std::strerror(errno) : "no error"));
+
+ if (!fd.ok()) {
+ vector<string> lines = android::base::Split(log_buf.data(), "\n");
+
+ ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
+ for (const auto& line : lines) ALOGW("%s", line.c_str());
+ ALOGW("BPF_PROG_LOAD - END log_buf contents.");
+
+ if (cs[i].prog_def->optional) {
+ ALOGW("failed program is marked optional - continuing...");
+ continue;
+ }
+ ALOGE("non-optional program failed to load.");
+ }
+ }
+
+ if (!fd.ok()) return fd.get();
+
+ if (!reuse) {
+ if (specified(selinux_context)) {
+ string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
+ "tmp_prog_" + objName + '_' + string(name);
+ ret = bpfFdPin(fd, createLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ ret = renameat2(AT_FDCWD, createLoc.c_str(),
+ AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
+ if (ret) {
+ int err = errno;
+ ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), progPinLoc.c_str(), ret,
+ err, strerror(err));
+ return -err;
+ }
+ } else {
+ ret = bpfFdPin(fd, progPinLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ }
+ if (chmod(progPinLoc.c_str(), 0440)) {
+ int err = errno;
+ ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
+ return -err;
+ }
+ if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
+ (gid_t)cs[i].prog_def->gid)) {
+ int err = errno;
+ ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), cs[i].prog_def->uid,
+ cs[i].prog_def->gid, err, strerror(err));
+ return -err;
+ }
+ }
+
+ int progId = bpfGetFdProgId(fd);
+ if (progId == -1) {
+ ALOGE("bpfGetFdProgId failed, ret: %d [%d]", progId, errno);
+ } else {
+ ALOGI("prog %s id %d", progPinLoc.c_str(), progId);
+ }
+ }
+
+ return 0;
+}
+
+int loadProg(const char* elfPath, bool* isCritical, const Location& location) {
+ vector<char> license;
+ vector<char> critical;
+ vector<codeSection> cs;
+ vector<unique_fd> mapFds;
+ int ret;
+
+ if (!isCritical) return -1;
+ *isCritical = false;
+
+ ifstream elfFile(elfPath, ios::in | ios::binary);
+ if (!elfFile.is_open()) return -1;
+
+ ret = readSectionByName("critical", elfFile, critical);
+ *isCritical = !ret;
+
+ ret = readSectionByName("license", elfFile, license);
+ if (ret) {
+ ALOGE("Couldn't find license in %s", elfPath);
+ return ret;
+ } else {
+ ALOGD("Loading %s%s ELF object %s with license %s",
+ *isCritical ? "critical for " : "optional", *isCritical ? (char*)critical.data() : "",
+ elfPath, (char*)license.data());
+ }
+
+ // the following default values are for bpfloader V0.0 format which does not include them
+ unsigned int bpfLoaderMinVer =
+ readSectionUint("bpfloader_min_ver", elfFile, DEFAULT_BPFLOADER_MIN_VER);
+ unsigned int bpfLoaderMaxVer =
+ readSectionUint("bpfloader_max_ver", elfFile, DEFAULT_BPFLOADER_MAX_VER);
+ unsigned int bpfLoaderMinRequiredVer =
+ readSectionUint("bpfloader_min_required_ver", elfFile, 0);
+ size_t sizeOfBpfMapDef =
+ readSectionUint("size_of_bpf_map_def", elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF);
+ size_t sizeOfBpfProgDef =
+ readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
+
+ // inclusive lower bound check
+ if (BPFLOADER_VERSION < bpfLoaderMinVer) {
+ ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
+ BPFLOADER_VERSION, elfPath, bpfLoaderMinVer);
+ return 0;
+ }
+
+ // exclusive upper bound check
+ if (BPFLOADER_VERSION >= bpfLoaderMaxVer) {
+ ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with max ver 0x%05x",
+ BPFLOADER_VERSION, elfPath, bpfLoaderMaxVer);
+ return 0;
+ }
+
+ if (BPFLOADER_VERSION < bpfLoaderMinRequiredVer) {
+ ALOGI("BpfLoader version 0x%05x failing due to ELF object %s with required min ver 0x%05x",
+ BPFLOADER_VERSION, elfPath, bpfLoaderMinRequiredVer);
+ return -1;
+ }
+
+ ALOGI("BpfLoader version 0x%05x processing ELF object %s with ver [0x%05x,0x%05x)",
+ BPFLOADER_VERSION, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
+
+ if (sizeOfBpfMapDef < DEFAULT_SIZEOF_BPF_MAP_DEF) {
+ ALOGE("sizeof(bpf_map_def) of %zu is too small (< %d)", sizeOfBpfMapDef,
+ DEFAULT_SIZEOF_BPF_MAP_DEF);
+ return -1;
+ }
+
+ if (sizeOfBpfProgDef < DEFAULT_SIZEOF_BPF_PROG_DEF) {
+ ALOGE("sizeof(bpf_prog_def) of %zu is too small (< %d)", sizeOfBpfProgDef,
+ DEFAULT_SIZEOF_BPF_PROG_DEF);
+ return -1;
+ }
+
+ ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef, location.allowedProgTypes,
+ location.allowedProgTypesLength);
+ if (ret) {
+ ALOGE("Couldn't read all code sections in %s", elfPath);
+ return ret;
+ }
+
+ /* Just for future debugging */
+ if (0) dumpAllCs(cs);
+
+ ret = createMaps(elfPath, elfFile, mapFds, location.prefix, location.allowedDomainBitmask,
+ sizeOfBpfMapDef);
+ if (ret) {
+ ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
+ return ret;
+ }
+
+ for (int i = 0; i < (int)mapFds.size(); i++)
+ ALOGD("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
+
+ applyMapRelo(elfFile, mapFds, cs);
+
+ ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix,
+ location.allowedDomainBitmask);
+ if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
+
+ return ret;
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/netbpfload/loader.h b/netbpfload/loader.h
new file mode 100644
index 0000000..a47e4da
--- /dev/null
+++ b/netbpfload/loader.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018-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.
+ */
+
+#pragma once
+
+#include <linux/bpf.h>
+
+#include <fstream>
+
+namespace android {
+namespace bpf {
+
+// Bpf programs may specify per-program & per-map selinux_context and pin_subdir.
+//
+// The BpfLoader needs to convert these bpf.o specified strings into an enum
+// for internal use (to check that valid values were specified for the specific
+// location of the bpf.o file).
+//
+// It also needs to map selinux_context's into pin_subdir's.
+// This is because of how selinux_context is actually implemented via pin+rename.
+//
+// Thus 'domain' enumerates all selinux_context's/pin_subdir's that the BpfLoader
+// is aware of. Thus there currently needs to be a 1:1 mapping between the two.
+//
+enum class domain : int {
+ unrecognized = -1, // invalid for this version of the bpfloader
+ unspecified = 0, // means just use the default for that specific pin location
+ tethering, // (S+) fs_bpf_tethering /sys/fs/bpf/tethering
+ net_private, // (T+) fs_bpf_net_private /sys/fs/bpf/net_private
+ net_shared, // (T+) fs_bpf_net_shared /sys/fs/bpf/net_shared
+ netd_readonly, // (T+) fs_bpf_netd_readonly /sys/fs/bpf/netd_readonly
+ netd_shared, // (T+) fs_bpf_netd_shared /sys/fs/bpf/netd_shared
+};
+
+// Note: this does not include domain::unrecognized, but does include domain::unspecified
+static constexpr domain AllDomains[] = {
+ domain::unspecified,
+ domain::tethering,
+ domain::net_private,
+ domain::net_shared,
+ domain::netd_readonly,
+ domain::netd_shared,
+};
+
+static constexpr bool unrecognized(domain d) {
+ return d == domain::unrecognized;
+}
+
+// Note: this doesn't handle unrecognized, handle it first.
+static constexpr bool specified(domain d) {
+ return d != domain::unspecified;
+}
+
+static constexpr unsigned long long domainToBitmask(domain d) {
+ return specified(d) ? 1uLL << (static_cast<int>(d) - 1) : 0;
+}
+
+static constexpr bool inDomainBitmask(domain d, unsigned long long v) {
+ return domainToBitmask(d) & v;
+}
+
+struct Location {
+ const char* const dir = "";
+ const char* const prefix = "";
+ unsigned long long allowedDomainBitmask = 0;
+ const bpf_prog_type* allowedProgTypes = nullptr;
+ size_t allowedProgTypesLength = 0;
+};
+
+// BPF loader implementation. Loads an eBPF ELF object
+int loadProg(const char* elfPath, bool* isCritical, const Location &location = {});
+
+// Exposed for testing
+unsigned int readSectionUint(const char* name, std::ifstream& elfFile, unsigned int defVal);
+
+// Returns the build type string (from ro.build.type).
+const std::string& getBuildType();
+
+// The following functions classify the 3 Android build types.
+inline bool isEng() {
+ return getBuildType() == "eng";
+}
+inline bool isUser() {
+ return getBuildType() == "user";
+}
+inline bool isUserdebug() {
+ return getBuildType() == "userdebug";
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/netbpfload/netbpfload.rc b/netbpfload/netbpfload.rc
new file mode 100644
index 0000000..20fbb9f
--- /dev/null
+++ b/netbpfload/netbpfload.rc
@@ -0,0 +1,85 @@
+# zygote-start is what officially starts netd (see //system/core/rootdir/init.rc)
+# However, on some hardware it's started from post-fs-data as well, which is just
+# a tad earlier. There's no benefit to that though, since on 4.9+ P+ devices netd
+# will just block until bpfloader finishes and sets the bpf.progs_loaded property.
+#
+# It is important that we start netbpfload after:
+# - /sys/fs/bpf is already mounted,
+# - apex (incl. rollback) is initialized (so that in the future we can load bpf
+# programs shipped as part of apex mainline modules)
+# - logd is ready for us to log stuff
+#
+# At the same time we want to be as early as possible to reduce races and thus
+# failures (before memory is fragmented, and cpu is busy running tons of other
+# stuff) and we absolutely want to be before netd and the system boot slot is
+# considered to have booted successfully.
+#
+on load_bpf_programs
+ exec_start netbpfload
+
+service netbpfload /system/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ # The following group memberships are a workaround for lack of DAC_OVERRIDE
+ # and allow us to open (among other things) files that we created and are
+ # no longer root owned (due to CHOWN) but still have group read access to
+ # one of the following groups. This is not perfect, but a more correct
+ # solution requires significantly more effort to implement.
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ #
+ # Set RLIMIT_MEMLOCK to 1GiB for netbpfload
+ #
+ # Actually only 8MiB would be needed if netbpfload ran as its own uid.
+ #
+ # However, while the rlimit is per-thread, the accounting is system wide.
+ # So, for example, if the graphics stack has already allocated 10MiB of
+ # memlock data before netbpfload even gets a chance to run, it would fail
+ # if its memlock rlimit is only 8MiB - since there would be none left for it.
+ #
+ # netbpfload succeeding is critical to system health, since a failure will
+ # cause netd crashloop and thus system server crashloop... and the only
+ # recovery is a full kernel reboot.
+ #
+ # We've had issues where devices would sometimes (rarely) boot into
+ # a crashloop because netbpfload would occasionally lose a boot time
+ # race against the graphics stack's boot time locked memory allocation.
+ #
+ # Thus netbpfload's memlock has to be 8MB higher then the locked memory
+ # consumption of the root uid anywhere else in the system...
+ # But we don't know what that is for all possible devices...
+ #
+ # Ideally, we'd simply grant netbpfload the IPC_LOCK capability and it
+ # would simply ignore it's memlock rlimit... but it turns that this
+ # capability is not even checked by the kernel's bpf system call.
+ #
+ # As such we simply use 1GiB as a reasonable approximation of infinity.
+ #
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ #
+ # How to debug bootloops caused by 'netbpfload-failed'.
+ #
+ # 1. On some lower RAM devices (like wembley) you may need to first enable developer mode
+ # (from the Settings app UI), and change the developer option "Logger buffer sizes"
+ # from the default (wembley: 64kB) to the maximum (1M) per log buffer.
+ # Otherwise buffer will overflow before you manage to dump it and you'll get useless logs.
+ #
+ # 2. comment out 'reboot_on_failure reboot,netbpfload-failed' below
+ # 3. rebuild/reflash/reboot
+ # 4. as the device is booting up capture netbpfload logs via:
+ # adb logcat -s 'NetBpfLoad:*' 'NetBpfLoader:*'
+ #
+ # something like:
+ # $ adb reboot; sleep 1; adb wait-for-device; adb root; sleep 1; adb wait-for-device; adb logcat -s 'NetBpfLoad:*' 'NetBpfLoader:*'
+ # will take care of capturing logs as early as possible
+ #
+ # 5. look through the logs from the kernel's bpf verifier that netbpfload dumps out,
+ # it usually makes sense to search back from the end and find the particular
+ # bpf verifier failure that caused netbpfload to terminate early with an error code.
+ # This will probably be something along the lines of 'too many jumps' or
+ # 'cannot prove return value is 0 or 1' or 'unsupported / unknown operation / helper',
+ # 'invalid bpf_context access', etc.
+ #
+ reboot_on_failure reboot,netbpfload-failed
+ # we're not really updatable, but want to be able to load bpf programs shipped in apexes
+ updatable
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index a090a54..fa92f10 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -129,6 +129,15 @@
RETURN_IF_NOT_OK(
attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
}
+
+ // This should trivially pass, since we just attached up above,
+ // but BPF_PROG_QUERY is only implemented on 4.19+ kernels.
+ if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_EGRESS) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_INGRESS) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_CREATE) <= 0) abort();
+ }
+
return netdutils::status::ok;
}
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index d94c8dc..c473444 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -17,6 +17,8 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -221,6 +223,19 @@
}
/**
+ * Returns whether the scorable has any of the PRIORITIZE_* capabilities.
+ *
+ * These capabilities code for customer slices, and a network that has one is a customer slice.
+ */
+ private boolean hasPrioritizedCapability(@NonNull final Scoreable nai) {
+ final NetworkCapabilities caps = nai.getCapsNoCopy();
+ final long anyPrioritizeCapability =
+ (1L << NET_CAPABILITY_PRIORITIZE_LATENCY)
+ | (1L << NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ return 0 != (caps.getCapabilitiesInternal() & anyPrioritizeCapability);
+ }
+
+ /**
* Get the best network among a list of candidates according to policy.
* @param candidates the candidates
* @param currentSatisfier the current satisfier, or null if none
@@ -324,6 +339,12 @@
// change from the previous result. If there were, it's guaranteed candidates.size() > 0
// because accepted.size() > 0 above.
+ // If any network is not a slice with prioritized bandwidth or latency, don't choose one
+ // that is.
+ partitionInto(candidates, nai -> !hasPrioritizedCapability(nai), accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
// If some of the networks have a better transport than others, keep only the ones with
// the best transports.
for (final int transport : PREFERRED_TRANSPORTS_ORDER) {
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 59a63f2..621759e 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -74,7 +74,10 @@
"framework-configinfrastructure",
"framework-connectivity.stubs.module_lib",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_defaults {
@@ -141,7 +144,10 @@
"com.android.tethering",
"//apex_available:platform",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_library {
@@ -169,7 +175,10 @@
"com.android.tethering",
"//apex_available:platform",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_library {
@@ -194,7 +203,10 @@
"com.android.tethering",
"//apex_available:platform",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_library {
@@ -223,7 +235,10 @@
"com.android.tethering",
"//apex_available:platform",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_library {
@@ -258,7 +273,10 @@
"//packages/modules/Wifi/framework/tests:__subpackages__",
"//packages/apps/Settings",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
errorprone: {
enabled: true,
// Error-prone checking only warns of problems when building. To make the build fail with
@@ -301,7 +319,10 @@
"//packages/modules/Bluetooth/android/app",
"//packages/modules/Wifi/service:__subpackages__",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_library {
@@ -323,7 +344,10 @@
"com.android.tethering",
"//apex_available:platform",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_library {
@@ -346,7 +370,10 @@
"com.android.tethering",
"//apex_available:platform",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
// Limited set of utilities for use by service-connectivity-mdns-standalone-build-test, to make sure
diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java
index 94af11b..f1546c0 100644
--- a/staticlibs/device/com/android/net/module/util/BpfUtils.java
+++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java
@@ -66,4 +66,6 @@
throws IOException;
private static native boolean native_detachSingleProgramFromCgroup(int type,
String programPath, String cgroupPath) throws IOException;
+ private static native int native_getProgramIdFromCgroup(int type, String cgroupPath)
+ throws IOException;
}
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 5b75699..42f26f4 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -64,6 +64,9 @@
@VisibleForTesting
public static final long DEFAULT_PACKAGE_VERSION = 1000;
+ private static final String CORE_NETWORKING_TRUNK_STABLE_NAMESPACE = "android_core_networking";
+ private static final String CORE_NETWORKING_TRUNK_STABLE_FLAG_PACKAGE = "com.android.net.flags";
+
@VisibleForTesting
public static void resetPackageVersionCacheForTest() {
sPackageVersion = -1;
@@ -165,32 +168,18 @@
*
* This is useful to ensure that if a module install is rolled back, flags are not left fully
* rolled out on a version where they have not been well tested.
+ *
+ * If the feature is disabled by default and enabled by flag push, this method should be used.
+ * If the feature is enabled by default and disabled by flag push (kill switch),
+ * {@link #isNetworkStackFeatureNotChickenedOut(Context, String)} should be used.
+ *
* @param context The global context information about an app environment.
* @param name The name of the property to look up.
* @return true if this feature is enabled, or false if disabled.
*/
public static boolean isNetworkStackFeatureEnabled(@NonNull Context context,
@NonNull String name) {
- return isNetworkStackFeatureEnabled(context, name, false /* defaultEnabled */);
- }
-
- /**
- * Check whether or not one specific experimental feature for a particular namespace from
- * {@link DeviceConfig} is enabled by comparing module package version
- * with current version of property. If this property version is valid, the corresponding
- * experimental feature would be enabled, otherwise disabled.
- *
- * This is useful to ensure that if a module install is rolled back, flags are not left fully
- * rolled out on a version where they have not been well tested.
- * @param context The global context information about an app environment.
- * @param name The name of the property to look up.
- * @param defaultEnabled The value to return if the property does not exist or its value is
- * null.
- * @return true if this feature is enabled, or false if disabled.
- */
- public static boolean isNetworkStackFeatureEnabled(@NonNull Context context,
- @NonNull String name, boolean defaultEnabled) {
- return isFeatureEnabled(NAMESPACE_CONNECTIVITY, name, defaultEnabled,
+ return isFeatureEnabled(NAMESPACE_CONNECTIVITY, name, false /* defaultEnabled */,
() -> getPackageVersion(context));
}
@@ -420,4 +409,31 @@
return pkgs.get(0).activityInfo.applicationInfo.packageName;
}
+
+ /**
+ * Check whether one specific trunk stable flag in android_core_networking namespace is enabled.
+ * This method reads trunk stable feature flag value from DeviceConfig directly since
+ * java_aconfig_library soong module is not available in the mainline branch.
+ * After the mainline branch support the aconfig soong module, this function must be removed and
+ * java_aconfig_library must be used instead to check if the feature is enabled.
+ *
+ * @param flagName The name of the trunk stable flag
+ * @return true if this feature is enabled, or false if disabled.
+ */
+ public static boolean isTrunkStableFeatureEnabled(final String flagName) {
+ return isTrunkStableFeatureEnabled(
+ CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
+ CORE_NETWORKING_TRUNK_STABLE_FLAG_PACKAGE,
+ flagName
+ );
+ }
+
+ private static boolean isTrunkStableFeatureEnabled(final String namespace,
+ final String packageName, final String flagName) {
+ return DeviceConfig.getBoolean(
+ namespace,
+ packageName + "." + flagName,
+ false /* defaultValue */
+ );
+ }
}
diff --git a/staticlibs/device/com/android/net/module/util/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java
index 9878ea5..5e6a6c6 100644
--- a/staticlibs/device/com/android/net/module/util/SocketUtils.java
+++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java
@@ -19,6 +19,8 @@
import static android.net.util.SocketUtils.closeSocket;
import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.os.Build;
import android.system.NetlinkSocketAddress;
import java.io.FileDescriptor;
@@ -39,7 +41,7 @@
/**
* Make a socket address to communicate with netlink.
*/
- @NonNull
+ @NonNull @RequiresApi(Build.VERSION_CODES.S)
public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
return new NetlinkSocketAddress(portId, groupsMask);
}
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
deleted file mode 100644
index d413b2a..0000000
--- a/staticlibs/lint-baseline.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`"
- errorLine1=" final Collection<InetAddress> leftAddresses = left.getAddresses();"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java"
- line="158"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`"
- errorLine1=" final Collection<InetAddress> rightAddresses = right.getAddresses();"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java"
- line="159"
- column="62"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 29): `android.net.NetworkStats#addEntry`"
- errorLine1=" stats = stats.addEntry(entry);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
- line="113"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats.Entry`"
- errorLine1=" return new android.net.NetworkStats.Entry("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
- line="120"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats`"
- errorLine1=" android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
- line="108"
- column="42"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 29): `new android.system.NetlinkSocketAddress`"
- errorLine1=" return new NetlinkSocketAddress(portId, groupsMask);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/libs/net/common/device/com/android/net/module/util/SocketUtils.java"
- line="44"
- column="16"/>
- </issue>
-
-</issues>
\ No newline at end of file
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
index dd0804c..f7ffddb 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
@@ -33,7 +33,7 @@
BPF_REJECT
// *TWO* instructions: compare and if none of the bits are set jump over the reject statement
-#define BPF2_REJECT_IF_ANY_BITS_SET(v) \
+#define BPF2_REJECT_IF_ANY_MASKED_BITS_SET(v) \
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \
BPF_REJECT
@@ -108,3 +108,55 @@
_Static_assert(field_sizeof(struct ipv6hdr, field) == 4, "field of wrong size"); \
offsetof(ipv6hdr, field); \
}))
+
+// Load the length of the IPv4 header into X index register.
+// ie. X := 4 * IPv4.IHL, where IPv4.IHL is the bottom nibble
+// of the first byte of the IPv4 (aka network layer) header.
+#define BPF_LOADX_NET_RELATIVE_IPV4_HLEN \
+ BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, (__u32)SKF_NET_OFF)
+
+// Blindly assumes no IPv6 extension headers, just does X := 40
+// You may later adjust this as you parse through IPv6 ext hdrs.
+#define BPF_LOADX_CONSTANT_IPV6_HLEN \
+ BPF_STMT(BPF_LDX | BPF_W | BPF_IMM, sizeof(struct ipv6hdr))
+
+// NOTE: all the following require X to be setup correctly (v4: 20+, v6: 40+)
+
+// 8-bit load from L4 (TCP/UDP/...) header
+#define BPF_LOAD_NETX_RELATIVE_L4_U8(ofs) \
+ BPF_STMT(BPF_LD | BPF_B | BPF_IND, (__u32)SKF_NET_OFF + (ofs))
+
+// Big/Network Endian 16-bit load from L4 (TCP/UDP/...) header
+#define BPF_LOAD_NETX_RELATIVE_L4_BE16(ofs) \
+ BPF_STMT(BPF_LD | BPF_H | BPF_IND, (__u32)SKF_NET_OFF + (ofs))
+
+// Big/Network Endian 32-bit load from L4 (TCP/UDP/...) header
+#define BPF_LOAD_NETX_RELATIVE_L4_BE32(ofs) \
+ BPF_STMT(BPF_LD | BPF_W | BPF_IND, (__u32)SKF_NET_OFF + (ofs))
+
+// Both ICMPv4 and ICMPv6 start with u8 type, u8 code
+#define BPF_LOAD_NETX_RELATIVE_ICMP_TYPE BPF_LOAD_NETX_RELATIVE_L4_U8(0)
+#define BPF_LOAD_NETX_RELATIVE_ICMP_CODE BPF_LOAD_NETX_RELATIVE_L4_U8(1)
+
+// IPv6 extension headers (HOPOPTS, DSTOPS, FRAG) begin with a u8 nexthdr
+#define BPF_LOAD_NETX_RELATIVE_V6EXTHDR_NEXTHDR BPF_LOAD_NETX_RELATIVE_L4_U8(0)
+
+// IPv6 fragment header is always exactly 8 bytes long
+#define BPF_LOAD_CONSTANT_V6FRAGHDR_LEN \
+ BPF_STMT(BPF_LD | BPF_IMM, 8)
+
+// HOPOPTS/DSTOPS follow up with 'u8 len', counting 8 byte units, (0->8, 1->16)
+// *THREE* instructions
+#define BPF3_LOAD_NETX_RELATIVE_V6EXTHDR_LEN \
+ BPF_LOAD_NETX_RELATIVE_L4_U8(1) \
+ BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 1) \
+ BPF_STMT(BPF_ALU | BPF_LSH | BPF_K, 3)
+
+// *TWO* instructions: A += X; X := A
+#define BPF2_ADD_A_TO_X \
+ BPF_STMT(BPF_ALU | BPF_ADD | BPF_X, 0) \
+ BPF_STMT(BPF_MISC | BPF_TAX, 0)
+
+// UDP/UDPLITE/TCP/SCTP/DCCP all start with be16 srcport, dstport
+#define BPF_LOAD_NETX_RELATIVE_SRC_PORT BPF_LOAD_NETX_RELATIVE_L4_BE16(0)
+#define BPF_LOAD_NETX_RELATIVE_DST_PORT BPF_LOAD_NETX_RELATIVE_L4_BE16(2)
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index 13f7cb3..9995cb9 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -44,6 +44,11 @@
return syscall(__NR_bpf, cmd, &attr, sizeof(attr));
}
+// this version is meant for use with cmd's which mutate the argument
+inline int bpf(enum bpf_cmd cmd, bpf_attr *attr) {
+ return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
+}
+
inline int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
uint32_t max_entries, uint32_t map_flags) {
return bpf(BPF_MAP_CREATE, {
@@ -160,6 +165,27 @@
});
}
+inline int queryProgram(const BPF_FD_TYPE cg_fd,
+ enum bpf_attach_type attach_type,
+ __u32 query_flags = 0,
+ __u32 attach_flags = 0) {
+ int prog_id = -1; // equivalent to an array of one integer.
+ bpf_attr arg = {
+ .query = {
+ .target_fd = BPF_FD_TO_U32(cg_fd),
+ .attach_type = attach_type,
+ .query_flags = query_flags,
+ .attach_flags = attach_flags,
+ .prog_ids = ptr_to_u64(&prog_id), // pointer to output array
+ .prog_cnt = 1, // in: space - nr of ints in the array, out: used
+ }
+ };
+ int v = bpf(BPF_PROG_QUERY, &arg);
+ if (v) return v; // error case
+ if (!arg.query.prog_cnt) return 0; // no program, kernel never returns zero id
+ return prog_id; // return actual id
+}
+
inline int detachSingleProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
const BPF_FD_TYPE cg_fd) {
return bpf(BPF_PROG_DETACH, {
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index cb06afb..ab83da6 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -27,7 +27,7 @@
}
static jboolean com_android_net_module_util_TcUtils_isEthernet(JNIEnv *env,
- jobject clazz,
+ jclass clazz,
jstring iface) {
ScopedUtfChars interface(env, iface);
bool result = false;
@@ -43,7 +43,7 @@
// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
// /sys/fs/bpf/... direct-action
static void com_android_net_module_util_TcUtils_tcFilterAddDevBpf(
- JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
+ JNIEnv *env, jclass clazz, jint ifIndex, jboolean ingress, jshort prio,
jshort proto, jstring bpfProgPath) {
ScopedUtfChars pathname(env, bpfProgPath);
int error = tcAddBpfFilter(ifIndex, ingress, prio, proto, pathname.c_str());
@@ -59,7 +59,7 @@
// action bpf object-pinned .. \
// drop
static void com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice(
- JNIEnv *env, jobject clazz, jint ifIndex, jshort prio, jshort proto,
+ JNIEnv *env, jclass clazz, jint ifIndex, jshort prio, jshort proto,
jint rateInBytesPerSec, jstring bpfProgPath) {
ScopedUtfChars pathname(env, bpfProgPath);
int error = tcAddIngressPoliceFilter(ifIndex, prio, proto, rateInBytesPerSec,
@@ -74,7 +74,7 @@
// tc filter del dev .. in/egress prio .. protocol ..
static void com_android_net_module_util_TcUtils_tcFilterDelDev(
- JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
+ JNIEnv *env, jclass clazz, jint ifIndex, jboolean ingress, jshort prio,
jshort proto) {
int error = tcDeleteFilter(ifIndex, ingress, prio, proto);
if (error) {
@@ -86,7 +86,7 @@
// tc qdisc add dev .. clsact
static void com_android_net_module_util_TcUtils_tcQdiscAddDevClsact(JNIEnv *env,
- jobject clazz,
+ jclass clazz,
jint ifIndex) {
int error = tcAddQdiscClsact(ifIndex);
if (error) {
diff --git a/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp b/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp
index 0f2ebbd..cf09379 100644
--- a/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp
+++ b/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp
@@ -32,7 +32,7 @@
// If attach fails throw error and return false.
static jboolean com_android_net_module_util_BpfUtil_attachProgramToCgroup(JNIEnv *env,
- jobject clazz, jint type, jstring bpfProgPath, jstring cgroupPath, jint flags) {
+ jclass clazz, jint type, jstring bpfProgPath, jstring cgroupPath, jint flags) {
ScopedUtfChars dirPath(env, cgroupPath);
unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
@@ -62,7 +62,7 @@
// If detach fails throw error and return false.
static jboolean com_android_net_module_util_BpfUtil_detachProgramFromCgroup(JNIEnv *env,
- jobject clazz, jint type, jstring cgroupPath) {
+ jclass clazz, jint type, jstring cgroupPath) {
ScopedUtfChars dirPath(env, cgroupPath);
unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
@@ -83,7 +83,7 @@
// If detach single program fails throw error and return false.
static jboolean com_android_net_module_util_BpfUtil_detachSingleProgramFromCgroup(JNIEnv *env,
- jobject clazz, jint type, jstring bpfProgPath, jstring cgroupPath) {
+ jclass clazz, jint type, jstring bpfProgPath, jstring cgroupPath) {
ScopedUtfChars dirPath(env, cgroupPath);
unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
@@ -110,6 +110,29 @@
return true;
}
+static jint com_android_net_module_util_BpfUtil_getProgramIdFromCgroup(JNIEnv *env,
+ jclass clazz, jint type, jstring cgroupPath) {
+
+ ScopedUtfChars dirPath(env, cgroupPath);
+ unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+ if (cg_fd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Failed to open the cgroup directory %s: %s",
+ dirPath.c_str(), strerror(errno));
+ return -1;
+ }
+
+ int id = bpf::queryProgram(cg_fd, (bpf_attach_type) type);
+ if (id < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Failed to query bpf program %d at %s: %s",
+ type, dirPath.c_str(), strerror(errno));
+ return -1;
+ }
+ return id; // may return 0 meaning none
+}
+
+
/*
* JNI registration.
*/
@@ -121,6 +144,8 @@
(void*) com_android_net_module_util_BpfUtil_detachProgramFromCgroup },
{ "native_detachSingleProgramFromCgroup", "(ILjava/lang/String;Ljava/lang/String;)Z",
(void*) com_android_net_module_util_BpfUtil_detachSingleProgramFromCgroup },
+ { "native_getProgramIdFromCgroup", "(ILjava/lang/String;)I",
+ (void*) com_android_net_module_util_BpfUtil_getProgramIdFromCgroup },
};
int register_com_android_net_module_util_BpfUtils(JNIEnv* env, char const* class_name) {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 9e04676..06b3e2f 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -71,6 +71,10 @@
public class DeviceConfigUtilsTest {
private static final String TEST_NAME_SPACE = "connectivity";
private static final String TEST_EXPERIMENT_FLAG = "experiment_flag";
+ private static final String CORE_NETWORKING_TRUNK_STABLE_NAMESPACE = "android_core_networking";
+ private static final String TEST_TRUNK_STABLE_FLAG = "trunk_stable_feature";
+ private static final String TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY =
+ "com.android.net.flags.trunk_stable_feature";
private static final int TEST_FLAG_VALUE = 28;
private static final String TEST_FLAG_VALUE_STRING = "28";
private static final int TEST_DEFAULT_FLAG_VALUE = 0;
@@ -301,15 +305,12 @@
assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
- // Follow defaultEnabled if the flag is not set
+ // If the flag is not set feature is disabled
doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
TEST_EXPERIMENT_FLAG));
doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
- assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG,
- false /* defaultEnabled */));
- assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG,
- true /* defaultEnabled */));
+ assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
}
@@ -506,4 +507,25 @@
verify(mContext, never()).getPackageName();
verify(mPm, never()).getPackageInfo(anyString(), anyInt());
}
+
+ @Test
+ public void testIsCoreNetworkingTrunkStableFeatureEnabled() {
+ doReturn(null).when(() -> DeviceConfig.getProperty(
+ CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
+ TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
+ assertFalse(DeviceConfigUtils.isTrunkStableFeatureEnabled(
+ TEST_TRUNK_STABLE_FLAG));
+
+ doReturn("false").when(() -> DeviceConfig.getProperty(
+ CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
+ TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
+ assertFalse(DeviceConfigUtils.isTrunkStableFeatureEnabled(
+ TEST_TRUNK_STABLE_FLAG));
+
+ doReturn("true").when(() -> DeviceConfig.getProperty(
+ CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
+ TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
+ assertTrue(DeviceConfigUtils.isTrunkStableFeatureEnabled(
+ TEST_TRUNK_STABLE_FLAG));
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d2c9481..62614c1 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -47,6 +47,7 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
@@ -3591,6 +3592,15 @@
}
}
+ private void setUidFirewallRule(final int chain, final int uid, final int rule) {
+ try {
+ mCm.setUidFirewallRule(chain, uid, rule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }
+
private static final boolean EXPECT_OPEN = false;
private static final boolean EXPECT_CLOSE = true;
@@ -3599,6 +3609,8 @@
runWithShellPermissionIdentity(() -> {
// Firewall chain status will be restored after the test.
final boolean wasChainEnabled = mCm.getFirewallChainEnabled(chain);
+ final int myUid = Process.myUid();
+ final int previousMyUidFirewallRule = mCm.getUidFirewallRule(chain, myUid);
final int previousUidFirewallRule = mCm.getUidFirewallRule(chain, targetUid);
final Socket socket = new Socket(TEST_HOST, HTTP_PORT);
socket.setSoTimeout(NETWORK_REQUEST_TIMEOUT_MS);
@@ -3606,12 +3618,12 @@
mCm.setFirewallChainEnabled(chain, false /* enable */);
assertSocketOpen(socket);
- try {
- mCm.setUidFirewallRule(chain, targetUid, rule);
- } catch (IllegalStateException ignored) {
- // Removing match causes an exception when the rule entry for the uid does
- // not exist. But this is fine and can be ignored.
+ setUidFirewallRule(chain, targetUid, rule);
+ if (targetUid != myUid) {
+ // If this test does not set rule on myUid, remove existing rule on myUid
+ setUidFirewallRule(chain, myUid, FIREWALL_RULE_DEFAULT);
}
+
mCm.setFirewallChainEnabled(chain, true /* enable */);
if (expectClose) {
@@ -3624,11 +3636,9 @@
mCm.setFirewallChainEnabled(chain, wasChainEnabled);
}, /* cleanup */ () -> {
// Restore the uid firewall rule status
- try {
- mCm.setUidFirewallRule(chain, targetUid, previousUidFirewallRule);
- } catch (IllegalStateException ignored) {
- // Removing match causes an exception when the rule entry for the uid does
- // not exist. But this is fine and can be ignored.
+ setUidFirewallRule(chain, targetUid, previousUidFirewallRule);
+ if (targetUid != myUid) {
+ setUidFirewallRule(chain, myUid, previousMyUidFirewallRule);
}
}, /* cleanup */ () -> {
socket.close();
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 805dd65..6b7954a 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -22,6 +22,7 @@
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -34,12 +35,14 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeFalse;
import android.Manifest;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Ikev2VpnProfile;
import android.net.IpSecAlgorithm;
@@ -60,11 +63,7 @@
import com.android.internal.util.HexDump;
import com.android.networkstack.apishim.ConstantsShim;
-import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
-import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
import com.android.networkstack.apishim.VpnManagerShimImpl;
-import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
-import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
import com.android.networkstack.apishim.common.VpnManagerShim;
import com.android.networkstack.apishim.common.VpnProfileStateShim;
import com.android.testutils.DevSdkIgnoreRule;
@@ -75,6 +74,7 @@
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -203,6 +203,12 @@
mUserCertKey = generateRandomCertAndKeyPair();
}
+ @Before
+ public void setUp() {
+ assumeFalse("Skipping test because watches don't support VPN",
+ sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+ }
+
@After
public void tearDown() {
for (TestableNetworkCallback callback : mCallbacksToUnregister) {
@@ -210,6 +216,15 @@
}
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
+
+ // Make sure the VpnProfile is not provisioned already.
+ sVpnMgr.stopProvisionedVpnProfile();
+
+ try {
+ sVpnMgr.startProvisionedVpnProfile();
+ fail("Expected SecurityException for missing consent");
+ } catch (SecurityException expected) {
+ }
}
/**
@@ -227,28 +242,25 @@
}
private Ikev2VpnProfile buildIkev2VpnProfileCommon(
- @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks,
+ @NonNull Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks,
boolean requiresValidation, boolean automaticIpVersionSelectionEnabled,
boolean automaticNattKeepaliveTimerEnabled) throws Exception {
- builderShim.setBypassable(true)
+ builder.setBypassable(true)
.setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
.setProxy(TEST_PROXY_INFO)
.setMaxMtu(TEST_MTU)
.setMetered(false);
- if (TestUtils.shouldTestTApis()) {
- builderShim.setRequiresInternetValidation(requiresValidation);
+ if (isAtLeastT()) {
+ builder.setRequiresInternetValidation(requiresValidation);
}
- if (TestUtils.shouldTestUApis()) {
- builderShim.setAutomaticIpVersionSelectionEnabled(automaticIpVersionSelectionEnabled);
- builderShim.setAutomaticNattKeepaliveTimerEnabled(automaticNattKeepaliveTimerEnabled);
+ if (isAtLeastU()) {
+ builder.setAutomaticIpVersionSelectionEnabled(automaticIpVersionSelectionEnabled);
+ builder.setAutomaticNattKeepaliveTimerEnabled(automaticNattKeepaliveTimerEnabled);
}
- // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
- // method and is not defined in shims.
// TODO: replace it in alternative way to remove the hidden method usage
- final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
if (isRestrictedToTestNetworks) {
builder.restrictToTestNetworks();
}
@@ -264,16 +276,14 @@
? IkeSessionTestUtils.IKE_PARAMS_V6 : IkeSessionTestUtils.IKE_PARAMS_V4,
IkeSessionTestUtils.CHILD_PARAMS);
- final Ikev2VpnProfileBuilderShim builderShim =
- Ikev2VpnProfileBuilderShimImpl.newInstance(params)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(params)
.setRequiresInternetValidation(requiresValidation)
.setProxy(TEST_PROXY_INFO)
.setMaxMtu(TEST_MTU)
.setMetered(false);
- // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
- // method and is not defined in shims.
+
// TODO: replace it in alternative way to remove the hidden method usage
- final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
if (isRestrictedToTestNetworks) {
builder.restrictToTestNetworks();
}
@@ -283,8 +293,8 @@
private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
boolean isRestrictedToTestNetworks, boolean requiresValidation)
throws Exception {
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY)
.setAuthPsk(TEST_PSK);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
requiresValidation, false /* automaticIpVersionSelectionEnabled */,
@@ -293,8 +303,8 @@
private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
throws Exception {
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
false /* requiresValidation */, false /* automaticIpVersionSelectionEnabled */,
@@ -303,8 +313,8 @@
private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
throws Exception {
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthDigitalSignature(
mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
@@ -347,7 +357,6 @@
@Test
public void testBuildIkev2VpnProfileWithIkeTunnelConnectionParams() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
- assumeTrue(TestUtils.shouldTestTApis());
final IkeTunnelConnectionParams expectedParams = new IkeTunnelConnectionParams(
IkeSessionTestUtils.IKE_PARAMS_V6, IkeSessionTestUtils.CHILD_PARAMS);
@@ -567,7 +576,7 @@
// regardless of its value. However, there is a race in Vpn(see b/228574221) that VPN may
// misuse VPN network itself as the underlying network. The fix is not available without
// SDK > T platform. Thus, verify this only on T+ platform.
- if (!requiresValidation && TestUtils.shouldTestTApis()) {
+ if (!requiresValidation && isAtLeastT()) {
cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, TIMEOUT_MS,
entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
.hasCapability(NET_CAPABILITY_VALIDATED));
@@ -647,7 +656,6 @@
@Test @IgnoreUpTo(SC_V2)
public void testStartStopVpnProfileV4WithValidation() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(false /* testIpv6Only */, true /* requiresValidation */,
false /* testSessionKey */, false /* testIkeTunConnParams */);
}
@@ -660,35 +668,30 @@
@Test @IgnoreUpTo(SC_V2)
public void testStartStopVpnProfileV6WithValidation() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(true /* testIpv6Only */, true /* requiresValidation */,
false /* testSessionKey */, false /* testIkeTunConnParams */);
}
@Test @IgnoreUpTo(SC_V2)
public void testStartStopVpnProfileIkeTunConnParamsV4() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
false /* testSessionKey */, true /* testIkeTunConnParams */);
}
@Test @IgnoreUpTo(SC_V2)
public void testStartStopVpnProfileIkeTunConnParamsV4WithValidation() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(false /* testIpv6Only */, true /* requiresValidation */,
false /* testSessionKey */, true /* testIkeTunConnParams */);
}
@Test @IgnoreUpTo(SC_V2)
public void testStartStopVpnProfileIkeTunConnParamsV6() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
false /* testSessionKey */, true /* testIkeTunConnParams */);
}
@Test @IgnoreUpTo(SC_V2)
public void testStartStopVpnProfileIkeTunConnParamsV6WithValidation() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(true /* testIpv6Only */, true /* requiresValidation */,
false /* testSessionKey */, true /* testIkeTunConnParams */);
}
@@ -696,7 +699,6 @@
@IgnoreUpTo(SC_V2)
@Test
public void testStartProvisionedVpnV4ProfileSession() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
true /* testSessionKey */, false /* testIkeTunConnParams */);
}
@@ -704,59 +706,44 @@
@IgnoreUpTo(SC_V2)
@Test
public void testStartProvisionedVpnV6ProfileSession() throws Exception {
- assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
true /* testSessionKey */, false /* testIkeTunConnParams */);
}
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
@Test
public void testBuildIkev2VpnProfileWithAutomaticNattKeepaliveTimerEnabled() throws Exception {
- // Cannot use @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) because this test also requires API
- // 34 shims, and @IgnoreUpTo does not check that.
- assumeTrue(TestUtils.shouldTestUApis());
-
final Ikev2VpnProfile profileWithDefaultValue = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
- final Ikev2VpnProfileShim<Ikev2VpnProfile> shimWithDefaultValue =
- Ikev2VpnProfileShimImpl.newInstance(profileWithDefaultValue);
- assertFalse(shimWithDefaultValue.isAutomaticNattKeepaliveTimerEnabled());
+ assertFalse(profileWithDefaultValue.isAutomaticNattKeepaliveTimerEnabled());
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthPsk(TEST_PSK);
final Ikev2VpnProfile profile = buildIkev2VpnProfileCommon(builder,
false /* isRestrictedToTestNetworks */,
false /* requiresValidation */,
false /* automaticIpVersionSelectionEnabled */,
true /* automaticNattKeepaliveTimerEnabled */);
- final Ikev2VpnProfileShim<Ikev2VpnProfile> shim =
- Ikev2VpnProfileShimImpl.newInstance(profile);
- assertTrue(shim.isAutomaticNattKeepaliveTimerEnabled());
+ assertTrue(profile.isAutomaticNattKeepaliveTimerEnabled());
}
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
@Test
public void testBuildIkev2VpnProfileWithAutomaticIpVersionSelectionEnabled() throws Exception {
- // Cannot use @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) because this test also requires API
- // 34 shims, and @IgnoreUpTo does not check that.
- assumeTrue(TestUtils.shouldTestUApis());
-
final Ikev2VpnProfile profileWithDefaultValue = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
- final Ikev2VpnProfileShim<Ikev2VpnProfile> shimWithDefaultValue =
- Ikev2VpnProfileShimImpl.newInstance(profileWithDefaultValue);
- assertFalse(shimWithDefaultValue.isAutomaticIpVersionSelectionEnabled());
+ assertFalse(profileWithDefaultValue.isAutomaticIpVersionSelectionEnabled());
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthPsk(TEST_PSK);
final Ikev2VpnProfile profile = buildIkev2VpnProfileCommon(builder,
false /* isRestrictedToTestNetworks */,
false /* requiresValidation */,
true /* automaticIpVersionSelectionEnabled */,
false /* automaticNattKeepaliveTimerEnabled */);
- final Ikev2VpnProfileShim<Ikev2VpnProfile> shim =
- Ikev2VpnProfileShimImpl.newInstance(profile);
- assertTrue(shim.isAutomaticIpVersionSelectionEnabled());
+ assertTrue(profile.isAutomaticIpVersionSelectionEnabled());
}
private static class CertificateAndKey {
diff --git a/tests/cts/net/src/android/net/cts/VpnServiceTest.java b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
index 5c7b5ca..f343e83 100644
--- a/tests/cts/net/src/android/net/cts/VpnServiceTest.java
+++ b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
@@ -15,12 +15,28 @@
*/
package android.net.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.net.DatagramSocket;
import java.net.Socket;
@@ -30,12 +46,21 @@
* blocks us from writing tests for positive cases. For now we only test for
* negative cases, and we will try to cover the rest in the future.
*/
-public class VpnServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class VpnServiceTest {
private static final String TAG = VpnServiceTest.class.getSimpleName();
+ private final Context mContext = InstrumentationRegistry.getContext();
private VpnService mVpnService = new VpnService();
+ @Before
+ public void setUp() {
+ assumeFalse("Skipping test because watches don't support VPN",
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+ }
+
+ @Test
@AppModeFull(reason = "PackageManager#queryIntentActivities cannot access in instant app mode")
public void testPrepare() throws Exception {
// Should never return null since we are not prepared.
@@ -47,6 +72,7 @@
assertEquals(1, count);
}
+ @Test
@AppModeFull(reason = "establish() requires prepare(), which requires PackageManager access")
public void testEstablish() throws Exception {
ParcelFileDescriptor descriptor = null;
@@ -63,6 +89,7 @@
}
}
+ @Test
@AppModeFull(reason = "Protecting sockets requires prepare(), which requires PackageManager")
public void testProtect_DatagramSocket() throws Exception {
DatagramSocket socket = new DatagramSocket();
@@ -78,6 +105,7 @@
}
}
+ @Test
@AppModeFull(reason = "Protecting sockets requires prepare(), which requires PackageManager")
public void testProtect_Socket() throws Exception {
Socket socket = new Socket();
@@ -93,6 +121,7 @@
}
}
+ @Test
@AppModeFull(reason = "Protecting sockets requires prepare(), which requires PackageManager")
public void testProtect_int() throws Exception {
DatagramSocket socket = new DatagramSocket();
@@ -114,6 +143,7 @@
}
}
+ @Test
public void testTunDevice() throws Exception {
File file = new File("/dev/tun");
assertTrue(file.exists());
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
index 532081a..2ab4e45 100644
--- a/tests/unit/java/android/net/VpnManagerTest.java
+++ b/tests/unit/java/android/net/VpnManagerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
@@ -27,11 +28,13 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.test.mock.MockContext;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
+import androidx.test.InstrumentationRegistry;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.MessageUtils;
@@ -47,6 +50,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class VpnManagerTest {
+
private static final String PKG_NAME = "fooPackage";
private static final String SESSION_NAME_STRING = "testSession";
@@ -66,6 +70,9 @@
@Before
public void setUp() throws Exception {
+ assumeFalse("Skipping test because watches don't support VPN",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH));
mMockService = mock(IVpnManager.class);
mVpnManager = new VpnManager(mMockContext, mMockService);
}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 1e3f389..87f7369 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -18,9 +18,12 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL as NET_CAP_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET as NET_CAP_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH as NET_CAP_PRIO_BW
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
import android.net.NetworkScore.POLICY_EXITING as EXITING
import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY as PRIMARY
import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI as YIELD_TO_BAD_WIFI
@@ -50,8 +53,8 @@
class NetworkRankerTest(private val activelyPreferBadWifi: Boolean) {
private val mRanker = NetworkRanker(NetworkRanker.Configuration(activelyPreferBadWifi))
- private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
- : NetworkRanker.Scoreable {
+ private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities) :
+ NetworkRanker.Scoreable {
override fun getScore() = sc
override fun getCapsNoCopy(): NetworkCapabilities = nc
}
@@ -196,4 +199,41 @@
val badExitingWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, EXITING), CAPS_WIFI)
assertEquals(cell, rank(cell, badExitingWifi))
}
+
+ @Test
+ fun testValidatedPolicyStrongerThanSlice() {
+ val unvalidatedNonslice = TestScore(score(EVER_EVALUATED),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+ val slice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+ assertEquals(slice, rank(slice, unvalidatedNonslice))
+ }
+
+ @Test
+ fun testPrimaryPolicyStrongerThanSlice() {
+ val nonslice = TestScore(score(EVER_EVALUATED),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+ val primarySlice = TestScore(score(EVER_EVALUATED, POLICY_TRANSPORT_PRIMARY),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+ assertEquals(primarySlice, rank(nonslice, primarySlice))
+ }
+
+ @Test
+ fun testPreferNonSlices() {
+ // Slices lose to non-slices for general ranking
+ val nonslice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+ val slice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+ assertEquals(nonslice, rank(slice, nonslice))
+ }
+
+ @Test
+ fun testSlicePolicyStrongerThanTransport() {
+ val nonSliceCell = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+ val sliceWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+ caps(TRANSPORT_WIFI, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+ assertEquals(nonSliceCell, rank(nonSliceCell, sliceWifi))
+ }
}