Merge "[Refactor] move BPF tests to BpfCoordinatorTest" into main

This commit is contained in:
KH Shi 2023-11-08 01:30:12 +00:00 • committed by Gerrit Code Review
commit f9e70f9bf3
2 changed files with 553 additions and 700 deletions

View file

@ -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<Tether4Key, Tether4Value> mBpfDownstream4Map;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
@Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
@Mock private BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
@Mock private BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
@Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
@Mock private BpfMap<S32, S32> mBpfErrorMap;
@Mock private BpfCoordinator mBpfCoordinator;
@Captor private ArgumentCaptor<DhcpServingParamsParcel> 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<NeighborEventConsumer> 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<IpPrefix> 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<IpPrefix> 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<Tether4Key, Tether4Value> getBpfDownstream4Map() {
return mBpfDownstream4Map;
}
@Nullable
public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
return mBpfUpstream4Map;
}
@Nullable
public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
return mBpfDownstream6Map;
}
@Nullable
public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
return mBpfUpstream6Map;
}
@Nullable
public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
return mBpfStatsMap;
}
@Nullable
public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
return mBpfLimitMap;
}
@Nullable
public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
return mBpfDevMap;
}
@Nullable
public BpfMap<S32, S32> 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<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<TetherOffloadRuleParcel> {
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> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
return verifyWithOrder(inOrder, t, times(1));
}
private <T> 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<IpPrefix> upstreamPrefixes) throws Exception {
if (!mBpfDeps.isAtLeastS()) return;
ArrayMap<TetherUpstream6Key, Tether6Value> 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<TetherUpstream6Key> keyCaptor =
ArgumentCaptor.forClass(TetherUpstream6Key.class);
ArgumentCaptor<Tether6Value> valueCaptor =
ArgumentCaptor.forClass(Tether6Value.class);
verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry(
keyCaptor.capture(), valueCaptor.capture());
List<TetherUpstream6Key> keys = keyCaptor.getAllValues();
List<Tether6Value> values = valueCaptor.getAllValues();
ArrayMap<TetherUpstream6Key, Tether6Value> 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<IpPrefix> upstreamPrefixes) throws Exception {
if (!mBpfDeps.isAtLeastS()) return;
Set<TetherUpstream6Key> 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<TetherUpstream6Key> 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));
}
}

View file

@ -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<LinkAddress> UPSTREAM_ADDRESSES = Set.of(UPSTREAM_ADDRESS);
private static final Set<LinkAddress> 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<IpPrefix> 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<Integer, UpstreamInformation> 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<RoutingCoordinatorManager> 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<IpServer, HashMap<Inet4Address, ClientInfo>> mTetherClients;
private long mElapsedRealtimeNanos = 0;
@ -429,6 +472,7 @@ public class BpfCoordinatorTest {
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
private final TestLooper mTestLooper = new TestLooper();
private final Handler mHandler = new Handler(mTestLooper.getLooper());
private final IBpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map =
spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
private final IBpfMap<Tether4Key, Tether4Value> 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<NeighborEventConsumer> 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<IpPrefix> upstreamPrefixes) throws Exception {
if (!mDeps.isAtLeastS()) return;
ArrayMap<TetherUpstream6Key, Tether6Value> 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<TetherUpstream6Key> keyCaptor =
ArgumentCaptor.forClass(TetherUpstream6Key.class);
ArgumentCaptor<Tether6Value> valueCaptor =
ArgumentCaptor.forClass(Tether6Value.class);
verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry(
keyCaptor.capture(), valueCaptor.capture());
List<TetherUpstream6Key> keys = keyCaptor.getAllValues();
List<Tether6Value> values = valueCaptor.getAllValues();
ArrayMap<TetherUpstream6Key, Tether6Value> 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<IpPrefix> upstreamPrefixes) throws Exception {
if (!mDeps.isAtLeastS()) return;
Set<TetherUpstream6Key> 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<TetherUpstream6Key> 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<TetherOffloadRuleParcel> {
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<Inet4Address, ClientInfo> 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<Inet4Address, ClientInfo> 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));
}
}