diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 98b624b263..a7064e8fed 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -35,15 +35,9 @@ import static android.net.ip.IpServer.STATE_LOCAL_ONLY; import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.ip.IpServer.STATE_UNAVAILABLE; import static android.net.ip.IpServer.getTetherableIpv6Prefixes; -import static android.system.OsConstants.ETH_P_IPV6; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; -import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; -import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH; -import static com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED; -import static com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE; -import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -57,7 +51,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -66,7 +59,6 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -83,8 +75,6 @@ import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; import android.net.RoutingCoordinatorManager; -import android.net.TetherOffloadRuleParcel; -import android.net.TetherStatsParcel; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpEventCallbacks; @@ -96,40 +86,17 @@ import android.os.Handler; import android.os.RemoteException; import android.os.test.TestLooper; import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.modules.utils.build.SdkLevel; -import com.android.net.module.util.BpfMap; import com.android.net.module.util.InterfaceParams; -import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; -import com.android.net.module.util.Struct.S32; -import com.android.net.module.util.bpf.Tether4Key; -import com.android.net.module.util.bpf.Tether4Value; -import com.android.net.module.util.bpf.TetherStatsKey; -import com.android.net.module.util.bpf.TetherStatsValue; -import com.android.net.module.util.ip.ConntrackMonitor; import com.android.net.module.util.ip.IpNeighborMonitor; -import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent; -import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer; import com.android.networkstack.tethering.BpfCoordinator; -import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; -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; -import com.android.networkstack.tethering.TetherUpstream6Key; import com.android.networkstack.tethering.TetheringConfiguration; import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.networkstack.tethering.util.InterfaceSet; @@ -143,18 +110,13 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.verification.VerificationMode; import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.List; import java.util.Set; @@ -219,16 +181,8 @@ public class IpServerTest { new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null); @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; - @Mock private ConntrackMonitor mConntrackMonitor; @Mock private TetheringMetrics mTetheringMetrics; - @Mock private BpfMap mBpfDownstream4Map; - @Mock private BpfMap mBpfUpstream4Map; - @Mock private BpfMap mBpfDownstream6Map; - @Mock private BpfMap mBpfUpstream6Map; - @Mock private BpfMap mBpfStatsMap; - @Mock private BpfMap mBpfLimitMap; - @Mock private BpfMap mBpfDevMap; - @Mock private BpfMap mBpfErrorMap; + @Mock private BpfCoordinator mBpfCoordinator; @Captor private ArgumentCaptor mDhcpParamsCaptor; @@ -238,9 +192,6 @@ public class IpServerTest { ArgumentCaptor.forClass(LinkProperties.class); private IpServer mIpServer; 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); @@ -262,22 +213,20 @@ public class IpServerTest { mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH; } - ArgumentCaptor neighborCaptor = - ArgumentCaptor.forClass(NeighborEventConsumer.class); - doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), - neighborCaptor.capture()); + doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), any()); when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload); when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp); when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH); + when(mBpfCoordinator.isUsingBpfOffload()).thenReturn(usingBpfOffload); mIpServer = createIpServer(interfaceType); + verify(mIpNeighborMonitor).start(); mIpServer.start(); - mNeighborEventConsumer = neighborCaptor.getValue(); // Starting the state machine always puts us in a consistent state and notifies // the rest of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); - reset(mNetd, mCallback); + reset(mNetd, mCallback, mIpNeighborMonitor); when(mRaDaemon.start()).thenReturn(true); } @@ -294,23 +243,17 @@ public class IpServerTest { initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); if (upstreamIface != null) { + InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface); + assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams); LinkProperties lp = new LinkProperties(); lp.setInterfaceName(upstreamIface); lp.setLinkAddresses(upstreamAddresses); dispatchTetherConnectionChanged(upstreamIface, lp, 0); - if (usingBpfOffload && !lp.getLinkAddresses().isEmpty()) { - Set upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses()); - InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface); - assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams); - verify(mBpfCoordinator).updateAllIpv6Rules( - mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes); - verifyStartUpstreamIpv6Forwarding(null, interfaceParams.index, upstreamPrefixes); - } else { - verifyNoUpstreamIpv6ForwardingChange(null); - } + Set upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses()); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes); } - reset(mCallback, mAddressCoordinator); - resetNetdBpfMapAndCoordinator(); + reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator); when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(), anyBoolean())).thenReturn(mTestAddress); } @@ -363,80 +306,6 @@ public class IpServerTest { private IpServer createIpServer(final int interfaceType) { mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); - mBpfDeps = new BpfCoordinator.Dependencies() { - @NonNull - public Handler getHandler() { - return mHandler; - } - - @NonNull - public INetd getNetd() { - return mNetd; - } - - @NonNull - public NetworkStatsManager getNetworkStatsManager() { - return mStatsManager; - } - - @NonNull - public SharedLog getSharedLog() { - return mSharedLog; - } - - @Nullable - public TetheringConfiguration getTetherConfig() { - return mTetherConfig; - } - - @NonNull - public ConntrackMonitor getConntrackMonitor( - ConntrackMonitor.ConntrackEventConsumer consumer) { - return mConntrackMonitor; - } - - @Nullable - public BpfMap getBpfDownstream4Map() { - return mBpfDownstream4Map; - } - - @Nullable - public BpfMap getBpfUpstream4Map() { - return mBpfUpstream4Map; - } - - @Nullable - public BpfMap getBpfDownstream6Map() { - return mBpfDownstream6Map; - } - - @Nullable - public BpfMap getBpfUpstream6Map() { - return mBpfUpstream6Map; - } - - @Nullable - public BpfMap getBpfStatsMap() { - return mBpfStatsMap; - } - - @Nullable - public BpfMap getBpfLimitMap() { - return mBpfLimitMap; - } - - @Nullable - public BpfMap getBpfDevMap() { - return mBpfDevMap; - } - - @Nullable - public BpfMap getBpfErrorMap() { - return mBpfErrorMap; - } - }; - - mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps)); return new IpServer(IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator, mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies); @@ -886,346 +755,19 @@ public class IpServerTest { verify(mDependencies, never()).makeDhcpServer(any(), any(), any()); } - private InetAddress addr(String addr) throws Exception { - return InetAddresses.parseNumericAddress(addr); - } - - private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { - mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr, - nudState, mac)); - mLooper.dispatchAll(); - } - - private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { - mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr, - nudState, mac)); - mLooper.dispatchAll(); - } - - /** - * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable - * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as: - * - * private void checkFooCalled(StableParcelable p, ...) { - * ArgumentCaptor captor = ArgumentCaptor.forClass(FooParam.class); - * verify(mMock).foo(captor.capture()); - * Foo foo = captor.getValue(); - * assertFooMatchesExpectations(foo); - * } - * - * almost works, but not quite. This is because if the code under test calls foo() twice, the - * first call to checkFooCalled() matches both the calls, putting both calls into the captor, - * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito - * features such as never(), inOrder(), etc. - * - * This approach isn't great because if the match fails, the error message is unhelpful - * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does - * work. - * - * TODO: consider making the error message more readable by adding a method that catching the - * AssertionFailedError and throwing a new assertion with more details. See - * NetworkMonitorTest#verifyNetworkTested. - * - * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the - * TooManyActualInvocations problem described above by forcing the caller of the custom assert - * method to specify all expected invocations in one call. This is useful when the stable - * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and - * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here - * because there is no such object. - */ - private static class TetherOffloadRuleParcelMatcher implements - ArgumentMatcher { - public final int upstreamIfindex; - public final InetAddress dst; - public final MacAddress dstMac; - - TetherOffloadRuleParcelMatcher(int upstreamIfindex, InetAddress dst, MacAddress dstMac) { - this.upstreamIfindex = upstreamIfindex; - this.dst = dst; - this.dstMac = dstMac; - } - - public boolean matches(TetherOffloadRuleParcel parcel) { - return upstreamIfindex == parcel.inputInterfaceIndex - && (TEST_IFACE_PARAMS.index == parcel.outputInterfaceIndex) - && Arrays.equals(dst.getAddress(), parcel.destination) - && (128 == parcel.prefixLength) - && Arrays.equals(TEST_IFACE_PARAMS.macAddr.toByteArray(), parcel.srcL2Address) - && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); - } - - public String toString() { - return String.format("TetherOffloadRuleParcelMatcher(%d, %s, %s", - upstreamIfindex, dst.getHostAddress(), dstMac); - } - } - - @NonNull - private static TetherOffloadRuleParcel matches( - int upstreamIfindex, InetAddress dst, MacAddress dstMac) { - return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac)); - } - - @NonNull - private static Ipv6DownstreamRule makeDownstreamRule(int upstreamIfindex, - @NonNull InetAddress dst, @NonNull MacAddress dstMac) { - return new Ipv6DownstreamRule(upstreamIfindex, TEST_IFACE_PARAMS.index, - (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac); - } - - @NonNull - private static TetherDownstream6Key makeDownstream6Key(int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst) { - return new TetherDownstream6Key(upstreamIfindex, upstreamMac, dst.getAddress()); - } - - @NonNull - private static Tether6Value makeDownstream6Value(@NonNull final MacAddress dstMac) { - return new Tether6Value(TEST_IFACE_PARAMS.index, dstMac, - TEST_IFACE_PARAMS.macAddr, ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); - } - - private static long prefixToLong(IpPrefix prefix) { - return ByteBuffer.wrap(prefix.getRawAddress()).getLong(); - } - - private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) { - return verifyWithOrder(inOrder, t, times(1)); - } - - private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t, VerificationMode mode) { - if (inOrder != null) { - return inOrder.verify(t, mode); - } else { - return verify(t, mode); - } - } - - private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, - @NonNull final MacAddress dstMac) throws Exception { - if (mBpfDeps.isAtLeastS()) { - verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry( - makeDownstream6Key(upstreamIfindex, upstreamMac, dst), - makeDownstream6Value(dstMac)); - } else { - verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, - dstMac)); - } - } - - private void verifyNeverTetherOffloadRuleAdd(int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, - @NonNull final MacAddress dstMac) throws Exception { - if (mBpfDeps.isAtLeastS()) { - verify(mBpfDownstream6Map, never()).updateEntry( - makeDownstream6Key(upstreamIfindex, upstreamMac, dst), - makeDownstream6Value(dstMac)); - } else { - verify(mNetd, never()).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac)); - } - } - - private void verifyNeverTetherOffloadRuleAdd() throws Exception { - if (mBpfDeps.isAtLeastS()) { - verify(mBpfDownstream6Map, never()).updateEntry(any(), any()); - } else { - verify(mNetd, never()).tetherOffloadRuleAdd(any()); - } - } - - private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, - @NonNull final MacAddress dstMac) throws Exception { - if (mBpfDeps.isAtLeastS()) { - verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry(makeDownstream6Key( - upstreamIfindex, upstreamMac, dst)); - } else { - // |dstMac| is not required for deleting rules. Used bacause tetherOffloadRuleRemove - // uses a whole rule to be a argument. - // See system/netd/server/TetherController.cpp/TetherController#removeOffloadRule. - verifyWithOrder(inOrder, mNetd).tetherOffloadRuleRemove(matches(upstreamIfindex, dst, - dstMac)); - } - } - - private void verifyNeverTetherOffloadRuleRemove() throws Exception { - if (mBpfDeps.isAtLeastS()) { - verify(mBpfDownstream6Map, never()).deleteEntry(any()); - } else { - verify(mNetd, never()).tetherOffloadRuleRemove(any()); - } - } - - private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull Set upstreamPrefixes) throws Exception { - if (!mBpfDeps.isAtLeastS()) return; - ArrayMap expected = new ArrayMap<>(); - for (IpPrefix upstreamPrefix : upstreamPrefixes) { - long prefix64 = prefixToLong(upstreamPrefix); - final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, - TEST_IFACE_PARAMS.macAddr, prefix64); - final Tether6Value value = new Tether6Value(upstreamIfindex, - MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, - ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); - expected.put(key, value); - } - ArgumentCaptor keyCaptor = - ArgumentCaptor.forClass(TetherUpstream6Key.class); - ArgumentCaptor valueCaptor = - ArgumentCaptor.forClass(Tether6Value.class); - verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry( - keyCaptor.capture(), valueCaptor.capture()); - List keys = keyCaptor.getAllValues(); - List values = valueCaptor.getAllValues(); - ArrayMap captured = new ArrayMap<>(); - for (int i = 0; i < keys.size(); i++) { - captured.put(keys.get(i), values.get(i)); - } - assertEquals(expected, captured); - } - - private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, - @NonNull Set upstreamPrefixes) throws Exception { - if (!mBpfDeps.isAtLeastS()) return; - Set expected = new ArraySet<>(); - for (IpPrefix upstreamPrefix : upstreamPrefixes) { - long prefix64 = prefixToLong(upstreamPrefix); - final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, - TEST_IFACE_PARAMS.macAddr, prefix64); - expected.add(key); - } - ArgumentCaptor keyCaptor = - ArgumentCaptor.forClass(TetherUpstream6Key.class); - verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).deleteEntry( - keyCaptor.capture()); - assertEquals(expected, new ArraySet(keyCaptor.getAllValues())); - } - - private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception { - if (!mBpfDeps.isAtLeastS()) return; - if (inOrder != null) { - inOrder.verify(mBpfUpstream6Map, never()).deleteEntry(any()); - inOrder.verify(mBpfUpstream6Map, never()).insertEntry(any(), any()); - inOrder.verify(mBpfUpstream6Map, never()).updateEntry(any(), any()); - } else { - verify(mBpfUpstream6Map, never()).deleteEntry(any()); - verify(mBpfUpstream6Map, never()).insertEntry(any(), any()); - verify(mBpfUpstream6Map, never()).updateEntry(any(), any()); - } - } - - @NonNull - private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { - TetherStatsParcel parcel = new TetherStatsParcel(); - parcel.ifIndex = ifIndex; - return parcel; - } - - private void resetNetdBpfMapAndCoordinator() throws Exception { - reset(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfCoordinator); - // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and - // potentially crash the test) if the stats map is empty. - when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); - when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX)) - .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX)); - when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2)) - .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2)); - // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and - // potentially crash the test) if the stats map is empty. - final TetherStatsValue allZeros = new TetherStatsValue(0, 0, 0, 0, 0, 0); - when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX))).thenReturn(allZeros); - when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX2))).thenReturn(allZeros); - } - @Test - public void addRemoveipv6ForwardingRules() throws Exception { + public void ipv6UpstreamInterfaceChanges() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); - final int myIfindex = TEST_IFACE_PARAMS.index; - final int notMyIfindex = myIfindex - 1; - - final InetAddress neighA = InetAddresses.parseNumericAddress("2001:db8::1"); - final InetAddress neighB = InetAddresses.parseNumericAddress("2001:db8::2"); - final InetAddress neighLL = InetAddresses.parseNumericAddress("fe80::1"); - final InetAddress neighMC = InetAddresses.parseNumericAddress("ff02::1234"); - final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); - final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b"); - - resetNetdBpfMapAndCoordinator(); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - - // 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, mBpfDownstream6Map, mBpfUpstream6Map); - - // 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); - resetNetdBpfMapAndCoordinator(); - - 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); - resetNetdBpfMapAndCoordinator(); - - // Link-local and multicast neighbors are ignored. - recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - - // A neighbor that is no longer valid causes the rule to be removed. - // NUD_FAILED events do not have a MAC address. - recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); - verify(mBpfCoordinator).removeIpv6DownstreamRule( - mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macNull)); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macNull); - resetNetdBpfMapAndCoordinator(); - - // A neighbor that is deleted causes the rule to be removed. - recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); - verify(mBpfCoordinator).removeIpv6DownstreamRule( - mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macNull)); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull); - resetNetdBpfMapAndCoordinator(); - // Upstream interface changes result in updating the rules. - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - resetNetdBpfMapAndCoordinator(); - - InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); LinkProperties lp = new LinkProperties(); lp.setInterfaceName(UPSTREAM_IFACE2); lp.setLinkAddresses(UPSTREAM_ADDRESSES); dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1); verify(mBpfCoordinator).updateAllIpv6Rules( mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES); - verifyTetherOffloadRuleRemove(inOrder, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(inOrder, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES); - verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES); - verifyTetherOffloadRuleAdd(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); - verifyTetherOffloadRuleAdd(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); - verifyNoUpstreamIpv6ForwardingChange(inOrder); - resetNetdBpfMapAndCoordinator(); + reset(mBpfCoordinator); // Upstream link addresses change result in updating the rules. LinkProperties lp2 = new LinkProperties(); @@ -1234,17 +776,7 @@ public class IpServerTest { dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, -1); verify(mBpfCoordinator).updateAllIpv6Rules( mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); - verifyTetherOffloadRuleRemove(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES); - verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); - verifyTetherOffloadRuleAdd(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); - verifyTetherOffloadRuleAdd(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); - resetNetdBpfMapAndCoordinator(); + reset(mBpfCoordinator); // When the upstream is lost, rules are removed. dispatchTetherConnectionChanged(null, null, 0); @@ -1254,156 +786,51 @@ public class IpServerTest { // See dispatchTetherConnectionChanged. verify(mBpfCoordinator, times(2)).updateAllIpv6Rules( mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); - verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); - // 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(); + reset(mBpfCoordinator); - // If the upstream is IPv4-only, no IPv6 rules are added to BPF map. + // If the upstream is IPv4-only, no rules are added. dispatchTetherConnectionChanged(UPSTREAM_IFACE); - resetNetdBpfMapAndCoordinator(); - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - 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); + verify(mBpfCoordinator, never()).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + reset(mBpfCoordinator); - // Rules can be added again once upstream IPv6 connectivity is available. The existing rules - // with an upstream of NO_UPSTREAM are reapplied. + // Rules are added again once upstream IPv6 connectivity is available. lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); - 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); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + reset(mBpfCoordinator); // If upstream IPv6 connectivity is lost, rules are removed. - resetNetdBpfMapAndCoordinator(); dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); verify(mBpfCoordinator).updateAllIpv6Rules( mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES); + reset(mBpfCoordinator); - // When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules - // are reapplied. + // When upstream IPv6 connectivity comes back, rules are added. lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); - verify(mBpfCoordinator).addIpv6DownstreamRule( - mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verify(mBpfCoordinator).addIpv6DownstreamRule( - mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - resetNetdBpfMapAndCoordinator(); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + reset(mBpfCoordinator); // When the downstream interface goes down, rules are removed. mIpServer.stop(); mLooper.dispatchAll(); verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer); - verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verify(mIpNeighborMonitor).stop(); - resetNetdBpfMapAndCoordinator(); - } - - @Test - public void enableDisableUsingBpfOffload() throws Exception { - final int myIfindex = TEST_IFACE_PARAMS.index; - final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1"); - final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); - final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - - // Expect that rules can be only added/removed when the BPF offload config is enabled. - // Note that the BPF offload disabled case is not a realistic test case. Because IP - // neighbor monitor doesn't start if BPF offload is disabled, there should have no - // neighbor event listening. This is used for testing the protection check just in case. - // TODO: Perhaps remove the BPF offload disabled case test once this check isn't needed - // anymore. - - // [1] Enable BPF offload. - // A neighbor that is added or deleted causes the rule to be added or removed. - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES, - false /* usingLegacyDhcp */, true /* usingBpfOffload */); - resetNetdBpfMapAndCoordinator(); - - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); - verify(mBpfCoordinator).addIpv6DownstreamRule( - mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neigh, macA)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macA); - resetNetdBpfMapAndCoordinator(); - - recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); - verify(mBpfCoordinator).removeIpv6DownstreamRule( - mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neigh, macNull)); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macNull); - resetNetdBpfMapAndCoordinator(); - - // Upstream IPv6 connectivity change causes upstream rules change. - LinkProperties lp2 = new LinkProperties(); - lp2.setInterfaceName(UPSTREAM_IFACE2); - lp2.setLinkAddresses(UPSTREAM_ADDRESSES2); - dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0); verify(mBpfCoordinator).updateAllIpv6Rules( - mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); - 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, UPSTREAM_ADDRESSES, - false /* usingLegacyDhcp */, false /* usingBpfOffload */); - resetNetdBpfMapAndCoordinator(); - - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); - verifyNeverTetherOffloadRuleAdd(); - 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(); + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + reset(mBpfCoordinator); } @Test - public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception { + public void stopNeighborMonitoringWhenInterfaceDown() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES, - false /* usingLegacyDhcp */, false /* usingBpfOffload */); + false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); - // IP neighbor monitor doesn't start if BPF offload is disabled. - verify(mIpNeighborMonitor, never()).start(); + mIpServer.stop(); + mLooper.dispatchAll(); + verify(mIpNeighborMonitor).stop(); } private LinkProperties buildIpv6OnlyLinkProperties(final String iface) { @@ -1659,75 +1086,4 @@ public class IpServerTest { public void testDadProxyUpdates_EnabledAfterR() throws Exception { checkDadProxyEnabled(true); } - - @Test - public void testSkipVirtualNetworkInBpf() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, null); - final LinkProperties v6Only = new LinkProperties(); - v6Only.setInterfaceName(IPSEC_IFACE); - dispatchTetherConnectionChanged(IPSEC_IFACE, v6Only, 0); - - verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, IPSEC_IFACE); - verify(mNetd).tetherAddForward(IFACE_NAME, IPSEC_IFACE); - verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, IPSEC_IFACE); - - final int myIfindex = TEST_IFACE_PARAMS.index; - final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1"); - final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a"); - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, mac); - verify(mBpfCoordinator, never()).addIpv6DownstreamRule( - mIpServer, makeDownstreamRule(IPSEC_IFINDEX, neigh, mac)); - } - - // TODO: move to BpfCoordinatorTest once IpNeighborMonitor is migrated to BpfCoordinator. - @Test - public void addRemoveTetherClient() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES, - false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); - - final int myIfindex = TEST_IFACE_PARAMS.index; - final int notMyIfindex = myIfindex - 1; - - final InetAddress neighA = InetAddresses.parseNumericAddress("192.168.80.1"); - final InetAddress neighB = InetAddresses.parseNumericAddress("192.168.80.2"); - final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1"); - final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1"); - final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); - final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b"); - - // Events on other interfaces are ignored. - recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator); - - // Events on this interface are received and sent to BpfCoordinator. - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - verify(mBpfCoordinator).tetherOffloadClientAdd(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighA, macA)); - clearInvocations(mBpfCoordinator); - - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mBpfCoordinator).tetherOffloadClientAdd(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighB, macB)); - clearInvocations(mBpfCoordinator); - - // Link-local and multicast neighbors are ignored. - recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator); - recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator); - clearInvocations(mBpfCoordinator); - - // A neighbor that is no longer valid causes the client to be removed. - // NUD_FAILED events do not have a MAC address. - recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); - verify(mBpfCoordinator).tetherOffloadClientRemove(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighA, macNull)); - clearInvocations(mBpfCoordinator); - - // A neighbor that is deleted causes the client to be removed. - recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); - verify(mBpfCoordinator).tetherOffloadClientRemove(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighB, macNull)); - } } 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 7fbb670d20..6c1721e9b9 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -25,6 +25,8 @@ import static android.net.NetworkStats.SET_DEFAULT; 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.TetheringManager.TETHERING_WIFI; +import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; @@ -43,6 +45,11 @@ import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4; import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto; import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE; import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH; +import static com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED; +import static com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE; +import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE; import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS; import static com.android.networkstack.tethering.BpfCoordinator.INVALID_MTU; import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED; @@ -71,11 +78,15 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.usage.NetworkStatsManager; @@ -88,13 +99,16 @@ import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkStats; +import android.net.RoutingCoordinatorManager; import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; import android.net.ip.IpServer; +import android.net.ip.RouterAdvertisementDaemon; import android.os.Build; import android.os.Handler; import android.os.test.TestLooper; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -104,10 +118,12 @@ import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.util.IndentingPrintWriter; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.IBpfMap; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.Struct.S32; import com.android.net.module.util.bpf.Tether4Key; @@ -116,6 +132,9 @@ import com.android.net.module.util.bpf.TetherStatsKey; import com.android.net.module.util.bpf.TetherStatsValue; import com.android.net.module.util.ip.ConntrackMonitor; import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer; +import com.android.net.module.util.ip.IpNeighborMonitor; +import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent; +import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer; import com.android.net.module.util.netlink.ConntrackMessage; import com.android.net.module.util.netlink.NetlinkConstants; import com.android.net.module.util.netlink.NetlinkUtils; @@ -123,6 +142,8 @@ import com.android.networkstack.tethering.BpfCoordinator.BpfConntrackEventConsum import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule; +import com.android.networkstack.tethering.metrics.TetheringMetrics; +import com.android.networkstack.tethering.util.InterfaceSet; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -172,19 +193,27 @@ public class BpfCoordinatorTest { private static final int UPSTREAM_IFINDEX2 = 1003; private static final int DOWNSTREAM_IFINDEX = 2001; private static final int DOWNSTREAM_IFINDEX2 = 2002; + private static final int IPSEC_IFINDEX = 103; 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 String IPSEC_IFACE = "ipsec0"; 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"); private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a"); private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b"); + private static final MacAddress MAC_NULL = MacAddress.fromString("00:00:00:00:00:00"); + private static final LinkAddress UPSTREAM_ADDRESS = new LinkAddress("2001:db8:0:1234::168/64"); + private static final LinkAddress UPSTREAM_ADDRESS2 = new LinkAddress("2001:db8:0:abcd::168/64"); + private static final Set UPSTREAM_ADDRESSES = Set.of(UPSTREAM_ADDRESS); + private static final Set UPSTREAM_ADDRESSES2 = + Set.of(UPSTREAM_ADDRESS, UPSTREAM_ADDRESS2); private static final IpPrefix UPSTREAM_PREFIX = new IpPrefix("2001:db8:0:1234::/64"); private static final IpPrefix UPSTREAM_PREFIX2 = new IpPrefix("2001:db8:0:abcd::/64"); private static final Set UPSTREAM_PREFIXES = Set.of(UPSTREAM_PREFIX); @@ -196,6 +225,8 @@ public class BpfCoordinatorTest { InetAddresses.parseNumericAddress("2001:db8:0:1234::1"); private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8:0:1234::2"); + private static final InetAddress NEIGH_LL = InetAddresses.parseNumericAddress("fe80::1"); + private static final InetAddress NEIGH_MC = InetAddresses.parseNumericAddress("ff02::1234"); private static final Inet4Address REMOTE_ADDR = (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116"); @@ -235,6 +266,9 @@ public class BpfCoordinatorTest { private static final InterfaceParams DOWNSTREAM_IFACE_PARAMS2 = new InterfaceParams( DOWNSTREAM_IFACE2, DOWNSTREAM_IFINDEX2, DOWNSTREAM_MAC2, NetworkStackConstants.ETHER_MTU); + private static final InterfaceParams IPSEC_IFACE_PARAMS = new InterfaceParams( + IPSEC_IFACE, IPSEC_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, + NetworkStackConstants.ETHER_MTU); private static final Map UPSTREAM_INFORMATIONS = Map.of( UPSTREAM_IFINDEX, new UpstreamInformation(UPSTREAM_IFACE_PARAMS, @@ -414,6 +448,14 @@ public class BpfCoordinatorTest { @Mock private IpServer mIpServer2; @Mock private TetheringConfiguration mTetherConfig; @Mock private ConntrackMonitor mConntrackMonitor; + @Mock private IpNeighborMonitor mIpNeighborMonitor; + @Mock private RouterAdvertisementDaemon mRaDaemon; + @Mock private IpServer.Dependencies mIpServerDeps; + @Mock private IpServer.Callback mIpServerCallback; + @Mock private PrivateAddressCoordinator mAddressCoordinator; + private final LateSdk mRoutingCoordinatorManager = + new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null); + @Mock private TetheringMetrics mTetheringMetrics; // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; @@ -422,6 +464,7 @@ public class BpfCoordinatorTest { // Late init since the object must be initialized by the BPF coordinator instance because // it has to access the non-static function of BPF coordinator. private BpfConntrackEventConsumer mConsumer; + private NeighborEventConsumer mNeighborEventConsumer; private HashMap> mTetherClients; private long mElapsedRealtimeNanos = 0; @@ -429,6 +472,7 @@ public class BpfCoordinatorTest { private final ArgumentCaptor mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); private final TestLooper mTestLooper = new TestLooper(); + private final Handler mHandler = new Handler(mTestLooper.getLooper()); private final IBpfMap mBpfDownstream4Map = spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class)); private final IBpfMap mBpfUpstream4Map = @@ -449,7 +493,7 @@ public class BpfCoordinatorTest { spy(new BpfCoordinator.Dependencies() { @NonNull public Handler getHandler() { - return new Handler(mTestLooper.getLooper()); + return mHandler; } @NonNull @@ -529,6 +573,24 @@ public class BpfCoordinatorTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */); + + // Simulate the behavior of RoutingCoordinator + if (null != mRoutingCoordinatorManager.value) { + doAnswer(it -> { + final String fromIface = (String) it.getArguments()[0]; + final String toIface = (String) it.getArguments()[1]; + mNetd.tetherAddForward(fromIface, toIface); + mNetd.ipfwdAddInterfaceForward(fromIface, toIface); + return null; + }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any()); + doAnswer(it -> { + final String fromIface = (String) it.getArguments()[0]; + final String toIface = (String) it.getArguments()[1]; + mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface); + mNetd.tetherRemoveForward(fromIface, toIface); + return null; + }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any()); + } } private void waitForIdle() { @@ -541,8 +603,69 @@ public class BpfCoordinatorTest { when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); } + @NonNull + private IpServer makeAndStartIpServer(String interfaceName, BpfCoordinator bpfCoordinator) + throws Exception { + final LinkAddress testAddress = new LinkAddress("192.168.42.5/24"); + when(mIpServerDeps.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); + when(mIpServerDeps.getInterfaceParams(DOWNSTREAM_IFACE)).thenReturn( + DOWNSTREAM_IFACE_PARAMS); + when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS); + when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2); + when(mIpServerDeps.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS); + when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(), + anyBoolean())).thenReturn(testAddress); + when(mRaDaemon.start()).thenReturn(true); + ArgumentCaptor neighborEventCaptor = + ArgumentCaptor.forClass(NeighborEventConsumer.class); + doReturn(mIpNeighborMonitor).when(mIpServerDeps).getIpNeighborMonitor(any(), any(), + neighborEventCaptor.capture()); + final IpServer ipServer = new IpServer( + interfaceName, mHandler, TETHERING_WIFI, new SharedLog("test"), mNetd, + bpfCoordinator, mRoutingCoordinatorManager, mIpServerCallback, mTetherConfig, + mAddressCoordinator, mTetheringMetrics, mIpServerDeps); + ipServer.start(); + ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); + mTestLooper.dispatchAll(); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE); + lp.setLinkAddresses(UPSTREAM_ADDRESSES); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, 0); + + mNeighborEventConsumer = neighborEventCaptor.getValue(); + return ipServer; + } + + private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface, + LinkProperties v6lp, int ttlAdjustment) { + dispatchTetherConnectionChanged(ipServer, upstreamIface); + ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, ttlAdjustment, 0, v6lp); + mTestLooper.dispatchAll(); + } + + private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface) { + final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null; + ipServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs); + mTestLooper.dispatchAll(); + } + + private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { + mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr, + nudState, mac)); + mTestLooper.dispatchAll(); + } + + private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { + mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr, + nudState, mac)); + mTestLooper.dispatchAll(); + } + @NonNull private BpfCoordinator makeBpfCoordinator() throws Exception { + // mStatsManager will be invoked twice if BpfCoordinator is created the second time. + clearInvocations(mStatsManager); final BpfCoordinator coordinator = new BpfCoordinator(mDeps); mConsumer = coordinator.getBpfConntrackEventConsumerForTesting(); @@ -666,6 +789,51 @@ public class BpfCoordinatorTest { } } + private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex, + @NonNull Set upstreamPrefixes) throws Exception { + if (!mDeps.isAtLeastS()) return; + ArrayMap expected = new ArrayMap<>(); + for (IpPrefix upstreamPrefix : upstreamPrefixes) { + long prefix64 = prefixToLong(upstreamPrefix); + final TetherUpstream6Key key = new TetherUpstream6Key(DOWNSTREAM_IFACE_PARAMS.index, + DOWNSTREAM_IFACE_PARAMS.macAddr, prefix64); + final Tether6Value value = new Tether6Value(upstreamIfindex, + MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, ETH_P_IPV6, + NetworkStackConstants.ETHER_MTU); + expected.put(key, value); + } + ArgumentCaptor keyCaptor = + ArgumentCaptor.forClass(TetherUpstream6Key.class); + ArgumentCaptor valueCaptor = + ArgumentCaptor.forClass(Tether6Value.class); + verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry( + keyCaptor.capture(), valueCaptor.capture()); + List keys = keyCaptor.getAllValues(); + List values = valueCaptor.getAllValues(); + ArrayMap captured = new ArrayMap<>(); + for (int i = 0; i < keys.size(); i++) { + captured.put(keys.get(i), values.get(i)); + } + assertEquals(expected, captured); + } + + private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, + @NonNull Set upstreamPrefixes) throws Exception { + if (!mDeps.isAtLeastS()) return; + Set expected = new ArraySet<>(); + for (IpPrefix upstreamPrefix : upstreamPrefixes) { + long prefix64 = prefixToLong(upstreamPrefix); + final TetherUpstream6Key key = new TetherUpstream6Key(DOWNSTREAM_IFACE_PARAMS.index, + DOWNSTREAM_IFACE_PARAMS.macAddr, prefix64); + expected.add(key); + } + ArgumentCaptor keyCaptor = + ArgumentCaptor.forClass(TetherUpstream6Key.class); + verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).deleteEntry( + keyCaptor.capture()); + assertEquals(expected, new ArraySet(keyCaptor.getAllValues())); + } + private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception { if (!mDeps.isAtLeastS()) return; if (inOrder != null) { @@ -708,6 +876,10 @@ public class BpfCoordinatorTest { assertEquals(expected, captured); } + private void verifyAddDownstreamRule(@NonNull Ipv6DownstreamRule rule) throws Exception { + verifyAddDownstreamRule(null, rule); + } + private void verifyAddDownstreamRule(@Nullable InOrder inOrder, @NonNull Ipv6DownstreamRule rule) throws Exception { if (mDeps.isAtLeastS()) { @@ -752,6 +924,11 @@ public class BpfCoordinatorTest { assertSameElements(expected, keyCaptor.getAllValues()); } + private void verifyRemoveDownstreamRule(@NonNull final Ipv6DownstreamRule rule) + throws Exception { + verifyRemoveDownstreamRule(null, rule); + } + private void verifyRemoveDownstreamRule(@Nullable InOrder inOrder, @NonNull final Ipv6DownstreamRule rule) throws Exception { if (mDeps.isAtLeastS()) { @@ -1016,8 +1193,37 @@ public class BpfCoordinatorTest { mTetherStatsProviderCb.assertNoCallback(); } - // The custom ArgumentMatcher simply comes from IpServerTest. - // TODO: move both of them into a common utility class for reusing the code. + /** + * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable + * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as: + * + * private void checkFooCalled(StableParcelable p, ...) { + * ArgumentCaptor<@FooParam> captor = ArgumentCaptor.forClass(FooParam.class); + * verify(mMock).foo(captor.capture()); + * Foo foo = captor.getValue(); + * assertFooMatchesExpectations(foo); + * } + * + * almost works, but not quite. This is because if the code under test calls foo() twice, the + * first call to checkFooCalled() matches both the calls, putting both calls into the captor, + * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito + * features such as never(), inOrder(), etc. + * + * This approach isn't great because if the match fails, the error message is unhelpful + * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does + * work. + * + * TODO: consider making the error message more readable by adding a method that catching the + * AssertionFailedError and throwing a new assertion with more details. See + * NetworkMonitorTest#verifyNetworkTested. + * + * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the + * TooManyActualInvocations problem described above by forcing the caller of the custom assert + * method to specify all expected invocations in one call. This is useful when the stable + * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and + * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here + * because there is no such object. + */ private static class TetherOffloadRuleParcelMatcher implements ArgumentMatcher { public final int upstreamIfindex; @@ -2064,8 +2270,7 @@ public class BpfCoordinatorTest { } private static long prefixToLong(IpPrefix prefix) { - byte[] prefixBytes = Arrays.copyOf(prefix.getRawAddress(), 8); - return ByteBuffer.wrap(prefixBytes).getLong(); + return ByteBuffer.wrap(prefix.getRawAddress()).getLong(); } void checkRule4ExistInUpstreamDownstreamMap() throws Exception { @@ -2135,7 +2340,7 @@ public class BpfCoordinatorTest { assertNull(mTetherClients.get(mIpServer2)); } - private void asseertClientInfoExist(@NonNull IpServer ipServer, + private void assertClientInfoExists(@NonNull IpServer ipServer, @NonNull ClientInfo clientInfo) { HashMap clients = mTetherClients.get(ipServer); assertNotNull(clients); @@ -2144,16 +2349,16 @@ public class BpfCoordinatorTest { // Although either ClientInfo for a given downstream (IpServer) is not found or a given // client address is not found on a given downstream can be treated "ClientInfo not - // exist", we still want to know the real reason exactly. For example, we don't the + // exist", we still want to know the real reason exactly. For example, we don't know the // exact reason in the following: - // assertNull(clients == null ? clients : clients.get(clientInfo.clientAddress)); + // assertNull(clients == null ? clients : clients.get(clientAddress)); // This helper only verifies the case that the downstream still has at least one client. // In other words, ClientInfo for a given IpServer has not been removed yet. - private void asseertClientInfoNotExist(@NonNull IpServer ipServer, - @NonNull ClientInfo clientInfo) { + private void assertClientInfoDoesNotExist(@NonNull IpServer ipServer, + @NonNull Inet4Address clientAddress) { HashMap clients = mTetherClients.get(ipServer); assertNotNull(clients); - assertNull(clients.get(clientInfo.clientAddress)); + assertNull(clients.get(clientAddress)); } @Test @@ -2189,8 +2394,8 @@ public class BpfCoordinatorTest { checkRule4NotExistInUpstreamDownstreamMap(); // Client information should be not deleted. - asseertClientInfoExist(mIpServer, CLIENT_INFO_A); - asseertClientInfoExist(mIpServer2, CLIENT_INFO_B); + assertClientInfoExists(mIpServer, CLIENT_INFO_A); + assertClientInfoExists(mIpServer2, CLIENT_INFO_B); } @Test @@ -2205,8 +2410,8 @@ public class BpfCoordinatorTest { PRIVATE_ADDR2, MAC_B); coordinator.tetherOffloadClientAdd(mIpServer, clientA); coordinator.tetherOffloadClientAdd(mIpServer, clientB); - asseertClientInfoExist(mIpServer, clientA); - asseertClientInfoExist(mIpServer, clientB); + assertClientInfoExists(mIpServer, clientA); + assertClientInfoExists(mIpServer, clientB); // Add the rules for client A and client B. final Tether4Key upstream4KeyA = makeUpstream4Key( @@ -2230,8 +2435,8 @@ public class BpfCoordinatorTest { // [2] Remove client information A. Only the rules on client A should be removed and // the rules on client B should exist. coordinator.tetherOffloadClientRemove(mIpServer, clientA); - asseertClientInfoNotExist(mIpServer, clientA); - asseertClientInfoExist(mIpServer, clientB); + assertClientInfoDoesNotExist(mIpServer, clientA.clientAddress); + assertClientInfoExists(mIpServer, clientB); assertNull(mBpfUpstream4Map.getValue(upstream4KeyA)); assertNull(mBpfDownstream4Map.getValue(downstream4KeyA)); assertEquals(upstream4ValueB, mBpfUpstream4Map.getValue(upstream4KeyB)); @@ -2239,9 +2444,9 @@ public class BpfCoordinatorTest { // [3] Remove client information B. The rules on client B should be removed. // Exactly, ClientInfo for a given IpServer is removed because the last client B - // has been removed from the downstream. Can't use the helper #asseertClientInfoExist + // has been removed from the downstream. Can't use the helper #assertClientInfoExists // to check because the container ClientInfo for a given downstream has been removed. - // See #asseertClientInfoExist. + // See #assertClientInfoExists. coordinator.tetherOffloadClientRemove(mIpServer, clientB); assertNull(mTetherClients.get(mIpServer)); assertNull(mBpfUpstream4Map.getValue(upstream4KeyB)); @@ -2581,4 +2786,296 @@ public class BpfCoordinatorTest { public void testUpdateUpstreamNetworkState() throws Exception { verifyUpdateUpstreamNetworkState(); } + + @NonNull + private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { + TetherStatsParcel parcel = new TetherStatsParcel(); + parcel.ifIndex = ifIndex; + return parcel; + } + + private void resetNetdAndBpfMaps() throws Exception { + reset(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and + // potentially crash the test) if the stats map is empty. + when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); + when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX)) + .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX)); + when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2)) + .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2)); + // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and + // potentially crash the test) if the stats map is empty. + final TetherStatsValue allZeros = new TetherStatsValue(0, 0, 0, 0, 0, 0); + when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX))).thenReturn(allZeros); + when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX2))).thenReturn(allZeros); + } + + @Test + public void addRemoveIpv6ForwardingRules() throws Exception { + final int myIfindex = DOWNSTREAM_IFINDEX; + final int notMyIfindex = myIfindex - 1; + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + + resetNetdAndBpfMaps(); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and + // tetherOffloadGetAndClearStats in netd while the rules are changed. + + // Events on other interfaces are ignored. + recvNewNeigh(notMyIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // Events on this interface are received and sent to BpfCoordinator. + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + final Ipv6DownstreamRule ruleA = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); + verifyAddDownstreamRule(ruleA); + resetNetdAndBpfMaps(); + + recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B); + final Ipv6DownstreamRule ruleB = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B); + verifyAddDownstreamRule(ruleB); + resetNetdAndBpfMaps(); + + // Link-local and multicast neighbors are ignored. + recvNewNeigh(myIfindex, NEIGH_LL, NUD_REACHABLE, MAC_A); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + recvNewNeigh(myIfindex, NEIGH_MC, NUD_REACHABLE, MAC_A); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // A neighbor that is no longer valid causes the rule to be removed. + // NUD_FAILED events do not have a MAC address. + recvNewNeigh(myIfindex, NEIGH_A, NUD_FAILED, null); + final Ipv6DownstreamRule ruleANull = buildTestDownstreamRule( + UPSTREAM_IFINDEX, NEIGH_A, MAC_NULL); + verifyRemoveDownstreamRule(ruleANull); + resetNetdAndBpfMaps(); + + // A neighbor that is deleted causes the rule to be removed. + recvDelNeigh(myIfindex, NEIGH_B, NUD_STALE, MAC_B); + final Ipv6DownstreamRule ruleBNull = buildTestDownstreamRule( + UPSTREAM_IFINDEX, NEIGH_B, MAC_NULL); + verifyRemoveDownstreamRule(ruleBNull); + resetNetdAndBpfMaps(); + + // Upstream interface changes result in updating the rules. + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B); + resetNetdAndBpfMaps(); + + InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE2); + lp.setLinkAddresses(UPSTREAM_ADDRESSES); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp, -1); + final Ipv6DownstreamRule ruleA2 = buildTestDownstreamRule( + UPSTREAM_IFINDEX2, NEIGH_A, MAC_A); + final Ipv6DownstreamRule ruleB2 = buildTestDownstreamRule( + UPSTREAM_IFINDEX2, NEIGH_B, MAC_B); + verifyRemoveDownstreamRule(inOrder, ruleA); + verifyRemoveDownstreamRule(inOrder, ruleB); + verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES); + verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES); + verifyAddDownstreamRule(inOrder, ruleA2); + verifyAddDownstreamRule(inOrder, ruleB2); + verifyNoUpstreamIpv6ForwardingChange(inOrder); + resetNetdAndBpfMaps(); + + // Upstream link addresses change result in updating the rules. + LinkProperties lp2 = new LinkProperties(); + lp2.setInterfaceName(UPSTREAM_IFACE2); + lp2.setLinkAddresses(UPSTREAM_ADDRESSES2); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, -1); + verifyRemoveDownstreamRule(inOrder, ruleA2); + verifyRemoveDownstreamRule(inOrder, ruleB2); + verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES); + verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); + verifyAddDownstreamRule(inOrder, ruleA2); + verifyAddDownstreamRule(inOrder, ruleB2); + resetNetdAndBpfMaps(); + + // When the upstream is lost, rules are removed. + dispatchTetherConnectionChanged(ipServer, null, null, 0); + verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2); + verifyRemoveDownstreamRule(ruleA2); + verifyRemoveDownstreamRule(ruleB2); + // Upstream lost doesn't clear the downstream rules from the maps. + // Do that here. + recvDelNeigh(myIfindex, NEIGH_A, NUD_STALE, MAC_A); + recvDelNeigh(myIfindex, NEIGH_B, NUD_STALE, MAC_B); + resetNetdAndBpfMaps(); + + // If the upstream is IPv4-only, no IPv6 rules are added to BPF map. + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE); + resetNetdAndBpfMaps(); + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNoUpstreamIpv6ForwardingChange(null); + // Downstream rules are only added to BpfCoordinator but not BPF map. + verifyNeverAddDownstreamRule(); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // 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(ipServer, UPSTREAM_IFACE, lp, -1); + verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + verifyAddDownstreamRule(ruleA); + recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B); + verifyAddDownstreamRule(ruleB); + + // If upstream IPv6 connectivity is lost, rules are removed. + resetNetdAndBpfMaps(); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, null, 0); + verifyRemoveDownstreamRule(ruleA); + verifyRemoveDownstreamRule(ruleB); + verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES); + + // When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules + // are reapplied. + lp.setInterfaceName(UPSTREAM_IFACE); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1); + verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + verifyAddDownstreamRule(ruleA); + verifyAddDownstreamRule(ruleB); + resetNetdAndBpfMaps(); + + // When the downstream interface goes down, rules are removed. + ipServer.stop(); + mTestLooper.dispatchAll(); + verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES); + verifyRemoveDownstreamRule(ruleA); + verifyRemoveDownstreamRule(ruleB); + verify(mIpNeighborMonitor).stop(); + resetNetdAndBpfMaps(); + } + + @Test + public void enableDisableUsingBpfOffload() throws Exception { + final int myIfindex = DOWNSTREAM_IFINDEX; + + // Expect that rules can be only added/removed when the BPF offload config is enabled. + // Note that the BPF offload disabled case is not a realistic test case. Because IP + // neighbor monitor doesn't start if BPF offload is disabled, there should have no + // neighbor event listening. This is used for testing the protection check just in case. + // TODO: Perhaps remove the BPF offload disabled case test once this check isn't needed + // anymore. + + // [1] Enable BPF offload. + // A neighbor that is added or deleted causes the rule to be added or removed. + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + resetNetdAndBpfMaps(); + + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + final Ipv6DownstreamRule rule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); + verifyAddDownstreamRule(rule); + resetNetdAndBpfMaps(); + + recvDelNeigh(myIfindex, NEIGH_A, NUD_STALE, MAC_A); + final Ipv6DownstreamRule ruleNull = buildTestDownstreamRule( + UPSTREAM_IFINDEX, NEIGH_A, MAC_NULL); + verifyRemoveDownstreamRule(ruleNull); + resetNetdAndBpfMaps(); + + // Upstream IPv6 connectivity change causes upstream rules change. + LinkProperties lp2 = new LinkProperties(); + lp2.setInterfaceName(UPSTREAM_IFACE2); + lp2.setLinkAddresses(UPSTREAM_ADDRESSES2); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, 0); + verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); + resetNetdAndBpfMaps(); + + // [2] Disable BPF offload. + // A neighbor that is added or deleted doesn’t cause the rule to be added or removed. + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); + final BpfCoordinator coordinator2 = makeBpfCoordinator(); + final IpServer ipServer2 = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator2); + verifyNoUpstreamIpv6ForwardingChange(null); + resetNetdAndBpfMaps(); + + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNeverAddDownstreamRule(); + resetNetdAndBpfMaps(); + + recvDelNeigh(myIfindex, NEIGH_A, NUD_STALE, MAC_A); + verifyNeverRemoveDownstreamRule(); + resetNetdAndBpfMaps(); + + // Upstream IPv6 connectivity change doesn't cause the rule to be added or removed. + dispatchTetherConnectionChanged(ipServer2, UPSTREAM_IFACE2, lp2, 0); + verifyNoUpstreamIpv6ForwardingChange(null); + verifyNeverRemoveDownstreamRule(); + resetNetdAndBpfMaps(); + } + + @Test + public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception { + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + + // IP neighbor monitor doesn't start if BPF offload is disabled. + verify(mIpNeighborMonitor, never()).start(); + } + + @Test + public void testSkipVirtualNetworkInBpf() throws Exception { + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + final LinkProperties v6Only = new LinkProperties(); + v6Only.setInterfaceName(IPSEC_IFACE); + v6Only.setLinkAddresses(UPSTREAM_ADDRESSES); + + resetNetdAndBpfMaps(); + dispatchTetherConnectionChanged(ipServer, IPSEC_IFACE, v6Only, 0); + verify(mNetd).tetherAddForward(DOWNSTREAM_IFACE, IPSEC_IFACE); + verify(mNetd).ipfwdAddInterfaceForward(DOWNSTREAM_IFACE, IPSEC_IFACE); + verifyNeverAddUpstreamRule(); + + recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNeverAddDownstreamRule(); + } + + @Test + public void addRemoveTetherClient() throws Exception { + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + final int myIfindex = DOWNSTREAM_IFINDEX; + final int notMyIfindex = myIfindex - 1; + + final InetAddress neighA = InetAddresses.parseNumericAddress("192.168.80.1"); + final InetAddress neighB = InetAddresses.parseNumericAddress("192.168.80.2"); + final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1"); + final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1"); + + // Events on other interfaces are ignored. + recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, MAC_A); + assertNull(mTetherClients.get(ipServer)); + + // Events on this interface are received and sent to BpfCoordinator. + recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, MAC_A); + assertClientInfoExists(ipServer, + new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighA, MAC_A)); + + recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, MAC_B); + assertClientInfoExists(ipServer, + new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighB, MAC_B)); + + // Link-local and multicast neighbors are ignored. + recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, MAC_A); + assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighLL); + recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, MAC_A); + assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighMC); + + // A neighbor that is no longer valid causes the client to be removed. + // NUD_FAILED events do not have a MAC address. + recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); + assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighA); + + // A neighbor that is deleted causes the client to be removed. + recvDelNeigh(myIfindex, neighB, NUD_STALE, MAC_B); + // When last client information is deleted, IpServer will be removed from mTetherClients + assertNull(mTetherClients.get(ipServer)); + } }