/* * 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_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.net.netlink.NetlinkConstants.RTM_DELNEIGH; import static android.net.netlink.NetlinkConstants.RTM_NEWNEIGH; import static android.net.netlink.StructNdMsg.NUD_FAILED; import static android.net.netlink.StructNdMsg.NUD_REACHABLE; import static android.net.netlink.StructNdMsg.NUD_STALE; import static android.system.OsConstants.ETH_P_IPV6; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; 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.InterfaceParams; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; 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.NetworkStackConstants; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.BpfMap; import com.android.networkstack.tethering.PrivateAddressCoordinator; import com.android.networkstack.tethering.Tether4Key; import com.android.networkstack.tethering.Tether4Value; import com.android.networkstack.tethering.Tether6Value; import com.android.networkstack.tethering.TetherDevKey; import com.android.networkstack.tethering.TetherDevValue; import com.android.networkstack.tethering.TetherDownstream6Key; import com.android.networkstack.tethering.TetherLimitKey; import com.android.networkstack.tethering.TetherLimitValue; import com.android.networkstack.tethering.TetherStatsKey; import com.android.networkstack.tethering.TetherStatsValue; import com.android.networkstack.tethering.TetherUpstream6Key; import com.android.networkstack.tethering.TetheringConfiguration; 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 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 BpfMap mBpfDownstream4Map; @Mock private BpfMap mBpfUpstream4Map; @Mock private BpfMap mBpfDownstream6Map; @Mock private BpfMap mBpfUpstream6Map; @Mock private BpfMap mBpfStatsMap; @Mock private BpfMap mBpfLimitMap; @Mock private BpfMap mBpfDevMap; @Captor private ArgumentCaptor mDhcpParamsCaptor; private final TestLooper mLooper = new TestLooper(); private final ArgumentCaptor 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 neighborCaptor = ArgumentCaptor.forClass(NeighborEventConsumer.class); doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), neighborCaptor.capture()); mIpServer = new IpServer( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, 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(true /* 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 getBpfDownstream4Map() { return mBpfDownstream4Map; } @Nullable public BpfMap getBpfUpstream4Map() { return mBpfUpstream4Map; } @Nullable public BpfMap getBpfDownstream6Map() { return mBpfDownstream6Map; } @Nullable public BpfMap getBpfUpstream6Map() { return mBpfUpstream6Map; } @Nullable public BpfMap getBpfStatsMap() { return mBpfStatsMap; } @Nullable public BpfMap getBpfLimitMap() { return mBpfLimitMap; } @Nullable public BpfMap getBpfDevMap() { return mBpfDevMap; } }; 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, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, 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 canBeTethered() throws Exception { initStateMachine(TETHERING_BLUETOOTH); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); InOrder inOrder = inOrder(mCallback, mNetd); 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); inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); 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)); 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 . 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 . 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 . 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 . 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 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 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 . 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 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 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).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()); } @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()); } @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); 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 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 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 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 captor = ArgumentCaptor.forClass(FooParam.class); * verify(mMock).foo(captor.capture()); * Foo foo = captor.getValue(); * assertFooMatchesExpectations(foo); * } * * almost works, but not quite. This is because if the code under test calls foo() twice, the * first call to checkFooCalled() matches both the calls, putting both calls into the captor, * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito * features such as never(), inOrder(), etc. * * This approach isn't great because if the match fails, the error message is unhelpful * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does * work. * * TODO: consider making the error message more readable by adding a method that catching the * AssertionFailedError and throwing a new assertion with more details. See * NetworkMonitorTest#verifyNetworkTested. * * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the * TooManyActualInvocations problem described above by forcing the caller of the custom assert * method to specify all expected invocations in one call. This is useful when the stable * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here * because there is no such object. */ private static class TetherOffloadRuleParcelMatcher implements ArgumentMatcher { public final int upstreamIfindex; public final InetAddress dst; public final MacAddress dstMac; TetherOffloadRuleParcelMatcher(int upstreamIfindex, InetAddress dst, MacAddress dstMac) { this.upstreamIfindex = upstreamIfindex; this.dst = dst; this.dstMac = dstMac; } public boolean matches(TetherOffloadRuleParcel parcel) { return upstreamIfindex == parcel.inputInterfaceIndex && (TEST_IFACE_PARAMS.index == parcel.outputInterfaceIndex) && Arrays.equals(dst.getAddress(), parcel.destination) && (128 == parcel.prefixLength) && Arrays.equals(TEST_IFACE_PARAMS.macAddr.toByteArray(), parcel.srcL2Address) && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); } public String toString() { return String.format("TetherOffloadRuleParcelMatcher(%d, %s, %s", upstreamIfindex, dst.getHostAddress(), dstMac); } } @NonNull private static TetherOffloadRuleParcel matches( int upstreamIfindex, InetAddress dst, MacAddress dstMac) { return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac)); } @NonNull private static 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 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 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 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); } } 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; } fail("Missing flag: " + match); 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)); } }