From 188e220da77a03f24b717068027e05482bafcdab Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Thu, 28 May 2020 02:23:21 +0000 Subject: [PATCH] Test remotely initiated rekey This commit: - Adds test for remotely initiated rekey for IKE and Child SA - Makes IkeTunUtils support building and injecting requests - Adds method to parse hex to long because Long.parseLong cannot handle negative long value Bug: 155821007 Test: atest CtsIkeTestCases Change-Id: I299cf190261ac15397f9ed389adb2c69e94a6507 Merged-In: I299cf190261ac15397f9ed389adb2c69e94a6507 (cherry picked from commit a5bff5a7b46c31dfa4ae692328edfb2b2a6462c8) --- .../net/ipsec/ike/cts/IkeSessionPskTest.java | 2 +- .../ipsec/ike/cts/IkeSessionRekeyTest.java | 265 ++++++++++++++++++ .../net/ipsec/ike/cts/IkeSessionTestBase.java | 23 +- .../net/ipsec/ike/cts/IkeTunUtils.java | 70 ++++- 4 files changed, 342 insertions(+), 18 deletions(-) create mode 100644 tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java index ef865afe9a..233e1c9ad5 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java @@ -220,5 +220,5 @@ public class IkeSessionPskTest extends IkeSessionTestBase { assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData()); } - // TODO(b/155821007): Verify rekey process and handling IKE_AUTH failure + // TODO(b/155821007): Verify handling IKE_AUTH failure } diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java new file mode 100644 index 0000000000..f954fcd031 --- /dev/null +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2020 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.ipsec.ike.cts; + +import static com.android.internal.util.HexDump.hexStringToByteArray; + +import android.net.InetAddresses; +import android.net.LinkAddress; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeSession; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.cts.IkeTunUtils.PortPair; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Explicitly test transport mode Child SA so that devices without FEATURE_IPSEC_TUNNELS can be test + * covered. Tunnel mode Child SA setup has been tested in IkeSessionPskTest. Rekeying process is + * independent from Child SA mode. + */ +@RunWith(AndroidJUnit4.class) +public class IkeSessionRekeyTest extends IkeSessionTestBase { + // This value is align with the test vectors hex that are generated in an IPv4 environment + private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS = + new IkeTrafficSelector( + MIN_PORT, + MAX_PORT, + InetAddresses.parseNumericAddress("172.58.35.40"), + InetAddresses.parseNumericAddress("172.58.35.40")); + + private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) { + IkeSessionParams ikeParams = + new IkeSessionParams.Builder(sContext) + .setNetwork(mTunNetwork) + .setServerHostname(remoteAddress.getHostAddress()) + .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher()) + .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher()) + .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME)) + .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME)) + .setAuthPsk(IKE_PSK) + .build(); + return new IkeSession( + sContext, + ikeParams, + buildTransportModeChildParamsWithTs( + TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS), + mUserCbExecutor, + mIkeSessionCallback, + mFirstChildSessionCallback); + } + + private byte[] buildInboundPkt(PortPair outPktSrcDestPortPair, String inboundDataHex) + throws Exception { + // Build inbound packet by flipping the outbound packet addresses and ports + return IkeTunUtils.buildIkePacket( + mRemoteAddress, + mLocalAddress, + outPktSrcDestPortPair.dstPort, + outPktSrcDestPortPair.srcPort, + true /* useEncap */, + hexStringToByteArray(inboundDataHex)); + } + + @Test + public void testRekeyIke() throws Exception { + final String ikeInitResp = + "46B8ECA1E0D72A1866B5248CF6C7472D21202220000000000000015022000030" + + "0000002C010100040300000C0100000C800E0100030000080300000C03000008" + + "0200000500000008040000022800008800020000920D3E830E7276908209212D" + + "E5A7F2A48706CFEF1BE8CB6E3B173B8B4E0D8C2DC626271FF1B13A88619E569E" + + "7B03C3ED2C127390749CDC7CDC711D0A8611E4457FFCBC4F0981B3288FBF58EA" + + "3E8B70E27E76AE70117FBBCB753660ADDA37EB5EB3A81BED6A374CCB7E132C2A" + + "94BFCE402DC76B19C158B533F6B1F2ABF01ACCC329000024B302CA2FB85B6CF4" + + "02313381246E3C53828D787F6DFEA6BD62D6405254AEE6242900001C00004004" + + "7A1682B06B58596533D00324886EF1F20EF276032900001C00004005BF633E31" + + "F9984B29A62E370BB2770FC09BAEA665290000080000402E290000100000402F" + + "00020003000400050000000800004014"; + final String ikeAuthResp = + "46B8ECA1E0D72A1866B5248CF6C7472D2E20232000000001000000F0240000D4" + + "10166CA8647F56123DE74C17FA5E256043ABF73216C812EE32EE1BB01EAF4A82" + + "DC107AB3ADBFEE0DEA5EEE10BDD5D43178F4C975C7C775D252273BB037283C7F" + + "236FE34A6BCE4833816897075DB2055B9FFD66DFA45A0A89A8F70AFB59431EED" + + "A20602FB614369D12906D3355CF7298A5D25364ABBCC75A9D88E0E6581449FCD" + + "4E361A39E00EFD1FD0A69651F63DB46C12470226AA21BA5EFF48FAF0B6DDF61C" + + "B0A69392CE559495EEDB4D1C1D80688434D225D57210A424C213F7C993D8A456" + + "38153FBD194C5E247B592D1D048DB4C8"; + final String rekeyIkeCreateReq = + "46B8ECA1E0D72A1866B5248CF6C7472D2E202400000000000000013021000114" + + "13743670039E308A8409BA5FD47B67F956B36FEE88AC3B70BB5D789B8218A135" + + "1B3D83E260E87B3EDB1BF064F09D4DC2611AEDBC99951B4B2DE767BD4AA2ACC3" + + "3653549CFC66B75869DF003CDC9A137A9CC27776AD5732B34203E74BE8CA4858" + + "1D5C0D9C9CA52D680EB299B4B21C7FA25FFEE174D57015E0FF2EAED653AAD95C" + + "071ABE269A8C2C9FBC1188E07550EB992F910D4CA9689E44BA66DE0FABB2BDF9" + + "8DD377186DBB25EF9B68B027BB2A27981779D8303D88D7CE860010A42862D50B" + + "1E0DBFD3D27C36F14809D7F493B2B96A65534CF98B0C32AD5219AD77F681AC04" + + "9D5CB89A0230A91A243FA7F16251B0D9B4B65E7330BEEAC9663EF4578991EAC8" + + "46C19EBB726E7D113F1D0D601102C05E"; + final String rekeyIkeDeleteReq = + "46B8ECA1E0D72A1866B5248CF6C7472D2E20250000000001000000502A000034" + + "02E40C0C7B1ED977729F705BB9B643FAC513A1070A6EB28ECD2AEA8A441ADC05" + + "7841382A7967BBF116AE52496590B2AD"; + final String deleteIkeReq = + "7D3DEDC65407D1FC9361C8CF8C47162A2E20250800000000000000502A000034" + + "201915C9E4E9173AA9EE79F3E02FE2D4954B22085C66D164762C34D347C16E9F" + + "FC5F7F114428C54D8D915860C57B1BC1"; + final long newIkeDeterministicInitSpi = Long.parseLong("7D3DEDC65407D1FC", 16); + + // Open IKE Session + IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress); + PortPair localRemotePorts = performSetupIkeAndFirstChildBlocking(ikeInitResp, ikeAuthResp); + + // Local request message ID starts from 2 because there is one IKE_INIT message and a single + // IKE_AUTH message. + int expectedReqMsgId = 2; + int expectedRespMsgId = 0; + + verifyIkeSessionSetupBlocking(); + verifyChildSessionSetupBlocking( + mFirstChildSessionCallback, + Arrays.asList(TRANSPORT_MODE_INBOUND_TS), + Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS), + new ArrayList()); + IpSecTransformCallRecord firstTransformRecordA = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + IpSecTransformCallRecord firstTransformRecordB = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB); + + // Inject rekey IKE requests + mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeCreateReq)); + mTunUtils.awaitResp( + IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */); + mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeDeleteReq)); + mTunUtils.awaitResp( + IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */); + + // IKE has been rekeyed, reset message IDs + expectedReqMsgId = 0; + expectedRespMsgId = 0; + + // Inject delete IKE request + mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq)); + mTunUtils.awaitResp( + newIkeDeterministicInitSpi, expectedRespMsgId++, true /* expectedUseEncap */); + + verifyDeleteIpSecTransformPair( + mFirstChildSessionCallback, firstTransformRecordA, firstTransformRecordB); + mFirstChildSessionCallback.awaitOnClosed(); + mIkeSessionCallback.awaitOnClosed(); + } + + @Test + public void testRekeyTransportModeChildSa() throws Exception { + final String ikeInitResp = + "46B8ECA1E0D72A18CECD871146CF83A121202220000000000000015022000030" + + "0000002C010100040300000C0100000C800E0100030000080300000C03000008" + + "0200000500000008040000022800008800020000C4904458957746BCF1C12972" + + "1D4E19EB8A584F78DE673053396D167CE0F34552DBC69BA63FE7C673B4CF4A99" + + "62481518EE985357876E8C47BAAA0DBE9C40AE47B12E52165874703586E8F786" + + "045F72EEEB238C5D1823352BED44B71B3214609276ADC0B3D42DAC820168C4E2" + + "660730DAAC92492403288805EBB9053F1AB060DA290000242D9364ACB93519FF" + + "8F8B019BAA43A40D699F59714B327B8382216EF427ED52282900001C00004004" + + "06D91438A0D6B734E152F76F5CC55A72A2E38A0A2900001C000040052EFF78B3" + + "55B37F3CE75AFF26C721B050F892C0D6290000080000402E290000100000402F" + + "00020003000400050000000800004014"; + final String ikeAuthResp = + "46B8ECA1E0D72A18CECD871146CF83A12E20232000000001000000F0240000D4" + + "A17BC258BA2714CF536663639DD5F665A60C75E93557CD5141990A8CEEDD2017" + + "93F5B181C8569FBCD6C2A00198EC2B62D42BEFAC016B8B6BF6A7BC9CEDE3413A" + + "6C495A6B8EC941864DC3E08F57D015EA6520C4B05884960B85478FCA53DA5F17" + + "9628BB1097DA77461C71837207A9EB80720B3E6E661816EE4E14AC995B5E8441" + + "A4C3F9097CC148142BA300076C94A23EC4ADE82B1DD2B121F7E9102860A8C3BF" + + "58DDC207285A3176E924C44DE820322524E1AA438EFDFBA781B36084AED80846" + + "3B77FCED9682B6E4E476408EF3F1037E"; + final String rekeyChildCreateReq = + "46B8ECA1E0D72A18CECD871146CF83A12E202400000000000000015029000134" + + "319D74B6B155B86942143CEC1D29D21F073F24B7BEDC9BFE0F0FDD8BDB5458C0" + + "8DB93506E1A43DD0640FE7370C97F9B34FF4EC9B2DB7257A87B75632301FB68A" + + "86B54871249534CA3D01C9BEB127B669F46470E1C8AAF72574C3CEEC15B901CF" + + "5A0D6ADAE59C3CA64AC8C86689C860FAF9500E608DFE63F2DCD30510FD6FFCD5" + + "A50838574132FD1D069BCACD4C7BAF45C9B1A7689FAD132E3F56DBCFAF905A8C" + + "4145D4BA1B74A54762F8F43308D94DE05649C49D885121CE30681D51AC1E3E68" + + "AB82F9A19B99579AFE257F32DBD1037814DA577379E4F42DEDAC84502E49C933" + + "9EA83F6F5DB4401B660CB1681B023B8603D205DFDD1DE86AD8DE22B6B754F30D" + + "05EAE81A709C2CEE81386133DC3DC7B5EF8F166E48E54A0722DD0C64F4D00638" + + "40F272144C47F6ECED72A248180645DB"; + final String rekeyChildDeleteReq = + "46B8ECA1E0D72A18CECD871146CF83A12E20250000000001000000502A000034" + + "02D98DAF0432EBD991CA4F2D89C1E0EFABC6E91A3327A85D8914FB2F1485BE1B" + + "8D3415D548F7CE0DC4224E7E9D0D3355"; + final String deleteIkeReq = + "46B8ECA1E0D72A18CECD871146CF83A12E20250000000002000000502A000034" + + "095041F4026B4634F04B0AB4F9349484F7BE9AEF03E3733EEE293330043B75D2" + + "ABF5F965ED51127629585E1B1BBA787F"; + + // Open IKE Session + IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress); + PortPair localRemotePorts = performSetupIkeAndFirstChildBlocking(ikeInitResp, ikeAuthResp); + + // IKE INIT and IKE AUTH takes two exchanges. Local request message ID starts from 2 + int expectedReqMsgId = 2; + int expectedRespMsgId = 0; + + verifyIkeSessionSetupBlocking(); + verifyChildSessionSetupBlocking( + mFirstChildSessionCallback, + Arrays.asList(TRANSPORT_MODE_INBOUND_TS), + Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS), + new ArrayList()); + IpSecTransformCallRecord oldTransformRecordA = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + IpSecTransformCallRecord oldTransformRecordB = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + verifyCreateIpSecTransformPair(oldTransformRecordA, oldTransformRecordB); + + // Inject rekey Child requests + mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildCreateReq)); + mTunUtils.awaitResp( + IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */); + mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildDeleteReq)); + mTunUtils.awaitResp( + IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */); + + // Verify IpSecTransforms are renewed + IpSecTransformCallRecord newTransformRecordA = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + IpSecTransformCallRecord newTransformRecordB = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + verifyCreateIpSecTransformPair(newTransformRecordA, newTransformRecordB); + verifyDeleteIpSecTransformPair( + mFirstChildSessionCallback, oldTransformRecordA, oldTransformRecordB); + + // Inject delete IKE request + mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq)); + mTunUtils.awaitResp( + IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */); + + verifyDeleteIpSecTransformPair( + mFirstChildSessionCallback, newTransformRecordA, newTransformRecordB); + mFirstChildSessionCallback.awaitOnClosed(); + mIkeSessionCallback.awaitOnClosed(); + } +} diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java index 86e5c48ebe..0f39fbd1cb 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java @@ -44,6 +44,7 @@ import android.net.ipsec.ike.IkeSessionConnectionInfo; import android.net.ipsec.ike.IkeTrafficSelector; import android.net.ipsec.ike.TransportModeChildSessionParams; import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.net.ipsec.ike.cts.IkeTunUtils.PortPair; import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeProtocolException; @@ -269,13 +270,13 @@ abstract class IkeSessionTestBase extends IkeTestBase { .build(); } - void performSetupIkeAndFirstChildBlocking(String ikeInitRespHex, String... ikeAuthRespHexes) + PortPair performSetupIkeAndFirstChildBlocking(String ikeInitRespHex, String... ikeAuthRespHexes) throws Exception { - performSetupIkeAndFirstChildBlocking( + return performSetupIkeAndFirstChildBlocking( ikeInitRespHex, 1 /* expectedAuthReqPktCnt */, ikeAuthRespHexes); } - void performSetupIkeAndFirstChildBlocking( + PortPair performSetupIkeAndFirstChildBlocking( String ikeInitRespHex, int expectedAuthReqPktCnt, String... ikeAuthRespHexes) throws Exception { mTunUtils.awaitReqAndInjectResp( @@ -284,12 +285,16 @@ abstract class IkeSessionTestBase extends IkeTestBase { false /* expectedUseEncap */, ikeInitRespHex); - mTunUtils.awaitReqAndInjectResp( - IKE_DETERMINISTIC_INITIATOR_SPI, - 1 /* expectedMsgId */, - true /* expectedUseEncap */, - expectedAuthReqPktCnt, - ikeAuthRespHexes); + byte[] ikeAuthReqPkt = + mTunUtils + .awaitReqAndInjectResp( + IKE_DETERMINISTIC_INITIATOR_SPI, + 1 /* expectedMsgId */, + true /* expectedUseEncap */, + expectedAuthReqPktCnt, + ikeAuthRespHexes) + .get(0); + return IkeTunUtils.getSrcDestPortPair(ikeAuthReqPkt); } void performCloseIkeBlocking(int expectedMsgId, String deleteIkeRespHex) throws Exception { diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java index c83d5f379d..41cbf0baa1 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java @@ -55,6 +55,8 @@ public class IkeTunUtils extends TunUtils { private static final int IKE_FRAG_NUM_OFFSET = 32; private static final int IKE_PAYLOAD_TYPE_SKF = 53; + private static final int RSP_FLAG_MASK = 0x20; + public IkeTunUtils(ParcelFileDescriptor tunFd) { super(tunFd); } @@ -136,8 +138,7 @@ public class IkeTunUtils extends TunUtils { InetAddress dstAddr = getAddress(request, true /* shouldGetSource */); int srcPort = getPort(request, false /* shouldGetSource */); int dstPort = getPort(request, true /* shouldGetSource */); - - for (String hex : ikeRespDataFragmentsHex) { + for (String resp : ikeRespDataFragmentsHex) { byte[] response = buildIkePacket( srcAddr, @@ -145,13 +146,27 @@ public class IkeTunUtils extends TunUtils { srcPort, dstPort, expectedUseEncap, - hexStringToByteArray(hex)); + hexStringToByteArray(resp)); injectPacket(response); } return reqList; } + /** Await the expected IKE response */ + public byte[] awaitResp(long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap) + throws Exception { + return awaitIkePacket( + (pkt) -> { + return isExpectedIkePkt( + pkt, + expectedInitIkeSpi, + expectedMsgId, + true /* expectedResp*/, + expectedUseEncap); + }); + } + private byte[] awaitIkePacket(Predicate pktVerifier) throws Exception { long endTime = System.currentTimeMillis() + TIMEOUT; int startIndex = 0; @@ -239,13 +254,36 @@ public class IkeTunUtils extends TunUtils { ByteBuffer buffer = ByteBuffer.wrap(pkt); buffer.get(new byte[ikeOffset]); // Skip IP, UDP header (and NON_ESP_MARKER) + buffer.mark(); // Mark this position so that later we can reset back here - // Check message ID. + // Check SPI + buffer.get(new byte[IKE_INIT_SPI_OFFSET]); + long initSpi = buffer.getLong(); + if (expectedInitIkeSpi != initSpi) { + return false; + } + + // Check direction + buffer.reset(); + buffer.get(new byte[IKE_IS_RESP_BYTE_OFFSET]); + byte flagsByte = buffer.get(); + boolean isResp = ((flagsByte & RSP_FLAG_MASK) != 0); + if (expectedResp != isResp) { + return false; + } + + // Check message ID + buffer.reset(); buffer.get(new byte[IKE_MSG_ID_OFFSET]); - int msgId = buffer.getInt(); - return expectedMsgId == msgId; - // TODO: Check SPI and packet direction + // Both the expected message ID and the packet's msgId are signed integers, so directly + // compare them. + int msgId = buffer.getInt(); + if (expectedMsgId != msgId) { + return false; + } + + return true; } private static boolean isExpectedFragNum(byte[] pkt, int ikeOffset, int expectedFragNum) { @@ -267,6 +305,22 @@ public class IkeTunUtils extends TunUtils { return expectedFragNum == fragNum; } + public static class PortPair { + public final int srcPort; + public final int dstPort; + + public PortPair(int sourcePort, int destinationPort) { + srcPort = sourcePort; + dstPort = destinationPort; + } + } + + public static PortPair getSrcDestPortPair(byte[] outboundIkePkt) throws Exception { + return new PortPair( + getPort(outboundIkePkt, true /* shouldGetSource */), + getPort(outboundIkePkt, false /* shouldGetSource */)); + } + private static InetAddress getAddress(byte[] pkt, boolean shouldGetSource) throws Exception { int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN; int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET; @@ -288,7 +342,7 @@ public class IkeTunUtils extends TunUtils { return Short.toUnsignedInt(buffer.getShort()); } - private static byte[] buildIkePacket( + public static byte[] buildIkePacket( InetAddress srcAddr, InetAddress dstAddr, int srcPort,