| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net.ip; |
| |
| import static android.net.INetd.IF_STATE_DOWN; |
| import static android.net.INetd.IF_STATE_UP; |
| import static android.net.RouteInfo.RTN_UNICAST; |
| import static android.net.TetheringManager.TETHERING_BLUETOOTH; |
| import static android.net.TetheringManager.TETHERING_NCM; |
| import static android.net.TetheringManager.TETHERING_USB; |
| import static android.net.TetheringManager.TETHERING_WIFI; |
| import static android.net.TetheringManager.TETHERING_WIFI_P2P; |
| import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; |
| import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; |
| import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; |
| import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; |
| import static android.net.ip.IpServer.STATE_AVAILABLE; |
| 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.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; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.argThat; |
| 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.doAnswer; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.doThrow; |
| import static org.mockito.Mockito.inOrder; |
| 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; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.usage.NetworkStatsManager; |
| import android.net.INetd; |
| import android.net.InetAddresses; |
| import android.net.InterfaceConfigurationParcel; |
| import android.net.IpPrefix; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.MacAddress; |
| import android.net.RouteInfo; |
| import android.net.TetherOffloadRuleParcel; |
| import android.net.TetherStatsParcel; |
| import android.net.dhcp.DhcpServerCallbacks; |
| import android.net.dhcp.DhcpServingParamsParcel; |
| import android.net.dhcp.IDhcpEventCallbacks; |
| import android.net.dhcp.IDhcpServer; |
| import android.net.dhcp.IDhcpServerCallbacks; |
| import android.net.ip.IpNeighborMonitor.NeighborEvent; |
| import android.net.ip.IpNeighborMonitor.NeighborEventConsumer; |
| import android.net.ip.RouterAdvertisementDaemon.RaParams; |
| import android.net.util.SharedLog; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.RemoteException; |
| import android.os.test.TestLooper; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| 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.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.networkstack.tethering.BpfCoordinator; |
| import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; |
| 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; |
| import com.android.networkstack.tethering.util.PrefixUtils; |
| import com.android.testutils.DevSdkIgnoreRule; |
| import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; |
| import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; |
| |
| import org.junit.Before; |
| 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 java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| public class IpServerTest { |
| @Rule |
| public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); |
| |
| private static final String IFACE_NAME = "testnet1"; |
| private static final String UPSTREAM_IFACE = "upstream0"; |
| private static final String UPSTREAM_IFACE2 = "upstream1"; |
| private static final String IPSEC_IFACE = "ipsec0"; |
| private static final int UPSTREAM_IFINDEX = 101; |
| private static final int UPSTREAM_IFINDEX2 = 102; |
| private static final int IPSEC_IFINDEX = 103; |
| private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1"; |
| private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24; |
| private static final int DHCP_LEASE_TIME_SECS = 3600; |
| private static final boolean DEFAULT_USING_BPF_OFFLOAD = true; |
| private static final int DEFAULT_SUBNET_PREFIX_LENGTH = 0; |
| private static final int P2P_SUBNET_PREFIX_LENGTH = 25; |
| |
| private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams( |
| IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); |
| private static final InterfaceParams UPSTREAM_IFACE_PARAMS = new InterfaceParams( |
| UPSTREAM_IFACE, UPSTREAM_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); |
| private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams( |
| UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.ALL_ZEROS_ADDRESS, |
| 1500 /* defaultMtu */); |
| private static final InterfaceParams IPSEC_IFACE_PARAMS = new InterfaceParams( |
| IPSEC_IFACE, IPSEC_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); |
| |
| private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; |
| |
| private final LinkAddress mTestAddress = new LinkAddress("192.168.42.5/24"); |
| private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24"); |
| |
| @Mock private INetd mNetd; |
| @Mock private IpServer.Callback mCallback; |
| @Mock private SharedLog mSharedLog; |
| @Mock private IDhcpServer mDhcpServer; |
| @Mock private DadProxy mDadProxy; |
| @Mock private RouterAdvertisementDaemon mRaDaemon; |
| @Mock private IpNeighborMonitor mIpNeighborMonitor; |
| @Mock private IpServer.Dependencies mDependencies; |
| @Mock private PrivateAddressCoordinator mAddressCoordinator; |
| @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; |
| |
| @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor; |
| |
| private final TestLooper mLooper = new TestLooper(); |
| private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = |
| 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); |
| } |
| |
| private void initStateMachine(int interfaceType, boolean usingLegacyDhcp, |
| boolean usingBpfOffload) throws Exception { |
| when(mDependencies.getDadProxy(any(), any())).thenReturn(mDadProxy); |
| when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); |
| when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS); |
| when(mDependencies.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS); |
| when(mDependencies.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2); |
| when(mDependencies.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS); |
| |
| mInterfaceConfiguration = new InterfaceConfigurationParcel(); |
| mInterfaceConfiguration.flags = new String[0]; |
| if (interfaceType == TETHERING_BLUETOOTH) { |
| mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR; |
| mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH; |
| } |
| |
| ArgumentCaptor<NeighborEventConsumer> neighborCaptor = |
| ArgumentCaptor.forClass(NeighborEventConsumer.class); |
| doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), |
| neighborCaptor.capture()); |
| |
| when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload); |
| when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp); |
| when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH); |
| mIpServer = new IpServer( |
| IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, |
| mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies); |
| 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); |
| |
| when(mRaDaemon.start()).thenReturn(true); |
| } |
| |
| private void initTetheredStateMachine(int interfaceType, String upstreamIface) |
| throws Exception { |
| initTetheredStateMachine(interfaceType, upstreamIface, false, |
| DEFAULT_USING_BPF_OFFLOAD); |
| } |
| |
| private void initTetheredStateMachine(int interfaceType, String upstreamIface, |
| boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception { |
| initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload); |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); |
| if (upstreamIface != null) { |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(upstreamIface); |
| dispatchTetherConnectionChanged(upstreamIface, lp, 0); |
| } |
| reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator); |
| when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn( |
| mTestAddress); |
| } |
| |
| private void setUpDhcpServer() throws Exception { |
| doAnswer(inv -> { |
| final IDhcpServerCallbacks cb = inv.getArgument(2); |
| new Thread(() -> { |
| try { |
| cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer); |
| } catch (RemoteException e) { |
| fail(e.getMessage()); |
| } |
| }).run(); |
| return null; |
| }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any()); |
| } |
| |
| @Before public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog); |
| when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn( |
| mTestAddress); |
| when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD); |
| when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */); |
| |
| mBpfDeps = new BpfCoordinator.Dependencies() { |
| @NonNull |
| public Handler getHandler() { |
| return new Handler(mLooper.getLooper()); |
| } |
| |
| @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; |
| } |
| }; |
| mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps)); |
| |
| setUpDhcpServer(); |
| } |
| |
| @Test |
| public void startsOutAvailable() { |
| when(mDependencies.getIpNeighborMonitor(any(), any(), any())) |
| .thenReturn(mIpNeighborMonitor); |
| mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, |
| mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator, |
| mTetheringMetrics, mDependencies); |
| mIpServer.start(); |
| mLooper.dispatchAll(); |
| verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); |
| verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); |
| verifyNoMoreInteractions(mCallback, mNetd); |
| } |
| |
| @Test |
| public void shouldDoNothingUntilRequested() throws Exception { |
| initStateMachine(TETHERING_BLUETOOTH); |
| final int [] noOp_commands = { |
| IpServer.CMD_TETHER_UNREQUESTED, |
| IpServer.CMD_IP_FORWARDING_ENABLE_ERROR, |
| IpServer.CMD_IP_FORWARDING_DISABLE_ERROR, |
| IpServer.CMD_START_TETHERING_ERROR, |
| IpServer.CMD_STOP_TETHERING_ERROR, |
| IpServer.CMD_SET_DNS_FORWARDERS_ERROR, |
| IpServer.CMD_TETHER_CONNECTION_CHANGED |
| }; |
| for (int command : noOp_commands) { |
| // None of these commands should trigger us to request action from |
| // the rest of the system. |
| dispatchCommand(command); |
| verifyNoMoreInteractions(mNetd, mCallback); |
| } |
| } |
| |
| @Test |
| public void handlesImmediateInterfaceDown() throws Exception { |
| initStateMachine(TETHERING_BLUETOOTH); |
| |
| dispatchCommand(IpServer.CMD_INTERFACE_DOWN); |
| verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); |
| verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); |
| verifyNoMoreInteractions(mNetd, mCallback); |
| } |
| |
| @Test |
| public void canBeTetheredAsBluetooth() throws Exception { |
| initStateMachine(TETHERING_BLUETOOTH); |
| |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); |
| InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator); |
| if (isAtLeastT()) { |
| inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true)); |
| inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> |
| IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP))); |
| } |
| inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); |
| inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); |
| // One for ipv4 route, one for ipv6 link local route. |
| inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), |
| any(), any()); |
| inOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); |
| inOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), any(LinkProperties.class)); |
| verifyNoMoreInteractions(mNetd, mCallback); |
| } |
| |
| @Test |
| public void canUnrequestTethering() throws Exception { |
| initTetheredStateMachine(TETHERING_BLUETOOTH, null); |
| |
| dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); |
| InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator); |
| inOrder.verify(mNetd).tetherApplyDnsInterfaces(); |
| inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME); |
| inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME); |
| // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only |
| // happen after T. Before T, the interface configuration control in bluetooth side. |
| if (isAtLeastT()) { |
| inOrder.verify(mNetd).interfaceSetCfg( |
| argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN))); |
| } |
| inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0)); |
| inOrder.verify(mAddressCoordinator).releaseDownstream(any()); |
| inOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); |
| inOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), any(LinkProperties.class)); |
| verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH), |
| eq(TETHER_ERROR_NO_ERROR)); |
| verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH)); |
| verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator); |
| } |
| |
| @Test |
| public void canBeTetheredAsUsb() throws Exception { |
| initStateMachine(TETHERING_USB); |
| |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); |
| InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator); |
| inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true)); |
| inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> |
| IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP))); |
| inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); |
| inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); |
| inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), |
| any(), any()); |
| inOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); |
| inOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), mLinkPropertiesCaptor.capture()); |
| assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); |
| verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator); |
| } |
| |
| @Test |
| public void canBeTetheredAsWifiP2p() throws Exception { |
| initStateMachine(TETHERING_WIFI_P2P); |
| |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY); |
| InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator); |
| inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true)); |
| inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> |
| IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP))); |
| inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); |
| inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); |
| inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), |
| any(), any()); |
| inOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); |
| inOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), mLinkPropertiesCaptor.capture()); |
| assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); |
| verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator); |
| } |
| |
| @Test |
| public void handlesFirstUpstreamChange() throws Exception { |
| initTetheredStateMachine(TETHERING_BLUETOOTH, null); |
| |
| // Telling the state machine about its upstream interface triggers |
| // a little more configuration. |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| InOrder inOrder = inOrder(mNetd, mBpfCoordinator); |
| |
| // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>. |
| inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, |
| UPSTREAM_IFACE); |
| inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); |
| |
| verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator); |
| } |
| |
| @Test |
| public void handlesChangingUpstream() throws Exception { |
| initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); |
| |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE2); |
| InOrder inOrder = inOrder(mNetd, mBpfCoordinator); |
| |
| // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>. |
| inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); |
| |
| // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>. |
| inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, |
| UPSTREAM_IFACE2); |
| inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); |
| |
| verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator); |
| } |
| |
| @Test |
| public void handlesChangingUpstreamNatFailure() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| |
| doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); |
| |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE2); |
| InOrder inOrder = inOrder(mNetd, mBpfCoordinator); |
| |
| // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>. |
| inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); |
| |
| // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on |
| // tetherAddForward. |
| inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, |
| UPSTREAM_IFACE2); |
| inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); |
| |
| // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback. |
| inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2); |
| } |
| |
| @Test |
| public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| |
| doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward( |
| IFACE_NAME, UPSTREAM_IFACE2); |
| |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE2); |
| InOrder inOrder = inOrder(mNetd, mBpfCoordinator); |
| |
| // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>. |
| inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); |
| |
| // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on |
| // ipfwdAddInterfaceForward. |
| inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, |
| UPSTREAM_IFACE2); |
| inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); |
| |
| // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback. |
| inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); |
| inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2); |
| } |
| |
| @Test |
| public void canUnrequestTetheringWithUpstream() throws Exception { |
| initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); |
| |
| dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); |
| InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator); |
| inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); |
| inOrder.verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); |
| inOrder.verify(mNetd).tetherApplyDnsInterfaces(); |
| inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME); |
| inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME); |
| inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg( |
| argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); |
| inOrder.verify(mAddressCoordinator).releaseDownstream(any()); |
| inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer); |
| inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer); |
| inOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); |
| inOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), any(LinkProperties.class)); |
| verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator); |
| } |
| |
| @Test |
| public void interfaceDownLeadsToUnavailable() throws Exception { |
| for (boolean shouldThrow : new boolean[]{true, false}) { |
| initTetheredStateMachine(TETHERING_USB, null); |
| |
| if (shouldThrow) { |
| doThrow(RemoteException.class).when(mNetd).tetherInterfaceRemove(IFACE_NAME); |
| } |
| dispatchCommand(IpServer.CMD_INTERFACE_DOWN); |
| InOrder usbTeardownOrder = inOrder(mNetd, mCallback); |
| // Currently IpServer interfaceSetCfg twice to stop IPv4. One just set interface down |
| // Another one is set IPv4 to 0.0.0.0/0 as clearng ipv4 address. |
| usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg( |
| argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); |
| usbTeardownOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); |
| usbTeardownOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), mLinkPropertiesCaptor.capture()); |
| assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); |
| } |
| } |
| |
| @Test |
| public void usbShouldBeTornDownOnTetherError() throws Exception { |
| initStateMachine(TETHERING_USB); |
| |
| doThrow(RemoteException.class).when(mNetd).tetherInterfaceAdd(IFACE_NAME); |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); |
| InOrder usbTeardownOrder = inOrder(mNetd, mCallback); |
| usbTeardownOrder.verify(mNetd).interfaceSetCfg( |
| argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); |
| usbTeardownOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); |
| |
| usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg( |
| argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); |
| usbTeardownOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); |
| usbTeardownOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), mLinkPropertiesCaptor.capture()); |
| assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); |
| verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB), |
| eq(TETHER_ERROR_TETHER_IFACE_ERROR)); |
| verify(mTetheringMetrics).sendReport(eq(TETHERING_USB)); |
| } |
| |
| @Test |
| public void shouldTearDownUsbOnUpstreamError() throws Exception { |
| initTetheredStateMachine(TETHERING_USB, null); |
| |
| doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString()); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| InOrder usbTeardownOrder = inOrder(mNetd, mCallback); |
| usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); |
| |
| usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg( |
| argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); |
| usbTeardownOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_FORWARDING_ERROR); |
| usbTeardownOrder.verify(mCallback).updateLinkProperties( |
| eq(mIpServer), mLinkPropertiesCaptor.capture()); |
| assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); |
| verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB), |
| eq(TETHER_ERROR_ENABLE_FORWARDING_ERROR)); |
| verify(mTetheringMetrics).sendReport(eq(TETHERING_USB)); |
| } |
| |
| @Test |
| public void ignoresDuplicateUpstreamNotifications() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| |
| verifyNoMoreInteractions(mNetd, mCallback); |
| |
| for (int i = 0; i < 5; i++) { |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| verifyNoMoreInteractions(mNetd, mCallback); |
| } |
| } |
| |
| @Test |
| public void startsDhcpServer() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| |
| assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress)); |
| } |
| |
| @Test |
| public void startsDhcpServerOnBluetooth() throws Exception { |
| initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| |
| if (isAtLeastT()) { |
| assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress)); |
| } else { |
| assertDhcpStarted(mBluetoothPrefix); |
| } |
| } |
| |
| @Test |
| public void startsDhcpServerOnWifiP2p() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI_P2P, UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| |
| assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress)); |
| } |
| |
| @Test |
| public void startsDhcpServerOnNcm() throws Exception { |
| initStateMachine(TETHERING_NCM); |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| |
| assertDhcpStarted(new IpPrefix("192.168.42.0/24")); |
| } |
| |
| @Test |
| public void testOnNewPrefixRequest() throws Exception { |
| initStateMachine(TETHERING_NCM); |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY); |
| |
| final IDhcpEventCallbacks eventCallbacks; |
| final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor = |
| ArgumentCaptor.forClass(IDhcpEventCallbacks.class); |
| verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks( |
| any(), dhcpEventCbsCaptor.capture()); |
| eventCallbacks = dhcpEventCbsCaptor.getValue(); |
| assertDhcpStarted(new IpPrefix("192.168.42.0/24")); |
| |
| final ArgumentCaptor<LinkProperties> lpCaptor = |
| ArgumentCaptor.forClass(LinkProperties.class); |
| InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator); |
| inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true)); |
| inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); |
| // One for ipv4 route, one for ipv6 link local route. |
| inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), |
| any(), any()); |
| inOrder.verify(mCallback).updateInterfaceState( |
| mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); |
| inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture()); |
| verifyNoMoreInteractions(mCallback, mAddressCoordinator); |
| |
| // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals |
| // onNewPrefixRequest callback. |
| final LinkAddress newAddress = new LinkAddress("192.168.100.125/24"); |
| when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn( |
| newAddress); |
| eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24")); |
| mLooper.dispatchAll(); |
| |
| inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(false)); |
| inOrder.verify(mNetd).tetherApplyDnsInterfaces(); |
| inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture()); |
| verifyNoMoreInteractions(mCallback); |
| |
| final LinkProperties linkProperties = lpCaptor.getValue(); |
| final List<LinkAddress> linkAddresses = linkProperties.getLinkAddresses(); |
| assertEquals(1, linkProperties.getLinkAddresses().size()); |
| assertEquals(1, linkProperties.getRoutes().size()); |
| final IpPrefix prefix = new IpPrefix(linkAddresses.get(0).getAddress(), |
| linkAddresses.get(0).getPrefixLength()); |
| assertNotEquals(prefix, new IpPrefix("192.168.42.0/24")); |
| |
| verify(mDhcpServer).updateParams(mDhcpParamsCaptor.capture(), any()); |
| assertDhcpServingParams(mDhcpParamsCaptor.getValue(), prefix); |
| } |
| |
| @Test |
| public void doesNotStartDhcpServerIfDisabled() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */, |
| DEFAULT_USING_BPF_OFFLOAD); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| |
| 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 Ipv6ForwardingRule makeForwardingRule( |
| int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) { |
| return new Ipv6ForwardingRule(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 <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) { |
| if (inOrder != null) { |
| return inOrder.verify(t); |
| } else { |
| return verify(t); |
| } |
| } |
| |
| private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex, |
| @NonNull 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) |
| throws Exception { |
| if (!mBpfDeps.isAtLeastS()) return; |
| final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, |
| TEST_IFACE_PARAMS.macAddr); |
| final Tether6Value value = new Tether6Value(upstreamIfindex, |
| MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, |
| ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); |
| verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value); |
| } |
| |
| private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder) |
| throws Exception { |
| if (!mBpfDeps.isAtLeastS()) return; |
| final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, |
| TEST_IFACE_PARAMS.macAddr); |
| verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key); |
| } |
| |
| 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 { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, 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 netd. |
| recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); |
| verify(mBpfCoordinator).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); |
| verifyTetherOffloadRuleAdd(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); |
| verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); |
| resetNetdBpfMapAndCoordinator(); |
| |
| recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); |
| verify(mBpfCoordinator).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); |
| verifyTetherOffloadRuleAdd(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); |
| verifyNoUpstreamIpv6ForwardingChange(null); |
| 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).tetherOffloadRuleRemove( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull)); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macNull); |
| verifyNoUpstreamIpv6ForwardingChange(null); |
| resetNetdBpfMapAndCoordinator(); |
| |
| // A neighbor that is deleted causes the rule to be removed. |
| recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); |
| verify(mBpfCoordinator).tetherOffloadRuleRemove( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull)); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull); |
| verifyStopUpstreamIpv6Forwarding(null); |
| resetNetdBpfMapAndCoordinator(); |
| |
| // Upstream changes result in updating the rules. |
| recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); |
| verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); |
| recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); |
| resetNetdBpfMapAndCoordinator(); |
| |
| InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(UPSTREAM_IFACE2); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1); |
| verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2); |
| verifyTetherOffloadRuleRemove(inOrder, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); |
| verifyTetherOffloadRuleRemove(inOrder, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); |
| verifyStopUpstreamIpv6Forwarding(inOrder); |
| verifyTetherOffloadRuleAdd(inOrder, |
| UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); |
| verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2); |
| verifyTetherOffloadRuleAdd(inOrder, |
| UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); |
| verifyNoUpstreamIpv6ForwardingChange(inOrder); |
| resetNetdBpfMapAndCoordinator(); |
| |
| // When the upstream is lost, rules are removed. |
| dispatchTetherConnectionChanged(null, null, 0); |
| // Clear function is called two times by: |
| // - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost. |
| // - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost. |
| // See dispatchTetherConnectionChanged. |
| verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); |
| verifyStopUpstreamIpv6Forwarding(inOrder); |
| resetNetdBpfMapAndCoordinator(); |
| |
| // If the upstream is IPv4-only, no rules are added. |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE); |
| resetNetdBpfMapAndCoordinator(); |
| recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); |
| // Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost. |
| verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); |
| verifyNoUpstreamIpv6ForwardingChange(null); |
| verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); |
| |
| // Rules can be added again once upstream IPv6 connectivity is available. |
| lp.setInterfaceName(UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); |
| recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); |
| verify(mBpfCoordinator).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); |
| verifyTetherOffloadRuleAdd(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); |
| verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); |
| verify(mBpfCoordinator, never()).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); |
| verifyNeverTetherOffloadRuleAdd( |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); |
| |
| // If upstream IPv6 connectivity is lost, rules are removed. |
| resetNetdBpfMapAndCoordinator(); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); |
| verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); |
| verifyStopUpstreamIpv6Forwarding(null); |
| |
| // When the interface goes down, rules are removed. |
| lp.setInterfaceName(UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); |
| recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); |
| recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); |
| verify(mBpfCoordinator).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); |
| verifyTetherOffloadRuleAdd(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); |
| verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); |
| verify(mBpfCoordinator).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); |
| verifyTetherOffloadRuleAdd(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); |
| resetNetdBpfMapAndCoordinator(); |
| |
| mIpServer.stop(); |
| mLooper.dispatchAll(); |
| verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); |
| verifyStopUpstreamIpv6Forwarding(null); |
| verify(mIpNeighborMonitor).stop(); |
| resetNetdBpfMapAndCoordinator(); |
| } |
| |
| @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, false /* usingLegacyDhcp */, |
| true /* usingBpfOffload */); |
| resetNetdBpfMapAndCoordinator(); |
| |
| recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); |
| verify(mBpfCoordinator).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA)); |
| verifyTetherOffloadRuleAdd(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macA); |
| verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); |
| resetNetdBpfMapAndCoordinator(); |
| |
| recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); |
| verify(mBpfCoordinator).tetherOffloadRuleRemove( |
| mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull)); |
| verifyTetherOffloadRuleRemove(null, |
| UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macNull); |
| verifyStopUpstreamIpv6Forwarding(null); |
| resetNetdBpfMapAndCoordinator(); |
| |
| // [2] Disable BPF offload. |
| // A neighbor that is added or deleted doesn’t cause the rule to be added or removed. |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, |
| false /* usingBpfOffload */); |
| resetNetdBpfMapAndCoordinator(); |
| |
| recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); |
| verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any()); |
| verifyNeverTetherOffloadRuleAdd(); |
| verifyNoUpstreamIpv6ForwardingChange(null); |
| resetNetdBpfMapAndCoordinator(); |
| |
| recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); |
| verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any()); |
| verifyNeverTetherOffloadRuleRemove(); |
| verifyNoUpstreamIpv6ForwardingChange(null); |
| resetNetdBpfMapAndCoordinator(); |
| } |
| |
| @Test |
| public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, |
| false /* usingBpfOffload */); |
| |
| // IP neighbor monitor doesn't start if BPF offload is disabled. |
| verify(mIpNeighborMonitor, never()).start(); |
| } |
| |
| private LinkProperties buildIpv6OnlyLinkProperties(final String iface) { |
| final LinkProperties linkProp = new LinkProperties(); |
| linkProp.setInterfaceName(iface); |
| linkProp.addLinkAddress(new LinkAddress("2001:db8::1/64")); |
| linkProp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, iface, RTN_UNICAST)); |
| final InetAddress dns = InetAddresses.parseNumericAddress("2001:4860:4860::8888"); |
| linkProp.addDnsServer(dns); |
| |
| return linkProp; |
| } |
| |
| @Test |
| public void testAdjustTtlValue() throws Exception { |
| final ArgumentCaptor<RaParams> raParamsCaptor = |
| ArgumentCaptor.forClass(RaParams.class); |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| verify(mRaDaemon).buildNewRa(any(), raParamsCaptor.capture()); |
| final RaParams noV6Params = raParamsCaptor.getValue(); |
| assertEquals(65, noV6Params.hopLimit); |
| reset(mRaDaemon); |
| |
| when(mNetd.getProcSysNet( |
| INetd.IPV6, INetd.CONF, UPSTREAM_IFACE, "hop_limit")).thenReturn("64"); |
| final LinkProperties lp = buildIpv6OnlyLinkProperties(UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 1); |
| verify(mRaDaemon).buildNewRa(any(), raParamsCaptor.capture()); |
| final RaParams nonCellularParams = raParamsCaptor.getValue(); |
| assertEquals(65, nonCellularParams.hopLimit); |
| reset(mRaDaemon); |
| |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); |
| verify(mRaDaemon).buildNewRa(any(), raParamsCaptor.capture()); |
| final RaParams noUpstream = raParamsCaptor.getValue(); |
| assertEquals(65, nonCellularParams.hopLimit); |
| reset(mRaDaemon); |
| |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); |
| verify(mRaDaemon).buildNewRa(any(), raParamsCaptor.capture()); |
| final RaParams cellularParams = raParamsCaptor.getValue(); |
| assertEquals(63, cellularParams.hopLimit); |
| reset(mRaDaemon); |
| } |
| |
| @Test |
| public void testStopObsoleteDhcpServer() throws Exception { |
| final ArgumentCaptor<DhcpServerCallbacks> cbCaptor = |
| ArgumentCaptor.forClass(DhcpServerCallbacks.class); |
| doNothing().when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), |
| cbCaptor.capture()); |
| initStateMachine(TETHERING_WIFI); |
| dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); |
| verify(mDhcpServer, never()).startWithCallbacks(any(), any()); |
| |
| // No stop dhcp server because dhcp server is not created yet. |
| dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); |
| verify(mDhcpServer, never()).stop(any()); |
| |
| // Stop obsolete dhcp server. |
| try { |
| final DhcpServerCallbacks cb = cbCaptor.getValue(); |
| cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer); |
| mLooper.dispatchAll(); |
| } catch (RemoteException e) { |
| fail(e.getMessage()); |
| } |
| verify(mDhcpServer).stop(any()); |
| } |
| |
| private void assertDhcpServingParams(final DhcpServingParamsParcel params, |
| final IpPrefix prefix) { |
| // Last address byte is random |
| assertTrue(prefix.contains(intToInet4AddressHTH(params.serverAddr))); |
| assertEquals(prefix.getPrefixLength(), params.serverAddrPrefixLength); |
| assertEquals(1, params.defaultRouters.length); |
| assertEquals(params.serverAddr, params.defaultRouters[0]); |
| assertEquals(1, params.dnsServers.length); |
| assertEquals(params.serverAddr, params.dnsServers[0]); |
| assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs); |
| if (mIpServer.interfaceType() == TETHERING_NCM) { |
| assertTrue(params.changePrefixOnDecline); |
| } |
| |
| if (mIpServer.interfaceType() == TETHERING_WIFI_P2P) { |
| assertEquals(P2P_SUBNET_PREFIX_LENGTH, params.leasesSubnetPrefixLength); |
| } else { |
| assertEquals(DEFAULT_SUBNET_PREFIX_LENGTH, params.leasesSubnetPrefixLength); |
| } |
| } |
| |
| private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception { |
| verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any()); |
| verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks( |
| any(), any()); |
| assertDhcpServingParams(mDhcpParamsCaptor.getValue(), expectedPrefix); |
| } |
| |
| /** |
| * Send a command to the state machine under test, and run the event loop to idle. |
| * |
| * @param command One of the IpServer.CMD_* constants. |
| * @param arg1 An additional argument to pass. |
| */ |
| private void dispatchCommand(int command, int arg1) { |
| mIpServer.sendMessage(command, arg1); |
| mLooper.dispatchAll(); |
| } |
| |
| /** |
| * Send a command to the state machine under test, and run the event loop to idle. |
| * |
| * @param command One of the IpServer.CMD_* constants. |
| */ |
| private void dispatchCommand(int command) { |
| mIpServer.sendMessage(command); |
| mLooper.dispatchAll(); |
| } |
| |
| /** |
| * Special override to tell the state machine that the upstream interface has changed. |
| * |
| * @see #dispatchCommand(int) |
| * @param upstreamIface String name of upstream interface (or null) |
| * @param v6lp IPv6 LinkProperties of the upstream interface, or null for an IPv4-only upstream. |
| */ |
| private void dispatchTetherConnectionChanged(String upstreamIface, LinkProperties v6lp, |
| int ttlAdjustment) { |
| dispatchTetherConnectionChanged(upstreamIface); |
| mIpServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, ttlAdjustment, 0, v6lp); |
| mLooper.dispatchAll(); |
| } |
| |
| private void dispatchTetherConnectionChanged(String upstreamIface) { |
| final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null; |
| mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs); |
| mLooper.dispatchAll(); |
| } |
| |
| private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) { |
| // Find the first IPv4 LinkAddress. |
| LinkAddress addr4 = null; |
| for (LinkAddress addr : lp.getLinkAddresses()) { |
| if (!(addr.getAddress() instanceof Inet4Address)) continue; |
| addr4 = addr; |
| break; |
| } |
| assertNotNull("missing IPv4 address", addr4); |
| |
| final IpPrefix destination = new IpPrefix(addr4.getAddress(), addr4.getPrefixLength()); |
| // Assert the presence of the associated directly connected route. |
| final RouteInfo directlyConnected = new RouteInfo(destination, null, lp.getInterfaceName(), |
| RouteInfo.RTN_UNICAST); |
| assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'", |
| lp.getRoutes().contains(directlyConnected)); |
| } |
| |
| private void assertNoAddressesNorRoutes(LinkProperties lp) { |
| assertTrue(lp.getLinkAddresses().isEmpty()); |
| assertTrue(lp.getRoutes().isEmpty()); |
| // We also check that interface name is non-empty, because we should |
| // never see an empty interface name in any LinkProperties update. |
| assertFalse(TextUtils.isEmpty(lp.getInterfaceName())); |
| } |
| |
| private boolean assertContainsFlag(String[] flags, String match) { |
| for (String flag : flags) { |
| if (flag.equals(match)) return true; |
| } |
| return false; |
| } |
| |
| private boolean assertNotContainsFlag(String[] flags, String match) { |
| for (String flag : flags) { |
| if (flag.equals(match)) { |
| fail("Unexpected flag: " + match); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Test @IgnoreUpTo(Build.VERSION_CODES.R) |
| public void dadProxyUpdates() throws Exception { |
| InOrder inOrder = inOrder(mDadProxy); |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); |
| |
| // Add an upstream without IPv6. |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(null); |
| |
| // Add IPv6 to the upstream. |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(UPSTREAM_IFACE); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); |
| |
| // Change upstream. |
| // New linkproperties is needed, otherwise changing the iface has no impact. |
| LinkProperties lp2 = new LinkProperties(); |
| lp2.setInterfaceName(UPSTREAM_IFACE2); |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS2); |
| |
| // Lose IPv6 on the upstream... |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE2, null, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(null); |
| |
| // ... and regain it on a different upstream. |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); |
| |
| // Lose upstream. |
| dispatchTetherConnectionChanged(null, null, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(null); |
| |
| // Regain upstream. |
| dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0); |
| inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); |
| |
| // Stop tethering. |
| mIpServer.stop(); |
| mLooper.dispatchAll(); |
| } |
| |
| private void checkDadProxyEnabled(boolean expectEnabled) throws Exception { |
| initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); |
| InOrder inOrder = inOrder(mDadProxy); |
| // Add IPv6 to the upstream. |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(UPSTREAM_IFACE); |
| if (expectEnabled) { |
| inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); |
| } else { |
| inOrder.verifyNoMoreInteractions(); |
| } |
| // Stop tethering. |
| mIpServer.stop(); |
| mLooper.dispatchAll(); |
| if (expectEnabled) { |
| inOrder.verify(mDadProxy).stop(); |
| } |
| else { |
| verify(mDependencies, never()).getDadProxy(any(), any()); |
| } |
| } |
| @Test @IgnoreAfter(Build.VERSION_CODES.R) |
| public void testDadProxyUpdates_DisabledUpToR() throws Exception { |
| checkDadProxyEnabled(false); |
| } |
| @Test @IgnoreUpTo(Build.VERSION_CODES.R) |
| 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()).tetherOffloadRuleAdd( |
| mIpServer, makeForwardingRule(IPSEC_IFINDEX, neigh, mac)); |
| } |
| } |