Merge "bpf: Add interface index to BPF devmap"
This commit is contained in:
@@ -178,6 +178,18 @@ public class BpfCoordinatorShimImpl
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDevMap(int ifIndex) {
|
||||
/* no op */
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeDevMap(int ifIndex) {
|
||||
/* no op */
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Netd used";
|
||||
|
||||
@@ -36,6 +36,8 @@ import com.android.networkstack.tethering.BpfUtils;
|
||||
import com.android.networkstack.tethering.Tether4Key;
|
||||
import com.android.networkstack.tethering.Tether4Value;
|
||||
import com.android.networkstack.tethering.Tether6Value;
|
||||
import com.android.networkstack.tethering.TetherDevKey;
|
||||
import com.android.networkstack.tethering.TetherDevValue;
|
||||
import com.android.networkstack.tethering.TetherDownstream6Key;
|
||||
import com.android.networkstack.tethering.TetherLimitKey;
|
||||
import com.android.networkstack.tethering.TetherLimitValue;
|
||||
@@ -85,6 +87,10 @@ public class BpfCoordinatorShimImpl
|
||||
@Nullable
|
||||
private final BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
|
||||
|
||||
// BPF map of interface index mapping for XDP.
|
||||
@Nullable
|
||||
private final BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
|
||||
|
||||
// Tracking IPv4 rule count while any rule is using the given upstream interfaces. Used for
|
||||
// reducing the BPF map iteration query. The count is increased or decreased when the rule is
|
||||
// added or removed successfully on mBpfDownstream4Map. Counting the rules on downstream4 map
|
||||
@@ -108,6 +114,7 @@ public class BpfCoordinatorShimImpl
|
||||
mBpfUpstream6Map = deps.getBpfUpstream6Map();
|
||||
mBpfStatsMap = deps.getBpfStatsMap();
|
||||
mBpfLimitMap = deps.getBpfLimitMap();
|
||||
mBpfDevMap = deps.getBpfDevMap();
|
||||
|
||||
// Clear the stubs of the maps for handling the system service crash if any.
|
||||
// Doesn't throw the exception and clear the stubs as many as possible.
|
||||
@@ -141,12 +148,18 @@ public class BpfCoordinatorShimImpl
|
||||
} catch (ErrnoException e) {
|
||||
mLog.e("Could not clear mBpfLimitMap: " + e);
|
||||
}
|
||||
try {
|
||||
if (mBpfDevMap != null) mBpfDevMap.clear();
|
||||
} catch (ErrnoException e) {
|
||||
mLog.e("Could not clear mBpfDevMap: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return mBpfDownstream4Map != null && mBpfUpstream4Map != null && mBpfDownstream6Map != null
|
||||
&& mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null;
|
||||
&& mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null
|
||||
&& mBpfDevMap != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -432,6 +445,32 @@ public class BpfCoordinatorShimImpl
|
||||
return mRule4CountOnUpstream.get(ifIndex) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDevMap(int ifIndex) {
|
||||
if (!isInitialized()) return false;
|
||||
|
||||
try {
|
||||
mBpfDevMap.updateEntry(new TetherDevKey(ifIndex), new TetherDevValue(ifIndex));
|
||||
} catch (ErrnoException e) {
|
||||
mLog.e("Could not add interface " + ifIndex + ": " + e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeDevMap(int ifIndex) {
|
||||
if (!isInitialized()) return false;
|
||||
|
||||
try {
|
||||
mBpfDevMap.deleteEntry(new TetherDevKey(ifIndex));
|
||||
} catch (ErrnoException e) {
|
||||
mLog.e("Could not delete interface " + ifIndex + ": " + e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String mapStatus(BpfMap m, String name) {
|
||||
return name + "{" + (m != null ? "OK" : "ERROR") + "}";
|
||||
}
|
||||
@@ -444,7 +483,8 @@ public class BpfCoordinatorShimImpl
|
||||
mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"),
|
||||
mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"),
|
||||
mapStatus(mBpfStatsMap, "mBpfStatsMap"),
|
||||
mapStatus(mBpfLimitMap, "mBpfLimitMap")
|
||||
mapStatus(mBpfLimitMap, "mBpfLimitMap"),
|
||||
mapStatus(mBpfDevMap, "mBpfDevMap")
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -166,5 +166,15 @@ public abstract class BpfCoordinatorShim {
|
||||
* TODO: consider using InterfaceParams to replace interface name.
|
||||
*/
|
||||
public abstract boolean detachProgram(@NonNull String iface);
|
||||
|
||||
/**
|
||||
* Add interface index mapping.
|
||||
*/
|
||||
public abstract boolean addDevMap(int ifIndex);
|
||||
|
||||
/**
|
||||
* Remove interface index mapping.
|
||||
*/
|
||||
public abstract boolean removeDevMap(int ifIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -767,8 +767,7 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", AID_ROOT, AID
|
||||
|
||||
// ----- XDP Support -----
|
||||
|
||||
DEFINE_BPF_MAP_GRW(tether_xdp_devmap, DEVMAP_HASH, uint32_t, uint32_t, 64,
|
||||
AID_NETWORK_STACK)
|
||||
DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, AID_NETWORK_STACK)
|
||||
|
||||
static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const bool is_ethernet,
|
||||
const bool downstream) {
|
||||
|
||||
@@ -72,6 +72,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -104,6 +105,7 @@ public class BpfCoordinator {
|
||||
private static final String TETHER_STATS_MAP_PATH = makeMapPath("stats");
|
||||
private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
|
||||
private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
|
||||
private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
|
||||
|
||||
/** The names of all the BPF counters defined in bpf_tethering.h. */
|
||||
public static final String[] sBpfCounterNames = getBpfCounterNames();
|
||||
@@ -220,6 +222,11 @@ public class BpfCoordinator {
|
||||
// Map for upstream and downstream pair.
|
||||
private final HashMap<String, HashSet<String>> mForwardingPairs = new HashMap<>();
|
||||
|
||||
// Set for upstream and downstream device map. Used for caching BPF dev map status and
|
||||
// reduce duplicate adding or removing map operations. Use LinkedHashSet because the test
|
||||
// BpfCoordinatorTest needs predictable iteration order.
|
||||
private final Set<Integer> mDeviceMapSet = new LinkedHashSet<>();
|
||||
|
||||
// Runnable that used by scheduling next polling of stats.
|
||||
private final Runnable mScheduledPollingTask = () -> {
|
||||
updateForwardedStats();
|
||||
@@ -336,6 +343,18 @@ public class BpfCoordinator {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get dev BPF map. */
|
||||
@Nullable public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
|
||||
if (!isAtLeastS()) return null;
|
||||
try {
|
||||
return new BpfMap<>(TETHER_DEV_MAP_PATH,
|
||||
BpfMap.BPF_F_RDWR, TetherDevKey.class, TetherDevValue.class);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Cannot create dev map: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -490,6 +509,9 @@ public class BpfCoordinator {
|
||||
}
|
||||
LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.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);
|
||||
|
||||
@@ -758,6 +780,11 @@ public class BpfCoordinator {
|
||||
dumpIpv4ForwardingRules(pw);
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println("Device map:");
|
||||
pw.increaseIndent();
|
||||
dumpDevmap(pw);
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println();
|
||||
pw.println("Forwarding counters:");
|
||||
pw.increaseIndent();
|
||||
@@ -890,6 +917,31 @@ public class BpfCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpDevmap(@NonNull IndentingPrintWriter pw) {
|
||||
try (BpfMap<TetherDevKey, TetherDevValue> map = mDeps.getBpfDevMap()) {
|
||||
if (map == null) {
|
||||
pw.println("No devmap support");
|
||||
return;
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
pw.println("No interface index");
|
||||
return;
|
||||
}
|
||||
pw.println("ifindex (iface) -> ifindex (iface)");
|
||||
pw.increaseIndent();
|
||||
map.forEach((k, v) -> {
|
||||
// Only get upstream interface name. Just do the best to make the index readable.
|
||||
// TODO: get downstream interface name because the index is either upstrema or
|
||||
// downstream interface in dev map.
|
||||
pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
|
||||
v.ifIndex, getIfName(v.ifIndex)));
|
||||
});
|
||||
} catch (ErrnoException e) {
|
||||
pw.println("Error dumping dev map: " + e);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
/** IPv6 forwarding rule class. */
|
||||
public static class Ipv6ForwardingRule {
|
||||
// The upstream6 and downstream6 rules are built as the following tables. Only raw ip
|
||||
@@ -1229,6 +1281,7 @@ public class BpfCoordinator {
|
||||
final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
|
||||
upstreamIndex);
|
||||
|
||||
maybeAddDevMap(upstreamIndex, tetherClient.downstreamIfindex);
|
||||
maybeSetLimit(upstreamIndex);
|
||||
mBpfCoordinatorShim.tetherOffloadRuleAdd(UPSTREAM, upstream4Key, upstream4Value);
|
||||
mBpfCoordinatorShim.tetherOffloadRuleAdd(DOWNSTREAM, downstream4Key, downstream4Value);
|
||||
@@ -1357,6 +1410,15 @@ public class BpfCoordinator {
|
||||
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) {
|
||||
for (Integer index : new Integer[] {upstreamIfindex, downstreamIfindex}) {
|
||||
if (mDeviceMapSet.contains(index)) continue;
|
||||
if (mBpfCoordinatorShim.addDevMap(index)) mDeviceMapSet.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void forwardingPairAdd(@NonNull String intIface, @NonNull String extIface) {
|
||||
if (!mForwardingPairs.containsKey(extIface)) {
|
||||
mForwardingPairs.put(extIface, new HashSet<String>());
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.networkstack.tethering;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/** The key of BpfMap which is used for mapping interface index. */
|
||||
public class TetherDevKey extends Struct {
|
||||
@Field(order = 0, type = Type.U32)
|
||||
public final long ifIndex; // interface index
|
||||
|
||||
public TetherDevKey(final long ifIndex) {
|
||||
this.ifIndex = ifIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.networkstack.tethering;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/** The key of BpfMap which is used for mapping interface index. */
|
||||
public class TetherDevValue extends Struct {
|
||||
@Field(order = 0, type = Type.U32)
|
||||
public final long ifIndex; // interface index
|
||||
|
||||
public TetherDevValue(final long ifIndex) {
|
||||
this.ifIndex = ifIndex;
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,8 @@ import com.android.networkstack.tethering.PrivateAddressCoordinator;
|
||||
import com.android.networkstack.tethering.Tether4Key;
|
||||
import com.android.networkstack.tethering.Tether4Value;
|
||||
import com.android.networkstack.tethering.Tether6Value;
|
||||
import com.android.networkstack.tethering.TetherDevKey;
|
||||
import com.android.networkstack.tethering.TetherDevValue;
|
||||
import com.android.networkstack.tethering.TetherDownstream6Key;
|
||||
import com.android.networkstack.tethering.TetherLimitKey;
|
||||
import com.android.networkstack.tethering.TetherLimitValue;
|
||||
@@ -182,6 +184,7 @@ public class IpServerTest {
|
||||
@Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
|
||||
@Mock private BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
|
||||
@Mock private BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
|
||||
@Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
|
||||
|
||||
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
|
||||
|
||||
@@ -334,6 +337,11 @@ public class IpServerTest {
|
||||
public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
|
||||
return mBpfLimitMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
|
||||
return mBpfDevMap;
|
||||
}
|
||||
};
|
||||
mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
|
||||
|
||||
|
||||
@@ -207,6 +207,7 @@ public class BpfCoordinatorTest {
|
||||
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
|
||||
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
|
||||
@Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
|
||||
@Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
|
||||
|
||||
// Late init since methods must be called by the thread that created this object.
|
||||
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
|
||||
@@ -284,6 +285,11 @@ public class BpfCoordinatorTest {
|
||||
public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
|
||||
return mBpfLimitMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
|
||||
return mBpfDevMap;
|
||||
}
|
||||
});
|
||||
|
||||
@Before public void setUp() {
|
||||
@@ -1368,12 +1374,9 @@ public class BpfCoordinatorTest {
|
||||
coordinator.tetherOffloadClientAdd(mIpServer, clientInfo);
|
||||
}
|
||||
|
||||
// TODO: Test the IPv4 and IPv6 exist concurrently.
|
||||
// TODO: Test the IPv4 rule delete failed.
|
||||
@Test
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public void testSetDataLimitOnRule4Change() throws Exception {
|
||||
final BpfCoordinator coordinator = makeBpfCoordinator();
|
||||
private void initBpfCoordinatorForRule4(final BpfCoordinator coordinator) throws Exception {
|
||||
// Needed because addUpstreamIfindexToMap only updates upstream information when polling
|
||||
// was started.
|
||||
coordinator.startPolling();
|
||||
|
||||
// Needed because tetherOffloadRuleRemove of api31.BpfCoordinatorShimImpl only decreases
|
||||
@@ -1387,6 +1390,15 @@ public class BpfCoordinatorTest {
|
||||
coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
|
||||
setUpstreamInformationTo(coordinator);
|
||||
setDownstreamAndClientInformationTo(coordinator);
|
||||
}
|
||||
|
||||
// TODO: Test the IPv4 and IPv6 exist concurrently.
|
||||
// TODO: Test the IPv4 rule delete failed.
|
||||
@Test
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public void testSetDataLimitOnRule4Change() throws Exception {
|
||||
final BpfCoordinator coordinator = makeBpfCoordinator();
|
||||
initBpfCoordinatorForRule4(coordinator);
|
||||
|
||||
// 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.
|
||||
@@ -1445,4 +1457,41 @@ public class BpfCoordinatorTest {
|
||||
verifyTetherOffloadGetAndClearStats(inOrder, UPSTREAM_IFINDEX);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public void testAddDevMapRule6() throws Exception {
|
||||
final BpfCoordinator coordinator = makeBpfCoordinator();
|
||||
|
||||
coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
|
||||
final Ipv6ForwardingRule ruleA = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
|
||||
final Ipv6ForwardingRule ruleB = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B);
|
||||
|
||||
coordinator.tetherOffloadRuleAdd(mIpServer, ruleA);
|
||||
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.tetherOffloadRuleAdd(mIpServer, ruleB);
|
||||
verify(mBpfDevMap, never()).updateEntry(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public void testAddDevMapRule4() throws Exception {
|
||||
final BpfCoordinator coordinator = makeBpfCoordinator();
|
||||
initBpfCoordinatorForRule4(coordinator);
|
||||
|
||||
mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP));
|
||||
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);
|
||||
|
||||
mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP));
|
||||
verify(mBpfDevMap, never()).updateEntry(any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user