/* * Copyright (C) 2019 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.internal.net.ipsec.ike; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.Context; import android.net.IpSecManager; import android.net.IpSecManager.UdpEncapsulationSocket; import android.os.HandlerThread; import android.os.Looper; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.Log; import android.util.LongSparseArray; import androidx.test.InstrumentationRegistry; import com.android.internal.net.TestUtils; import com.android.internal.net.ipsec.ike.IkeSocket.PacketReceiver; import com.android.internal.net.ipsec.ike.message.IkeHeader; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.io.FileDescriptor; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public final class IkeSocketTest { private static final int REMOTE_RECV_BUFF_SIZE = 2048; private static final int TIMEOUT = 1000; private static final String NON_ESP_MARKER_HEX_STRING = "00000000"; private static final String IKE_REQ_MESSAGE_HEX_STRING = "5f54bf6d8b48e6e100000000000000002120220800000000" + "00000150220000300000002c010100040300000c0100000c" + "800e00800300000803000002030000080400000200000008" + "020000022800008800020000b4a2faf4bb54878ae21d6385" + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656" + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37" + "534036040610ebdd92f46bef84f0be7db860351843858f8a" + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb" + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024" + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb" + "881eab2051d8663f421d10b02b00001c00004005d915368c" + "a036004cb578ae3e3fb268509aeab1900000002069936922" + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500"; private static final String LOCAL_SPI = "0000000000000000"; private static final String REMOTE_SPI = "5f54bf6d8b48e6e1"; private static final String DATA_ONE = "one 1"; private static final String DATA_TWO = "two 2"; private static final String IPV4_LOOPBACK = "127.0.0.1"; private byte[] mDataOne; private byte[] mDataTwo; private long mLocalSpi; private long mRemoteSpi; private LongSparseArray mSpiToIkeStateMachineMap; private PacketReceiver mPacketReceiver; private UdpEncapsulationSocket mClientUdpEncapSocket; private InetAddress mLocalAddress; private FileDescriptor mDummyRemoteServerFd; private IkeSessionStateMachine mMockIkeSessionStateMachine; @Before public void setUp() throws Exception { Context context = InstrumentationRegistry.getContext(); IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE); mClientUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket(); mLocalAddress = InetAddress.getByName(IPV4_LOOPBACK); mDummyRemoteServerFd = getBoundUdpSocket(mLocalAddress); mDataOne = DATA_ONE.getBytes("UTF-8"); mDataTwo = DATA_TWO.getBytes("UTF-8"); ByteBuffer localSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(LOCAL_SPI)); mLocalSpi = localSpiBuffer.getLong(); ByteBuffer remoteSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(REMOTE_SPI)); mRemoteSpi = remoteSpiBuffer.getLong(); mMockIkeSessionStateMachine = mock(IkeSessionStateMachine.class); mSpiToIkeStateMachineMap = new LongSparseArray(); mSpiToIkeStateMachineMap.put(mLocalSpi, mMockIkeSessionStateMachine); mPacketReceiver = new IkeSocket.PacketReceiver(); } @After public void tearDown() throws Exception { mClientUdpEncapSocket.close(); IkeSocket.setPacketReceiver(mPacketReceiver); Os.close(mDummyRemoteServerFd); } private static FileDescriptor getBoundUdpSocket(InetAddress address) throws Exception { FileDescriptor sock = Os.socket(OsConstants.AF_INET, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP); Os.bind(sock, address, IkeSocket.IKE_SERVER_PORT); return sock; } @Test public void testGetAndCloseIkeSocket() throws Exception { // Must be prepared here; AndroidJUnitRunner runs tests on different threads from the // setUp() call. Since the new Handler() call is run in getIkeSocket, the Looper must be // prepared here. if (Looper.myLooper() == null) Looper.prepare(); IkeSessionStateMachine mMockIkeSessionOne = mock(IkeSessionStateMachine.class); IkeSessionStateMachine mMockIkeSessionTwo = mock(IkeSessionStateMachine.class); IkeSocket ikeSocketOne = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionOne); assertEquals(1, ikeSocketOne.mAliveIkeSessions.size()); IkeSocket ikeSocketTwo = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionTwo); assertEquals(ikeSocketOne, ikeSocketTwo); assertEquals(2, ikeSocketTwo.mAliveIkeSessions.size()); ikeSocketOne.releaseReference(mMockIkeSessionOne); assertEquals(1, ikeSocketOne.mAliveIkeSessions.size()); ikeSocketTwo.releaseReference(mMockIkeSessionTwo); assertEquals(0, ikeSocketTwo.mAliveIkeSessions.size()); } @Test public void testSendIkePacket() throws Exception { if (Looper.myLooper() == null) Looper.prepare(); // Send IKE packet IkeSocket ikeSocket = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionStateMachine); ikeSocket.sendIkePacket(mDataOne, mLocalAddress); byte[] receivedData = receive(mDummyRemoteServerFd); // Verify received data ByteBuffer expectedBuffer = ByteBuffer.allocate(IkeSocket.NON_ESP_MARKER_LEN + mDataOne.length); expectedBuffer.put(IkeSocket.NON_ESP_MARKER).put(mDataOne); assertArrayEquals(expectedBuffer.array(), receivedData); ikeSocket.releaseReference(mMockIkeSessionStateMachine); } @Test public void testReceiveIkePacket() throws Exception { // Create working thread. HandlerThread mIkeThread = new HandlerThread("IkeSocketTest"); mIkeThread.start(); // Create IkeSocket on working thread. IkeSocketReceiver socketReceiver = new IkeSocketReceiver(); TestCountDownLatch createLatch = new TestCountDownLatch(); mIkeThread .getThreadHandler() .post( () -> { try { socketReceiver.setIkeSocket( IkeSocket.getIkeSocket( mClientUdpEncapSocket, mMockIkeSessionStateMachine)); createLatch.countDown(); Log.d("IkeSocketTest", "IkeSocket created."); } catch (ErrnoException e) { Log.e("IkeSocketTest", "error encountered creating IkeSocket ", e); } }); createLatch.await(); IkeSocket ikeSocket = socketReceiver.getIkeSocket(); assertNotNull(ikeSocket); // Configure IkeSocket TestCountDownLatch receiveLatch = new TestCountDownLatch(); DummyPacketReceiver packetReceiver = new DummyPacketReceiver(receiveLatch); IkeSocket.setPacketReceiver(packetReceiver); // Send first packet. sendToIkeSocket(mDummyRemoteServerFd, mDataOne, mLocalAddress); receiveLatch.await(); assertEquals(1, ikeSocket.numPacketsReceived()); assertArrayEquals(mDataOne, packetReceiver.mReceivedData); // Send second packet. sendToIkeSocket(mDummyRemoteServerFd, mDataTwo, mLocalAddress); receiveLatch.await(); assertEquals(2, ikeSocket.numPacketsReceived()); assertArrayEquals(mDataTwo, packetReceiver.mReceivedData); // Close IkeSocket. TestCountDownLatch closeLatch = new TestCountDownLatch(); ikeSocket .getHandler() .post( () -> { ikeSocket.releaseReference(mMockIkeSessionStateMachine); closeLatch.countDown(); }); closeLatch.await(); mIkeThread.quitSafely(); } @Test public void testHandlePacket() throws Exception { byte[] recvBuf = TestUtils.hexStringToByteArray( NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING); mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); byte[] expectedIkePacketBytes = TestUtils.hexStringToByteArray(IKE_REQ_MESSAGE_HEX_STRING); ArgumentCaptor ikeHeaderCaptor = ArgumentCaptor.forClass(IkeHeader.class); verify(mMockIkeSessionStateMachine) .receiveIkePacket(ikeHeaderCaptor.capture(), eq(expectedIkePacketBytes)); IkeHeader capturedIkeHeader = ikeHeaderCaptor.getValue(); assertEquals(mRemoteSpi, capturedIkeHeader.ikeInitiatorSpi); assertEquals(mLocalSpi, capturedIkeHeader.ikeResponderSpi); } @Test public void testHandleEspPacket() throws Exception { byte[] recvBuf = TestUtils.hexStringToByteArray( NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING); // Modify Non-ESP Marker recvBuf[0] = 1; mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any()); } @Test public void testHandlePacketWithMalformedHeader() throws Exception { String malformedIkePacketHexString = "5f54bf6d8b48e6e100000000000000002120220800000000"; byte[] recvBuf = TestUtils.hexStringToByteArray( NON_ESP_MARKER_HEX_STRING + malformedIkePacketHexString); mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any()); } private byte[] receive(FileDescriptor mfd) throws Exception { byte[] receiveBuffer = new byte[REMOTE_RECV_BUFF_SIZE]; AtomicInteger bytesRead = new AtomicInteger(-1); Thread receiveThread = new Thread( () -> { while (bytesRead.get() < 0) { try { bytesRead.set( Os.recvfrom( mDummyRemoteServerFd, receiveBuffer, 0, REMOTE_RECV_BUFF_SIZE, 0, null)); } catch (Exception e) { Log.e( "IkeSocketTest", "Error encountered reading from socket", e); } } Log.d( "IkeSocketTest", "Packet received with size of " + bytesRead.get()); }); receiveThread.start(); receiveThread.join(TIMEOUT); return Arrays.copyOfRange(receiveBuffer, 0, bytesRead.get()); } private void sendToIkeSocket(FileDescriptor fd, byte[] data, InetAddress destAddress) throws Exception { Os.sendto(fd, data, 0, data.length, 0, destAddress, mClientUdpEncapSocket.getPort()); } private static class IkeSocketReceiver { private IkeSocket mIkeSocket; void setIkeSocket(IkeSocket ikeSocket) { mIkeSocket = ikeSocket; } IkeSocket getIkeSocket() { return mIkeSocket; } } private static class DummyPacketReceiver implements IkeSocket.IPacketReceiver { byte[] mReceivedData = null; final TestCountDownLatch mLatch; DummyPacketReceiver(TestCountDownLatch latch) { mLatch = latch; } public void handlePacket( byte[] revbuf, LongSparseArray spiToIkeSession) { mReceivedData = Arrays.copyOfRange(revbuf, 0, revbuf.length); mLatch.countDown(); Log.d("IkeSocketTest", "Packet received"); } } private static class TestCountDownLatch { private CountDownLatch mLatch; TestCountDownLatch() { reset(); } private void reset() { mLatch = new CountDownLatch(1); } void countDown() { mLatch.countDown(); } void await() { try { if (!mLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { fail("Time out"); } } catch (InterruptedException e) { fail(e.toString()); } reset(); } } }