blob: db65c2b997938e3ac88bcf648e8abb76238769cb [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.ignoreStubs;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
import android.net.INetd;
import android.net.ISocketKeepaliveCallback;
import android.net.KeepalivePacketData;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MarkMaskParcel;
import android.net.NattKeepalivePacketData;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.SocketKeepalive;
import android.net.TcpKeepalivePacketData;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.connectivity.resources.R;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
import libcore.util.HexEncoding;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public class AutomaticOnOffKeepaliveTrackerTest {
private static final String TAG = AutomaticOnOffKeepaliveTrackerTest.class.getSimpleName();
private static final int TEST_SLOT = 1;
private static final int TEST_NETID = 0xA85;
private static final int TEST_NETID_FWMARK = 0x0A85;
private static final int OTHER_NETID = 0x1A85;
private static final int NETID_MASK = 0xffff;
private static final int TIMEOUT_MS = 30_000;
private static final int MOCK_RESOURCE_ID = 5;
private static final int TEST_KEEPALIVE_INTERVAL_SEC = 10;
private static final int TEST_KEEPALIVE_INVALID_INTERVAL_SEC = 9;
private AutomaticOnOffKeepaliveTracker mAOOKeepaliveTracker;
private HandlerThread mHandlerThread;
@Mock INetd mNetd;
@Mock AutomaticOnOffKeepaliveTracker.Dependencies mDependencies;
@Mock Context mCtx;
@Mock AlarmManager mAlarmManager;
@Mock NetworkAgentInfo mNai;
TestKeepaliveTracker mKeepaliveTracker;
AOOTestHandler mTestHandler;
TestTcpKeepaliveController mTcpController;
// Hexadecimal representation of a SOCK_DIAG response with tcp info.
private static final String SOCK_DIAG_TCP_INET_HEX =
// struct nlmsghdr.
"14010000" + // length = 276
"1400" + // type = SOCK_DIAG_BY_FAMILY
"0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP
"00000000" + // seqno
"00000000" + // pid (0 == kernel)
// struct inet_diag_req_v2
"02" + // family = AF_INET
"06" + // state
"00" + // timer
"00" + // retrans
// inet_diag_sockid
"DEA5" + // idiag_sport = 42462
"71B9" + // idiag_dport = 47473
"0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
"08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
"00000000" + // idiag_if
"34ED000076270000" + // idiag_cookie = 43387759684916
"00000000" + // idiag_expires
"00000000" + // idiag_rqueue
"00000000" + // idiag_wqueue
"00000000" + // idiag_uid
"00000000" + // idiag_inode
// rtattr
"0500" + // len = 5
"0800" + // type = 8
"00000000" + // data
"0800" + // len = 8
"0F00" + // type = 15(INET_DIAG_MARK)
"850A0C00" + // data, socket mark=789125
"AC00" + // len = 172
"0200" + // type = 2(INET_DIAG_INFO)
// tcp_info
"01" + // state = TCP_ESTABLISHED
"00" + // ca_state = TCP_CA_OPEN
"05" + // retransmits = 5
"00" + // probes = 0
"00" + // backoff = 0
"07" + // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
"88" + // wscale = 8
"00" + // delivery_rate_app_limited = 0
"4A911B00" + // rto = 1806666
"00000000" + // ato = 0
"2E050000" + // sndMss = 1326
"18020000" + // rcvMss = 536
"00000000" + // unsacked = 0
"00000000" + // acked = 0
"00000000" + // lost = 0
"00000000" + // retrans = 0
"00000000" + // fackets = 0
"BB000000" + // lastDataSent = 187
"00000000" + // lastAckSent = 0
"BB000000" + // lastDataRecv = 187
"BB000000" + // lastDataAckRecv = 187
"DC050000" + // pmtu = 1500
"30560100" + // rcvSsthresh = 87600
"3E2C0900" + // rttt = 601150
"1F960400" + // rttvar = 300575
"78050000" + // sndSsthresh = 1400
"0A000000" + // sndCwnd = 10
"A8050000" + // advmss = 1448
"03000000" + // reordering = 3
"00000000" + // rcvrtt = 0
"30560100" + // rcvspace = 87600
"00000000" + // totalRetrans = 0
"53AC000000000000" + // pacingRate = 44115
"FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615
"0100000000000000" + // bytesAcked = 1
"0000000000000000" + // bytesReceived = 0
"0A000000" + // SegsOut = 10
"00000000" + // SegsIn = 0
"00000000" + // NotSentBytes = 0
"3E2C0900" + // minRtt = 601150
"00000000" + // DataSegsIn = 0
"00000000" + // DataSegsOut = 0
"0000000000000000"; // deliverRate = 0
private static final String SOCK_DIAG_NO_TCP_INET_HEX =
// struct nlmsghdr
"14000000" // length = 20
+ "0300" // type = NLMSG_DONE
+ "0301" // flags = NLM_F_REQUEST | NLM_F_DUMP
+ "00000000" // seqno
+ "00000000" // pid (0 == kernel)
// struct inet_diag_req_v2
+ "02" // family = AF_INET
+ "06" // state
+ "00" // timer
+ "00"; // retrans
private static final byte[] SOCK_DIAG_NO_TCP_INET_BYTES =
HexEncoding.decode(SOCK_DIAG_NO_TCP_INET_HEX.toCharArray(), false);
private static final String TEST_RESPONSE_HEX =
SOCK_DIAG_TCP_INET_HEX + SOCK_DIAG_NO_TCP_INET_HEX;
private static final byte[] TEST_RESPONSE_BYTES =
HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false);
private static class TestKeepaliveInfo {
private static List<Socket> sOpenSockets = new ArrayList<>();
public static void closeAllSockets() throws Exception {
for (final Socket socket : sOpenSockets) {
socket.close();
}
sOpenSockets.clear();
}
public final Socket socket;
public final Binder binder;
public final FileDescriptor fd;
public final ISocketKeepaliveCallback socketKeepaliveCallback;
public final Network underpinnedNetwork;
public final KeepalivePacketData kpd;
TestKeepaliveInfo(KeepalivePacketData kpd) throws Exception {
this.kpd = kpd;
socket = new Socket();
socket.bind(null);
sOpenSockets.add(socket);
fd = socket.getFileDescriptor$();
binder = new Binder();
socketKeepaliveCallback = mock(ISocketKeepaliveCallback.class);
doReturn(binder).when(socketKeepaliveCallback).asBinder();
underpinnedNetwork = mock(Network.class);
}
}
private class TestKeepaliveTracker extends KeepaliveTracker {
private KeepaliveInfo mKi;
TestKeepaliveTracker(@NonNull final Context context, @NonNull final Handler handler,
@NonNull final TcpKeepaliveController tcpController) {
super(context, handler, tcpController);
}
public void setReturnedKeepaliveInfo(@NonNull final KeepaliveInfo ki) {
mKi = ki;
}
@NonNull
@Override
public KeepaliveInfo makeNattKeepaliveInfo(@Nullable final NetworkAgentInfo nai,
@Nullable final FileDescriptor fd, final int intervalSeconds,
@NonNull final ISocketKeepaliveCallback cb, @NonNull final String srcAddrString,
final int srcPort,
@NonNull final String dstAddrString, final int dstPort) {
if (null == mKi) {
throw new IllegalStateException("Must call setReturnedKeepaliveInfo");
}
return mKi;
}
@NonNull
@Override
public KeepaliveInfo makeTcpKeepaliveInfo(@Nullable final NetworkAgentInfo nai,
@Nullable final FileDescriptor fd, final int intervalSeconds,
@NonNull final ISocketKeepaliveCallback cb) {
if (null == mKi) {
throw new IllegalStateException("Please call `setReturnedKeepaliveInfo`"
+ " before makeTcpKeepaliveInfo is called");
}
return mKi;
}
}
private static class TestTcpKeepaliveController extends TcpKeepaliveController {
TestTcpKeepaliveController(final Handler connectivityServiceHandler) {
super(connectivityServiceHandler);
}
}
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mNai.networkCapabilities =
new NetworkCapabilities.Builder().addTransportType(TRANSPORT_CELLULAR).build();
mNai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");
mNai.networkInfo.setDetailedState(
NetworkInfo.DetailedState.CONNECTED, "test reason", "test extra info");
doReturn(new Network(TEST_NETID)).when(mNai).network();
mNai.linkProperties = new LinkProperties();
doReturn(PERMISSION_GRANTED).when(mCtx).checkPermission(any() /* permission */,
anyInt() /* pid */, anyInt() /* uid */);
ConnectivityResources.setResourcesContextForTest(mCtx);
final Resources mockResources = mock(Resources.class);
doReturn(new String[] { "0,3", "3,3" }).when(mockResources)
.getStringArray(R.array.config_networkSupportedKeepaliveCount);
doReturn(mockResources).when(mCtx).getResources();
doReturn(mNetd).when(mDependencies).getNetd();
doReturn(mAlarmManager).when(mDependencies).getAlarmManager(any());
doReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID_FWMARK)).when(mNetd)
.getFwmarkForNetwork(TEST_NETID);
doNothing().when(mDependencies).sendRequest(any(), any());
mHandlerThread = new HandlerThread("KeepaliveTrackerTest");
mHandlerThread.start();
mTestHandler = new AOOTestHandler(mHandlerThread.getLooper());
mTcpController = new TestTcpKeepaliveController(mTestHandler);
mKeepaliveTracker = new TestKeepaliveTracker(mCtx, mTestHandler, mTcpController);
doReturn(mKeepaliveTracker).when(mDependencies).newKeepaliveTracker(mCtx, mTestHandler);
doReturn(true).when(mDependencies).isFeatureEnabled(any(), anyBoolean());
mAOOKeepaliveTracker =
new AutomaticOnOffKeepaliveTracker(mCtx, mTestHandler, mDependencies);
}
@After
public void teardown() throws Exception {
TestKeepaliveInfo.closeAllSockets();
}
private final class AOOTestHandler extends Handler {
public AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive mLastAutoKi = null;
AOOTestHandler(@NonNull final Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull final Message msg) {
switch (msg.what) {
case AutomaticOnOffKeepaliveTracker.CMD_REQUEST_START_KEEPALIVE:
Log.d(TAG, "Test handler received CMD_REQUEST_START_KEEPALIVE : " + msg);
mAOOKeepaliveTracker.handleStartKeepalive(msg);
break;
case AutomaticOnOffKeepaliveTracker.CMD_MONITOR_AUTOMATIC_KEEPALIVE:
Log.d(TAG, "Test handler received CMD_MONITOR_AUTOMATIC_KEEPALIVE : " + msg);
mLastAutoKi = mAOOKeepaliveTracker.getKeepaliveForBinder((IBinder) msg.obj);
break;
case CMD_STOP_SOCKET_KEEPALIVE:
Log.d(TAG, "Test handler received CMD_STOP_SOCKET_KEEPALIVE : " + msg);
mLastAutoKi = mAOOKeepaliveTracker.getKeepaliveForBinder((IBinder) msg.obj);
if (mLastAutoKi == null) {
fail("Attempt to stop an already stopped keepalive");
}
mAOOKeepaliveTracker.handleStopKeepalive(mLastAutoKi, msg.arg2);
break;
}
}
}
@Test
public void testIsAnyTcpSocketConnected_runOnNonHandlerThread() throws Exception {
setupResponseWithSocketExisting();
assertThrows(IllegalStateException.class,
() -> mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID));
}
@Test
public void testIsAnyTcpSocketConnected_withTargetNetId() throws Exception {
setupResponseWithSocketExisting();
mTestHandler.post(
() -> assertTrue(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
}
@Test
public void testIsAnyTcpSocketConnected_withIncorrectNetId() throws Exception {
setupResponseWithSocketExisting();
mTestHandler.post(
() -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
}
@Test
public void testIsAnyTcpSocketConnected_noSocketExists() throws Exception {
setupResponseWithoutSocketExisting();
mTestHandler.post(
() -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
}
private void triggerEventKeepalive(int slot, int reason) {
visibleOnHandlerThread(
mTestHandler,
() -> mAOOKeepaliveTracker.handleEventSocketKeepalive(mNai, slot, reason));
}
private TestKeepaliveInfo doStartNattKeepalive(int intervalSeconds) throws Exception {
final InetAddress srcAddress = InetAddress.getByAddress(
new byte[] { (byte) 192, 0, 0, (byte) 129 });
final int srcPort = 12345;
final InetAddress dstAddress = InetAddress.getByAddress(new byte[] {8, 8, 8, 8});
final int dstPort = 12345;
mNai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
final NattKeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
dstAddress, dstPort, new byte[] {1});
final TestKeepaliveInfo testInfo = new TestKeepaliveInfo(kpd);
final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(
testInfo.socketKeepaliveCallback, mNai, kpd, intervalSeconds,
KeepaliveInfo.TYPE_NATT, testInfo.fd);
mKeepaliveTracker.setReturnedKeepaliveInfo(ki);
mAOOKeepaliveTracker.startNattKeepalive(mNai, testInfo.fd, intervalSeconds,
testInfo.socketKeepaliveCallback, srcAddress.toString(), srcPort,
dstAddress.toString(), dstPort, true /* automaticOnOffKeepalives */,
testInfo.underpinnedNetwork);
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
return testInfo;
}
private TestKeepaliveInfo doStartNattKeepalive() throws Exception {
return doStartNattKeepalive(TEST_KEEPALIVE_INTERVAL_SEC);
}
private void doPauseKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
setupResponseWithoutSocketExisting();
visibleOnHandlerThread(
mTestHandler,
() -> mAOOKeepaliveTracker.handleMonitorAutomaticKeepalive(autoKi, TEST_NETID));
}
private void doResumeKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
setupResponseWithSocketExisting();
visibleOnHandlerThread(
mTestHandler,
() -> mAOOKeepaliveTracker.handleMonitorAutomaticKeepalive(autoKi, TEST_NETID));
}
private void doStopKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
visibleOnHandlerThread(
mTestHandler,
() -> mAOOKeepaliveTracker.handleStopKeepalive(autoKi, SocketKeepalive.SUCCESS));
}
@Test
public void testAlarm() throws Exception {
// Mock elapsed real time to verify the alarm timer.
final long time = SystemClock.elapsedRealtime();
doReturn(time).when(mDependencies).getElapsedRealtime();
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
final ArgumentCaptor<AlarmManager.OnAlarmListener> listenerCaptor =
ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
// The alarm timer should be smaller than the keepalive delay. Verify the alarm trigger time
// is higher than base time but smaller than the keepalive delay.
verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME),
longThat(t -> t > time + 1000L && t < time + TEST_KEEPALIVE_INTERVAL_SEC * 1000L),
any() /* tag */, listenerCaptor.capture(), eq(mTestHandler));
final AlarmManager.OnAlarmListener listener = listenerCaptor.getValue();
// For realism, the listener should be posted on the handler
mTestHandler.post(() -> listener.onAlarm());
// Wait for the listener to be called. The listener enqueues a message to the handler.
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
// Wait for the message posted by the listener to be processed.
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
assertNotNull(mTestHandler.mLastAutoKi);
assertEquals(testInfo.socketKeepaliveCallback, mTestHandler.mLastAutoKi.getCallback());
assertEquals(testInfo.underpinnedNetwork, mTestHandler.mLastAutoKi.getUnderpinnedNetwork());
}
private void setupResponseWithSocketExisting() throws Exception {
final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES);
final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES);
doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvSockDiagResponse(any());
}
private void setupResponseWithoutSocketExisting() throws Exception {
final ByteBuffer tcpBufferV6 = getByteBuffer(SOCK_DIAG_NO_TCP_INET_BYTES);
final ByteBuffer tcpBufferV4 = getByteBuffer(SOCK_DIAG_NO_TCP_INET_BYTES);
doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvSockDiagResponse(any());
}
private MarkMaskParcel makeMarkMaskParcel(final int mask, final int mark) {
final MarkMaskParcel parcel = new MarkMaskParcel();
parcel.mask = mask;
parcel.mark = mark;
return parcel;
}
private ByteBuffer getByteBuffer(final byte[] bytes) {
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
buffer.order(ByteOrder.nativeOrder());
return buffer;
}
private AutomaticOnOffKeepalive getAutoKiForBinder(IBinder binder) {
return visibleOnHandlerThread(
mTestHandler, () -> mAOOKeepaliveTracker.getKeepaliveForBinder(binder));
}
private void checkAndProcessKeepaliveStart(final KeepalivePacketData kpd) throws Exception {
checkAndProcessKeepaliveStart(TEST_SLOT, kpd);
}
private void checkAndProcessKeepaliveStart(
int slot, final KeepalivePacketData kpd) throws Exception {
verify(mNai).onStartNattSocketKeepalive(
slot, TEST_KEEPALIVE_INTERVAL_SEC, (NattKeepalivePacketData) kpd);
verify(mNai).onAddNattKeepalivePacketFilter(slot, (NattKeepalivePacketData) kpd);
triggerEventKeepalive(slot, SocketKeepalive.SUCCESS);
}
private void checkAndProcessKeepaliveStop() throws Exception {
checkAndProcessKeepaliveStop(TEST_SLOT);
}
private void checkAndProcessKeepaliveStop(int slot) throws Exception {
verify(mNai).onStopSocketKeepalive(slot);
verify(mNai).onRemoveKeepalivePacketFilter(slot);
triggerEventKeepalive(slot, SocketKeepalive.SUCCESS);
}
@Test
public void testStartNattKeepalive_valid() throws Exception {
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
final AutomaticOnOffKeepalive autoKi = getAutoKiForBinder(testInfo.binder);
assertNotNull(autoKi);
assertEquals(testInfo.socketKeepaliveCallback, autoKi.getCallback());
verify(testInfo.socketKeepaliveCallback).onStarted();
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testStartNattKeepalive_invalidInterval() throws Exception {
final TestKeepaliveInfo testInfo =
doStartNattKeepalive(TEST_KEEPALIVE_INVALID_INTERVAL_SEC);
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_INTERVAL);
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testHandleEventSocketKeepalive_startingFailureHardwareError() throws Exception {
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
verify(mNai).onStartNattSocketKeepalive(
TEST_SLOT, TEST_KEEPALIVE_INTERVAL_SEC, (NattKeepalivePacketData) testInfo.kpd);
verify(mNai).onAddNattKeepalivePacketFilter(
TEST_SLOT, (NattKeepalivePacketData) testInfo.kpd);
// Network agent returns an error, fails to start the keepalive.
triggerEventKeepalive(TEST_SLOT, SocketKeepalive.ERROR_HARDWARE_ERROR);
checkAndProcessKeepaliveStop();
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_HARDWARE_ERROR);
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testHandleCheckKeepalivesStillValid_linkPropertiesChanged() throws Exception {
// Successful start of NATT keepalive.
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
verify(testInfo.socketKeepaliveCallback).onStarted();
// Source address is removed from link properties by clearing.
mNai.linkProperties.clear();
// Check for valid keepalives
visibleOnHandlerThread(
mTestHandler, () -> mAOOKeepaliveTracker.handleCheckKeepalivesStillValid(mNai));
checkAndProcessKeepaliveStop();
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testStopKeepalive() throws Exception {
// Successful start of NATT keepalive.
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
verify(testInfo.socketKeepaliveCallback).onStarted();
doStopKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStop();
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onStopped();
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testPauseKeepalive() throws Exception {
// Successful start of NATT keepalive.
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
verify(testInfo.socketKeepaliveCallback).onStarted();
doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStop();
verify(testInfo.socketKeepaliveCallback).onPaused();
// Pausing does not cleanup the autoKi
assertNotNull(getAutoKiForBinder(testInfo.binder));
clearInvocations(mNai);
doStopKeepalive(getAutoKiForBinder(testInfo.binder));
// The keepalive is already stopped.
verify(mNai, never()).onStopSocketKeepalive(TEST_SLOT);
verify(mNai, never()).onRemoveKeepalivePacketFilter(TEST_SLOT);
// Stopping while paused still calls onStopped.
verify(testInfo.socketKeepaliveCallback).onStopped();
// autoKi is cleaned up.
assertNull(getAutoKiForBinder(testInfo.binder));
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testResumeKeepalive() throws Exception {
// Successful start of NATT keepalive.
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
verify(testInfo.socketKeepaliveCallback).onStarted();
doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStop();
verify(testInfo.socketKeepaliveCallback).onPaused();
clearInvocations(mNai);
doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStart(testInfo.kpd);
assertNotNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onResumed();
doStopKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStop();
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onStopped();
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testResumeKeepalive_invalidSourceAddress() throws Exception {
// Successful start of NATT keepalive.
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
verify(testInfo.socketKeepaliveCallback).onStarted();
doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStop();
verify(testInfo.socketKeepaliveCallback).onPaused();
mNai.linkProperties.clear();
clearInvocations(mNai);
doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
verify(mNai, never()).onStartNattSocketKeepalive(anyInt(), anyInt(), any());
verify(mNai, never()).onAddNattKeepalivePacketFilter(anyInt(), any());
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testResumeKeepalive_startingFailureHardwareError() throws Exception {
// Successful start of NATT keepalive.
final TestKeepaliveInfo testInfo = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo.kpd);
verify(testInfo.socketKeepaliveCallback).onStarted();
doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
checkAndProcessKeepaliveStop();
verify(testInfo.socketKeepaliveCallback).onPaused();
clearInvocations(mNai);
doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
verify(mNai).onStartNattSocketKeepalive(
TEST_SLOT, TEST_KEEPALIVE_INTERVAL_SEC, (NattKeepalivePacketData) testInfo.kpd);
verify(mNai).onAddNattKeepalivePacketFilter(
TEST_SLOT, (NattKeepalivePacketData) testInfo.kpd);
// Network agent returns error on starting the keepalive.
triggerEventKeepalive(TEST_SLOT, SocketKeepalive.ERROR_HARDWARE_ERROR);
checkAndProcessKeepaliveStop();
assertNull(getAutoKiForBinder(testInfo.binder));
verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_HARDWARE_ERROR);
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
}
@Test
public void testStopAllKeepalives() throws Exception {
final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
checkAndProcessKeepaliveStart(TEST_SLOT, testInfo1.kpd);
checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo2.kpd);
verify(testInfo1.socketKeepaliveCallback).onStarted();
verify(testInfo2.socketKeepaliveCallback).onStarted();
// Pause the first keepalive
doPauseKeepalive(getAutoKiForBinder(testInfo1.binder));
checkAndProcessKeepaliveStop(TEST_SLOT);
verify(testInfo1.socketKeepaliveCallback).onPaused();
visibleOnHandlerThread(
mTestHandler,
() -> mAOOKeepaliveTracker.handleStopAllKeepalives(
mNai, SocketKeepalive.ERROR_INVALID_NETWORK));
// Note that checkAndProcessKeepaliveStop is not called since the network agent is assumed
// to be disconnected for a handleStopAllKeepalives call.
assertNull(getAutoKiForBinder(testInfo1.binder));
assertNull(getAutoKiForBinder(testInfo2.binder));
verify(testInfo1.socketKeepaliveCallback, never()).onStopped();
verify(testInfo2.socketKeepaliveCallback, never()).onStopped();
verify(testInfo1.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_NETWORK);
verify(testInfo2.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_NETWORK);
verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
}
@Test
public void testTwoKeepalives_startAfterPause() throws Exception {
final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo1.kpd);
verify(testInfo1.socketKeepaliveCallback).onStarted();
assertNotNull(getAutoKiForBinder(testInfo1.binder));
final AutomaticOnOffKeepalive autoKi1 = getAutoKiForBinder(testInfo1.binder);
doPauseKeepalive(autoKi1);
checkAndProcessKeepaliveStop(TEST_SLOT);
verify(testInfo1.socketKeepaliveCallback).onPaused();
assertNotNull(getAutoKiForBinder(testInfo1.binder));
clearInvocations(mNai);
// Start the second keepalive while the first is paused.
final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
// The slot used is TEST_SLOT since it is now a free slot.
checkAndProcessKeepaliveStart(TEST_SLOT, testInfo2.kpd);
verify(testInfo2.socketKeepaliveCallback).onStarted();
assertNotNull(getAutoKiForBinder(testInfo2.binder));
clearInvocations(mNai);
doResumeKeepalive(autoKi1);
// The next free slot is TEST_SLOT + 1.
checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo1.kpd);
verify(testInfo1.socketKeepaliveCallback).onResumed();
clearInvocations(mNai);
doStopKeepalive(autoKi1);
// TODO: The slot should be consistent with the checkAndProcessKeepaliveStart directly above
checkAndProcessKeepaliveStop(TEST_SLOT);
// TODO: onStopped should only be called on the first keepalive callback.
verify(testInfo1.socketKeepaliveCallback, never()).onStopped();
verify(testInfo2.socketKeepaliveCallback).onStopped();
assertNull(getAutoKiForBinder(testInfo1.binder));
clearInvocations(mNai);
assertNotNull(getAutoKiForBinder(testInfo2.binder));
doStopKeepalive(getAutoKiForBinder(testInfo2.binder));
// This slot should be consistent with its corresponding checkAndProcessKeepaliveStart.
// TODO: checkAndProcessKeepaliveStop should be called instead but the keepalive is
// unexpectedly already stopped above.
verify(mNai, never()).onStopSocketKeepalive(TEST_SLOT);
verify(mNai, never()).onRemoveKeepalivePacketFilter(TEST_SLOT);
verify(testInfo2.socketKeepaliveCallback).onStopped();
assertNull(getAutoKiForBinder(testInfo2.binder));
verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
}
@Test
public void testStartTcpKeepalive_fdInitiatedStop() throws Exception {
final InetAddress srcAddress = InetAddress.getByAddress(
new byte[] { (byte) 192, 0, 0, (byte) 129 });
mNai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
final KeepalivePacketData kpd = new TcpKeepalivePacketData(
InetAddress.getByAddress(new byte[] { (byte) 192, 0, 0, (byte) 129 }) /* srcAddr */,
12345 /* srcPort */,
InetAddress.getByAddress(new byte[] { 8, 8, 8, 8}) /* dstAddr */,
12345 /* dstPort */, new byte[] {1}, 111 /* tcpSeq */,
222 /* tcpAck */, 800 /* tcpWindow */, 2 /* tcpWindowScale */,
4 /* ipTos */, 64 /* ipTtl */);
final TestKeepaliveInfo testInfo = new TestKeepaliveInfo(kpd);
final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(
testInfo.socketKeepaliveCallback, mNai, kpd,
TEST_KEEPALIVE_INTERVAL_SEC, KeepaliveInfo.TYPE_TCP, testInfo.fd);
mKeepaliveTracker.setReturnedKeepaliveInfo(ki);
// Setup TCP keepalive.
mAOOKeepaliveTracker.startTcpKeepalive(mNai, testInfo.fd, TEST_KEEPALIVE_INTERVAL_SEC,
testInfo.socketKeepaliveCallback);
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
// A closed socket will result in EVENT_HANGUP and trigger error to
// FileDescriptorEventListener.
testInfo.socket.close();
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
// The keepalive should be removed in AutomaticOnOffKeepaliveTracker.
getAutoKiForBinder(testInfo.binder);
}
}