From dd55b91228bbe7a9836fe0dbdf489760299cb135 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 26 Dec 2020 17:13:22 +0800 Subject: [PATCH] [NFCT.TETHER.2] Migrate tetherOffloadRuleAdd from netd to mainline A preparation for updating BPF map in mainline module. Test: atest TetheringCoverageTests Change-Id: I6f8667bf110dae3e2805c2752ff59e7284a7bce0 --- Tethering/Android.bp | 2 + .../apishim/api30/BpfCoordinatorShimImpl.java | 67 ++++++ .../apishim/api31/BpfCoordinatorShimImpl.java | 79 ++++++++ .../apishim/common/BpfCoordinatorShim.java | 58 ++++++ .../tethering/BpfCoordinator.java | 81 ++++++-- .../unit/src/android/net/ip/IpServerTest.java | 139 +++++++++---- .../tethering/BpfCoordinatorTest.java | 191 ++++++++++++++---- 7 files changed, 525 insertions(+), 92 deletions(-) create mode 100644 Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java create mode 100644 Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java create mode 100644 Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java diff --git a/Tethering/Android.bp b/Tethering/Android.bp index 5edcfe38bd..ef9777da84 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -19,6 +19,7 @@ java_defaults { sdk_version: "module_current", min_sdk_version: "30", srcs: [ + "apishim/**/*.java", "src/**/*.java", ":framework-tethering-shared-srcs", ":tethering-module-utils-srcs", @@ -26,6 +27,7 @@ java_defaults { ], static_libs: [ "androidx.annotation_annotation", + "modules-utils-build", "netlink-client", // TODO: use networkstack-client instead of just including the AIDL interface "networkstack-aidl-interfaces-unstable-java", diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java new file mode 100644 index 0000000000..5cf0384de7 --- /dev/null +++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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.apishim.api30; + +import android.net.INetd; +import android.net.util.SharedLog; +import android.os.RemoteException; +import android.os.ServiceSpecificException; + +import androidx.annotation.NonNull; + +import com.android.networkstack.tethering.BpfCoordinator.Dependencies; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; + +/** + * Bpf coordinator class for API shims. + */ +public class BpfCoordinatorShimImpl + extends com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim { + private static final String TAG = "api30.BpfCoordinatorShimImpl"; + + @NonNull + private final SharedLog mLog; + @NonNull + private final INetd mNetd; + + public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) { + mLog = deps.getSharedLog().forSubComponent(TAG); + mNetd = deps.getNetd(); + } + + @Override + public boolean isInitialized() { + return true; + }; + + @Override + public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) { + try { + mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Could not add IPv6 forwarding rule: ", e); + return false; + } + + return true; + }; + + @Override + public String toString() { + return "Netd used"; + } +} 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 new file mode 100644 index 0000000000..03616cab1c --- /dev/null +++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 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.apishim.api31; + +import android.net.util.SharedLog; +import android.system.ErrnoException; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.networkstack.tethering.BpfCoordinator.Dependencies; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfMap; +import com.android.networkstack.tethering.TetherIngressKey; +import com.android.networkstack.tethering.TetherIngressValue; + +/** + * Bpf coordinator class for API shims. + */ +public class BpfCoordinatorShimImpl + extends com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim { + private static final String TAG = "api31.BpfCoordinatorShimImpl"; + + @NonNull + private final SharedLog mLog; + + // BPF map of ingress queueing discipline which pre-processes the packets by the IPv6 + // forwarding rules. + @Nullable + private final BpfMap mBpfIngressMap; + + public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) { + mLog = deps.getSharedLog().forSubComponent(TAG); + mBpfIngressMap = deps.getBpfIngressMap(); + } + + @Override + public boolean isInitialized() { + return mBpfIngressMap != null; + } + + @Override + public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) { + if (!isInitialized()) return false; + + final TetherIngressKey key = rule.makeTetherIngressKey(); + final TetherIngressValue value = rule.makeTetherIngressValue(); + + try { + mBpfIngressMap.updateEntry(key, value); + } catch (ErrnoException e) { + mLog.e("Could not update entry: ", e); + return false; + } + + return true; + } + + @Override + public String toString() { + return "mBpfIngressMap{" + + (mBpfIngressMap != null ? "initialized" : "not initialized") + "} " + + "}"; + } +} diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java new file mode 100644 index 0000000000..bcb644c5a6 --- /dev/null +++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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.apishim.common; + +import androidx.annotation.NonNull; + +import com.android.networkstack.tethering.BpfCoordinator.Dependencies; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; + +/** + * Bpf coordinator class for API shims. + */ +public abstract class BpfCoordinatorShim { + /** + * Get BpfCoordinatorShim object by OS build version. + */ + @NonNull + public static BpfCoordinatorShim getBpfCoordinatorShim(@NonNull final Dependencies deps) { + if (deps.isAtLeastS()) { + return new com.android.networkstack.tethering.apishim.api31.BpfCoordinatorShimImpl( + deps); + } else { + return new com.android.networkstack.tethering.apishim.api30.BpfCoordinatorShimImpl( + deps); + } + } + + /** + * Return true if this class has been initialized, otherwise return false. + */ + public abstract boolean isInitialized(); + + /** + * Adds a tethering offload rule to BPF map, or updates it if it already exists. + * + * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated + * if the input interface and destination prefix match. Otherwise, a new rule will be created. + * Note that this can be only called on handler thread. + * + * @param rule The rule to add or update. + */ + public abstract boolean tetherOffloadRuleAdd(@NonNull Ipv6ForwardingRule rule); +} + diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 20f30ea7a4..d890e088bf 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -24,6 +24,7 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; +import static android.system.OsConstants.ETH_P_IPV6; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; @@ -42,6 +43,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; +import android.system.ErrnoException; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; @@ -51,6 +53,9 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.NetworkStackConstants; +import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim; import java.net.Inet6Address; import java.util.ArrayList; @@ -71,6 +76,8 @@ import java.util.Objects; public class BpfCoordinator { private static final String TAG = BpfCoordinator.class.getSimpleName(); private static final int DUMP_TIMEOUT_MS = 10_000; + private static final String TETHER_INGRESS_FS_PATH = + "/sys/fs/bpf/map_offload_tether_ingress_map"; @VisibleForTesting enum StatsType { @@ -88,6 +95,8 @@ public class BpfCoordinator { private final Dependencies mDeps; @Nullable private final BpfTetherStatsProvider mStatsProvider; + @NonNull + private final BpfCoordinatorShim mBpfCoordinatorShim; // True if BPF offload is supported, false otherwise. The BPF offload could be disabled by // a runtime resource overlay package or device configuration. This flag is only initialized @@ -168,6 +177,28 @@ public class BpfCoordinator { /** Get tethering configuration. */ @Nullable public abstract TetheringConfiguration getTetherConfig(); + + /** + * Check OS Build at least S. + * + * TODO: move to BpfCoordinatorShim once the test doesn't need the mocked OS build for + * testing different code flows concurrently. + */ + public boolean isAtLeastS() { + // TODO: consider using ShimUtils.isAtLeastS. + return SdkLevel.isAtLeastS(); + } + + /** Get ingress BPF map. */ + @Nullable public BpfMap getBpfIngressMap() { + try { + return new BpfMap<>(TETHER_INGRESS_FS_PATH, + BpfMap.BPF_F_RDWR, TetherIngressKey.class, TetherIngressValue.class); + } catch (ErrnoException e) { + Log.e(TAG, "Cannot create ingress map: " + e); + return null; + } + } } @VisibleForTesting @@ -188,6 +219,11 @@ public class BpfCoordinator { provider = null; } mStatsProvider = provider; + + mBpfCoordinatorShim = BpfCoordinatorShim.getBpfCoordinatorShim(deps); + if (!mBpfCoordinatorShim.isInitialized()) { + mLog.e("Bpf shim not initialized"); + } } /** @@ -199,8 +235,8 @@ public class BpfCoordinator { public void startPolling() { if (mPollingStarted) return; - if (!mIsBpfEnabled) { - mLog.i("Offload disabled"); + if (!isUsingBpf()) { + mLog.i("BPF is not using"); return; } @@ -231,6 +267,10 @@ public class BpfCoordinator { mLog.i("Polling stopped"); } + private boolean isUsingBpf() { + return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized(); + } + /** * Add forwarding rule. After adding the first rule on a given upstream, must add the data * limit on the given upstream. @@ -238,15 +278,10 @@ public class BpfCoordinator { */ public void tetherOffloadRuleAdd( @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { - if (!mIsBpfEnabled) return; + if (!isUsingBpf()) return; - try { - // TODO: Perhaps avoid to add a duplicate rule. - mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Could not add IPv6 forwarding rule: ", e); - return; - } + // TODO: Perhaps avoid to add a duplicate rule. + if (!mBpfCoordinatorShim.tetherOffloadRuleAdd(rule)) return; if (!mIpv6ForwardingRules.containsKey(ipServer)) { mIpv6ForwardingRules.put(ipServer, new LinkedHashMap rules = mIpv6ForwardingRules.get( ipServer); @@ -341,7 +376,7 @@ public class BpfCoordinator { * Note that this can be only called on handler thread. */ public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) { - if (!mIsBpfEnabled) return; + if (!isUsingBpf()) return; final LinkedHashMap rules = mIpv6ForwardingRules.get( ipServer); @@ -365,7 +400,7 @@ public class BpfCoordinator { * Note that this can be only called on handler thread. */ public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) { - if (!mIsBpfEnabled) return; + if (!isUsingBpf()) return; if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return; @@ -396,6 +431,7 @@ public class BpfCoordinator { ? "registered" : "not registered")); pw.println("Upstream quota: " + mInterfaceQuotas.toString()); pw.println("Polling interval: " + getPollingInterval() + " ms"); + pw.println("Bpf shim: " + mBpfCoordinatorShim.toString()); pw.println("Forwarding stats:"); pw.increaseIndent(); @@ -497,6 +533,23 @@ public class BpfCoordinator { return parcel; } + /** + * Return a TetherIngressKey object built from the rule. + */ + @NonNull + public TetherIngressKey makeTetherIngressKey() { + return new TetherIngressKey(upstreamIfindex, address.getAddress()); + } + + /** + * Return a TetherIngressValue object built from the rule. + */ + @NonNull + public TetherIngressValue makeTetherIngressValue() { + return new TetherIngressValue(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6, + NetworkStackConstants.ETHER_MTU); + } + @Override public boolean equals(Object o) { if (!(o instanceof Ipv6ForwardingRule)) return false; diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 2eb75895ac..dae19b710b 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -36,6 +36,7 @@ import static android.net.netlink.NetlinkConstants.RTM_NEWNEIGH; import static android.net.netlink.StructNdMsg.NUD_FAILED; import static android.net.netlink.StructNdMsg.NUD_REACHABLE; import static android.net.netlink.StructNdMsg.NUD_STALE; +import static android.system.OsConstants.ETH_P_IPV6; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; @@ -98,9 +99,13 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.net.module.util.NetworkStackConstants; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfMap; import com.android.networkstack.tethering.PrivateAddressCoordinator; +import com.android.networkstack.tethering.TetherIngressKey; +import com.android.networkstack.tethering.TetherIngressValue; import com.android.networkstack.tethering.TetheringConfiguration; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; @@ -163,6 +168,7 @@ public class IpServerTest { @Mock private PrivateAddressCoordinator mAddressCoordinator; @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; + @Mock private BpfMap mBpfIngressMap; @Captor private ArgumentCaptor mDhcpParamsCaptor; @@ -173,6 +179,7 @@ public class IpServerTest { private InterfaceConfigurationParcel mInterfaceConfiguration; private NeighborEventConsumer mNeighborEventConsumer; private BpfCoordinator mBpfCoordinator; + private BpfCoordinator.Dependencies mBpfDeps; private void initStateMachine(int interfaceType) throws Exception { initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); @@ -256,8 +263,7 @@ public class IpServerTest { mTestAddress); when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */); - mBpfCoordinator = spy(new BpfCoordinator( - new BpfCoordinator.Dependencies() { + mBpfDeps = new BpfCoordinator.Dependencies() { @NonNull public Handler getHandler() { return new Handler(mLooper.getLooper()); @@ -282,7 +288,13 @@ public class IpServerTest { public TetheringConfiguration getTetherConfig() { return mTetherConfig; } - })); + + @Nullable + public BpfMap getBpfIngressMap() { + return mBpfIngressMap; + } + }; + mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps)); setUpDhcpServer(); } @@ -741,6 +753,55 @@ public class IpServerTest { (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac); } + @NonNull + private static TetherIngressKey makeIngressKey(int upstreamIfindex, + @NonNull final InetAddress dst) { + return new TetherIngressKey(upstreamIfindex, dst.getAddress()); + } + + @NonNull + private static TetherIngressValue makeIngressValue(@NonNull final MacAddress dstMac) { + return new TetherIngressValue(TEST_IFACE_PARAMS.index, dstMac, TEST_IFACE_PARAMS.macAddr, + ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); + } + + private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) { + if (inOrder != null) { + return inOrder.verify(t); + } else { + return verify(t); + } + } + + private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex, + @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception { + if (mBpfDeps.isAtLeastS()) { + verifyWithOrder(inOrder, mBpfIngressMap).updateEntry( + makeIngressKey(upstreamIfindex, dst), makeIngressValue(dstMac)); + } else { + verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, + dstMac)); + } + } + + private void verifyNeverTetherOffloadRuleAdd(int upstreamIfindex, + @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception { + if (mBpfDeps.isAtLeastS()) { + verify(mBpfIngressMap, never()).updateEntry(makeIngressKey(upstreamIfindex, dst), + makeIngressValue(dstMac)); + } else { + verify(mNetd, never()).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac)); + } + } + + private void verifyNeverTetherOffloadRuleAdd() throws Exception { + if (mBpfDeps.isAtLeastS()) { + verify(mBpfIngressMap, never()).updateEntry(any(), any()); + } else { + verify(mNetd, never()).tetherOffloadRuleAdd(any()); + } + } + @NonNull private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { TetherStatsParcel parcel = new TetherStatsParcel(); @@ -748,8 +809,8 @@ public class IpServerTest { return parcel; } - private void resetNetdAndBpfCoordinator() throws Exception { - reset(mNetd, mBpfCoordinator); + private void resetNetdBpfMapAndCoordinator() throws Exception { + reset(mNetd, mBpfIngressMap, mBpfCoordinator); when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX)) .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX)); @@ -774,34 +835,34 @@ public class IpServerTest { final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b"); - resetNetdAndBpfCoordinator(); - verifyNoMoreInteractions(mBpfCoordinator, mNetd); + resetNetdBpfMapAndCoordinator(); + verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap); // TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and // tetherOffloadGetAndClearStats in netd while the rules are changed. // Events on other interfaces are ignored. recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap); // Events on this interface are received and sent to netd. recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); - resetNetdAndBpfCoordinator(); + verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA); + resetNetdBpfMapAndCoordinator(); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); - resetNetdAndBpfCoordinator(); + verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB); + resetNetdBpfMapAndCoordinator(); // Link-local and multicast neighbors are ignored. recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap); recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap); // A neighbor that is no longer valid causes the rule to be removed. // NUD_FAILED events do not have a MAC address. @@ -809,30 +870,30 @@ public class IpServerTest { verify(mBpfCoordinator).tetherOffloadRuleRemove( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macNull)); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); // A neighbor that is deleted causes the rule to be removed. recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); verify(mBpfCoordinator).tetherOffloadRuleRemove( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macNull)); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); // Upstream changes result in updating the rules. recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); - InOrder inOrder = inOrder(mNetd); + InOrder inOrder = inOrder(mNetd, mBpfIngressMap); LinkProperties lp = new LinkProperties(); lp.setInterfaceName(UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1); verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA)); + verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighA, macA); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB)); - resetNetdAndBpfCoordinator(); + verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighB, macB); + resetNetdBpfMapAndCoordinator(); // When the upstream is lost, rules are removed. dispatchTetherConnectionChanged(null, null, 0); @@ -843,15 +904,15 @@ public class IpServerTest { verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB)); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); // If the upstream is IPv4-only, no rules are added. dispatchTetherConnectionChanged(UPSTREAM_IFACE); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); // Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost. verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); - verifyNoMoreInteractions(mBpfCoordinator, mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap); // Rules can be added again once upstream IPv6 connectivity is available. lp.setInterfaceName(UPSTREAM_IFACE); @@ -859,13 +920,13 @@ public class IpServerTest { recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); + verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB); verify(mBpfCoordinator, never()).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); + verifyNeverTetherOffloadRuleAdd(UPSTREAM_IFINDEX, neighA, macA); // If upstream IPv6 connectivity is lost, rules are removed. - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); @@ -877,11 +938,11 @@ public class IpServerTest { recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); + verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); - resetNetdAndBpfCoordinator(); + verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB); + resetNetdBpfMapAndCoordinator(); mIpServer.stop(); mLooper.dispatchAll(); @@ -889,7 +950,7 @@ public class IpServerTest { verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); verify(mIpNeighborMonitor).stop(); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); } @Test @@ -910,35 +971,35 @@ public class IpServerTest { // A neighbor that is added or deleted causes the rule to be added or removed. initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, true /* usingBpfOffload */); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA)); - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA)); - resetNetdAndBpfCoordinator(); + verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neigh, macA); + resetNetdBpfMapAndCoordinator(); recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); verify(mBpfCoordinator).tetherOffloadRuleRemove( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull)); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); // [2] Disable BPF offload. // A neighbor that is added or deleted doesn’t cause the rule to be added or removed. initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, false /* usingBpfOffload */); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any()); - verify(mNetd, never()).tetherOffloadRuleAdd(any()); - resetNetdAndBpfCoordinator(); + verifyNeverTetherOffloadRuleAdd(); + resetNetdBpfMapAndCoordinator(); recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any()); verify(mNetd, never()).tetherOffloadRuleRemove(any()); - resetNetdAndBpfCoordinator(); + resetNetdBpfMapAndCoordinator(); } @Test 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 64242ae825..b920fa8b67 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -24,6 +24,7 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; +import static android.system.OsConstants.ETH_P_IPV6; import static com.android.networkstack.tethering.BpfCoordinator.StatsType; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; @@ -33,6 +34,7 @@ import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -40,8 +42,10 @@ import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,6 +58,7 @@ import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; import android.net.ip.IpServer; import android.net.util.SharedLog; +import android.os.Build; import android.os.Handler; import android.os.test.TestLooper; @@ -62,7 +67,9 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.net.module.util.NetworkStackConstants; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.TestableNetworkStatsProviderCbBinder; import org.junit.Before; @@ -94,6 +101,7 @@ public class BpfCoordinatorTest { @Mock private INetd mNetd; @Mock private IpServer mIpServer; @Mock private TetheringConfiguration mTetherConfig; + @Mock private BpfMap mBpfIngressMap; // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; @@ -102,32 +110,37 @@ public class BpfCoordinatorTest { ArgumentCaptor.forClass(ArrayList.class); private final TestLooper mTestLooper = new TestLooper(); private BpfCoordinator.Dependencies mDeps = - new BpfCoordinator.Dependencies() { - @NonNull - public Handler getHandler() { - return new Handler(mTestLooper.getLooper()); - } + spy(new BpfCoordinator.Dependencies() { + @NonNull + public Handler getHandler() { + return new Handler(mTestLooper.getLooper()); + } - @NonNull - public INetd getNetd() { - return mNetd; - } + @NonNull + public INetd getNetd() { + return mNetd; + } - @NonNull - public NetworkStatsManager getNetworkStatsManager() { - return mStatsManager; - } + @NonNull + public NetworkStatsManager getNetworkStatsManager() { + return mStatsManager; + } - @NonNull - public SharedLog getSharedLog() { - return new SharedLog("test"); - } + @NonNull + public SharedLog getSharedLog() { + return new SharedLog("test"); + } - @Nullable - public TetheringConfiguration getTetherConfig() { - return mTetherConfig; - } - }; + @Nullable + public TetheringConfiguration getTetherConfig() { + return mTetherConfig; + } + + @Nullable + public BpfMap getBpfIngressMap() { + return mBpfIngressMap; + } + }); @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -185,6 +198,63 @@ public class BpfCoordinatorTest { waitForIdle(); } + private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) { + if (inOrder != null) { + return inOrder.verify(t); + } else { + return verify(t); + } + } + + private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, + @NonNull Ipv6ForwardingRule rule) throws Exception { + if (mDeps.isAtLeastS()) { + verifyWithOrder(inOrder, mBpfIngressMap).updateEntry( + rule.makeTetherIngressKey(), rule.makeTetherIngressValue()); + } else { + verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(rule)); + } + } + + private void verifyNeverTetherOffloadRuleAdd() throws Exception { + if (mDeps.isAtLeastS()) { + verify(mBpfIngressMap, never()).updateEntry(any(), any()); + } else { + verify(mNetd, never()).tetherOffloadRuleAdd(any()); + } + } + + // TODO: remove once presubmit tests on R even the code is submitted on S. + private void checkTetherOffloadRuleAdd(boolean usingApiS) throws Exception { + setupFunctioningNetdInterface(); + + // Replace Dependencies#isAtLeastS() for testing R and S+ BPF map apis. Note that |mDeps| + // must be mocked before calling #makeBpfCoordinator which use |mDeps| to initialize the + // coordinator. + doReturn(usingApiS).when(mDeps).isAtLeastS(); + final BpfCoordinator coordinator = makeBpfCoordinator(); + + final String mobileIface = "rmnet_data0"; + final Integer mobileIfIndex = 100; + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + coordinator.tetherOffloadRuleAdd(mIpServer, rule); + verifyTetherOffloadRuleAdd(null, rule); + } + + // TODO: remove once presubmit tests on R even the code is submitted on S. + @Test + public void testTetherOffloadRuleAddSdkR() throws Exception { + checkTetherOffloadRuleAdd(false /* R */); + } + + // TODO: remove once presubmit tests on R even the code is submitted on S. + @Test + public void testTetherOffloadRuleAddAtLeastSdkS() throws Exception { + checkTetherOffloadRuleAdd(true /* S+ */); + } + @Test public void testGetForwardedStats() throws Exception { setupFunctioningNetdInterface(); @@ -338,6 +408,33 @@ public class BpfCoordinatorTest { DOWNSTREAM_MAC, dstMac); } + @Test + public void testRuleMakeTetherIngressKey() throws Exception { + final Integer mobileIfIndex = 100; + final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + + final TetherIngressKey key = rule.makeTetherIngressKey(); + assertEquals(key.iif, (long) mobileIfIndex); + assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress())); + // iif (4) + neigh6 (16) = 20. + assertEquals(20, key.writeToBytes().length); + } + + @Test + public void testRuleMakeTetherIngressValue() throws Exception { + final Integer mobileIfIndex = 100; + final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + + final TetherIngressValue value = rule.makeTetherIngressValue(); + assertEquals(value.oif, DOWNSTREAM_IFINDEX); + assertEquals(value.ethDstMac, MAC_A); + assertEquals(value.ethSrcMac, DOWNSTREAM_MAC); + assertEquals(value.ethProto, ETH_P_IPV6); + assertEquals(value.pmtu, NetworkStackConstants.ETHER_MTU); + // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20. + assertEquals(20, value.writeToBytes().length); + } + @Test public void testSetDataLimit() throws Exception { setupFunctioningNetdInterface(); @@ -352,9 +449,9 @@ public class BpfCoordinatorTest { // 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 Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); - final InOrder inOrder = inOrder(mNetd); + final InOrder inOrder = inOrder(mNetd, mBpfIngressMap); coordinator.tetherOffloadRuleAdd(mIpServer, rule); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(rule)); + verifyTetherOffloadRuleAdd(inOrder, rule); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); inOrder.verifyNoMoreInteractions(); @@ -393,7 +490,7 @@ public class BpfCoordinatorTest { // 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); + final InOrder inOrder = inOrder(mNetd, mBpfIngressMap); mTetherStatsProvider.onSetLimit(mobileIface, limit); waitForIdle(); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); @@ -401,14 +498,14 @@ public class BpfCoordinatorTest { // Adding the first rule on current upstream immediately sends the quota to netd. final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); coordinator.tetherOffloadRuleAdd(mIpServer, ruleA); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleA)); + verifyTetherOffloadRuleAdd(inOrder, ruleA); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, limit); inOrder.verifyNoMoreInteractions(); // Adding the second rule on current upstream does not send the quota to netd. final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B); coordinator.tetherOffloadRuleAdd(mIpServer, ruleB); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleB)); + verifyTetherOffloadRuleAdd(inOrder, ruleB); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Removing the second rule on current upstream does not send the quota to netd. @@ -438,7 +535,7 @@ public class BpfCoordinatorTest { coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface); coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); - final InOrder inOrder = inOrder(mNetd); + final InOrder inOrder = inOrder(mNetd, mBpfIngressMap); // Before the rule test, here are the additional actions while the rules are changed. // - After adding the first rule on a given upstream, the coordinator adds a data limit. @@ -455,11 +552,11 @@ public class BpfCoordinatorTest { ethIfIndex, NEIGH_B, MAC_B); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleA)); + verifyTetherOffloadRuleAdd(inOrder, ethernetRuleA); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(ethIfIndex, QUOTA_UNLIMITED); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleB)); + verifyTetherOffloadRuleAdd(inOrder, ethernetRuleB); // [2] Update the existing rules from Ethernet to cellular. final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule( @@ -473,11 +570,11 @@ public class BpfCoordinatorTest { // by one for updating upstream interface index by #tetherOffloadRuleUpdate. coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleA)); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleA)); + verifyTetherOffloadRuleAdd(inOrder, mobileRuleA); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleB)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(ethIfIndex); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleB)); + verifyTetherOffloadRuleAdd(inOrder, mobileRuleB); // [3] Clear all rules for a given IpServer. when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) @@ -499,11 +596,10 @@ public class BpfCoordinatorTest { .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 50, 60, 70, 80))); } - @Test - public void testTetheringConfigDisable() throws Exception { - setupFunctioningNetdInterface(); - when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); - + private void checkBpfDisabled() throws Exception { + // The caller may mock the global dependencies |mDeps| which is used in + // #makeBpfCoordinator for testing. + // See #testBpfDisabledbyNoBpfIngressMap. final BpfCoordinator coordinator = makeBpfCoordinator(); coordinator.startPolling(); @@ -523,7 +619,7 @@ public class BpfCoordinatorTest { final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a"); final Ipv6ForwardingRule rule = buildTestForwardingRule(ifIndex, neigh, mac); coordinator.tetherOffloadRuleAdd(mIpServer, rule); - verify(mNetd, never()).tetherOffloadRuleAdd(any()); + verifyNeverTetherOffloadRuleAdd(); LinkedHashMap rules = coordinator.getForwardingRulesForTesting().get(mIpServer); assertNull(rules); @@ -550,12 +646,29 @@ public class BpfCoordinatorTest { // The rule can't be updated. coordinator.tetherOffloadRuleUpdate(mIpServer, rule.upstreamIfindex + 1 /* new */); verify(mNetd, never()).tetherOffloadRuleRemove(any()); - verify(mNetd, never()).tetherOffloadRuleAdd(any()); + verifyNeverTetherOffloadRuleAdd(); rules = coordinator.getForwardingRulesForTesting().get(mIpServer); assertNotNull(rules); assertEquals(1, rules.size()); } + @Test + public void testBpfDisabledbyConfig() throws Exception { + setupFunctioningNetdInterface(); + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); + + checkBpfDisabled(); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.R) + public void testBpfDisabledbyNoBpfIngressMap() throws Exception { + setupFunctioningNetdInterface(); + doReturn(null).when(mDeps).getBpfIngressMap(); + + checkBpfDisabled(); + } + @Test public void testTetheringConfigSetPollingInterval() throws Exception { setupFunctioningNetdInterface();