diff --git a/core/java/android/net/TcpKeepalivePacketData.java b/core/java/android/net/TcpKeepalivePacketData.java new file mode 100644 index 0000000000..ddb3a6a72f --- /dev/null +++ b/core/java/android/net/TcpKeepalivePacketData.java @@ -0,0 +1,163 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.util.Objects; + +/** + * Represents the actual tcp keep alive packets which will be used for hardware offload. + * @hide + */ +@SystemApi +public final class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable { + private static final String TAG = "TcpKeepalivePacketData"; + + /** TCP sequence number. */ + public final int tcpSeq; + + /** TCP ACK number. */ + public final int tcpAck; + + /** TCP RCV window. */ + public final int tcpWindow; + + /** TCP RCV window scale. */ + public final int tcpWindowScale; + + /** IP TOS. */ + public final int ipTos; + + /** IP TTL. */ + public final int ipTtl; + + public TcpKeepalivePacketData(@NonNull final InetAddress srcAddress, int srcPort, + @NonNull final InetAddress dstAddress, int dstPort, @NonNull final byte[] data, + int tcpSeq, int tcpAck, int tcpWindow, int tcpWindowScale, int ipTos, int ipTtl) + throws InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + this.tcpSeq = tcpSeq; + this.tcpAck = tcpAck; + this.tcpWindow = tcpWindow; + this.tcpWindowScale = tcpWindowScale; + this.ipTos = ipTos; + this.ipTtl = ipTtl; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof TcpKeepalivePacketData)) return false; + final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o; + final InetAddress srcAddress = getSrcAddress(); + final InetAddress dstAddress = getDstAddress(); + return srcAddress.equals(other.getSrcAddress()) + && dstAddress.equals(other.getDstAddress()) + && getSrcPort() == other.getSrcPort() + && getDstPort() == other.getDstPort() + && this.tcpAck == other.tcpAck + && this.tcpSeq == other.tcpSeq + && this.tcpWindow == other.tcpWindow + && this.tcpWindowScale == other.tcpWindowScale + && this.ipTos == other.ipTos + && this.ipTtl == other.ipTtl; + } + + @Override + public int hashCode() { + return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort(), + tcpAck, tcpSeq, tcpWindow, tcpWindowScale, ipTos, ipTtl); + } + + /** + * Parcelable Implementation. + * Note that this object implements parcelable (and needs to keep doing this as it inherits + * from a class that does), but should usually be parceled as a stable parcelable using + * the toStableParcelable() and fromStableParcelable() methods. + */ + @Override + public int describeContents() { + return 0; + } + + /** Write to parcel. */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(getSrcAddress().getHostAddress()); + out.writeString(getDstAddress().getHostAddress()); + out.writeInt(getSrcPort()); + out.writeInt(getDstPort()); + out.writeByteArray(getPacket()); + out.writeInt(tcpSeq); + out.writeInt(tcpAck); + out.writeInt(tcpWindow); + out.writeInt(tcpWindowScale); + out.writeInt(ipTos); + out.writeInt(ipTtl); + } + + private static TcpKeepalivePacketData readFromParcel(Parcel in) throws InvalidPacketException { + InetAddress srcAddress = InetAddresses.parseNumericAddress(in.readString()); + InetAddress dstAddress = InetAddresses.parseNumericAddress(in.readString()); + int srcPort = in.readInt(); + int dstPort = in.readInt(); + byte[] packet = in.createByteArray(); + int tcpSeq = in.readInt(); + int tcpAck = in.readInt(); + int tcpWnd = in.readInt(); + int tcpWndScale = in.readInt(); + int ipTos = in.readInt(); + int ipTtl = in.readInt(); + return new TcpKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, packet, tcpSeq, + tcpAck, tcpWnd, tcpWndScale, ipTos, ipTtl); + } + + /** Parcelable Creator. */ + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public TcpKeepalivePacketData createFromParcel(Parcel in) { + try { + return readFromParcel(in); + } catch (InvalidPacketException e) { + throw new IllegalArgumentException( + "Invalid TCP keepalive data: " + e.getError()); + } + } + + public TcpKeepalivePacketData[] newArray(int size) { + return new TcpKeepalivePacketData[size]; + } + }; + + @Override + public String toString() { + return "saddr: " + getSrcAddress() + + " daddr: " + getDstAddress() + + " sport: " + getSrcPort() + + " dport: " + getDstPort() + + " seq: " + tcpSeq + + " ack: " + tcpAck + + " window: " + tcpWindow + + " windowScale: " + tcpWindowScale + + " tos: " + ipTos + + " ttl: " + ipTtl; + } +} diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java index 1129899ee3..b5f20d70db 100644 --- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java +++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java @@ -36,6 +36,7 @@ import android.net.SocketKeepalive.InvalidSocketException; import android.net.TcpKeepalivePacketData; import android.net.TcpKeepalivePacketDataParcelable; import android.net.TcpRepairWindow; +import android.net.util.KeepalivePacketDataUtil; import android.os.Handler; import android.os.MessageQueue; import android.os.Messenger; @@ -112,7 +113,7 @@ public class TcpKeepaliveController { throws InvalidPacketException, InvalidSocketException { try { final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd); - return TcpKeepalivePacketData.tcpKeepalivePacket(tcpDetails); + return KeepalivePacketDataUtil.fromStableParcelable(tcpDetails); } catch (InvalidPacketException | InvalidSocketException e) { switchOutOfRepairMode(fd); throw e; @@ -122,7 +123,7 @@ public class TcpKeepaliveController { * Switch the tcp socket to repair mode and query detail tcp information. * * @param fd the fd of socket on which to use keepalive offload. - * @return a {@link TcpKeepalivePacketData#TcpKeepalivePacketDataParcelable} object for current + * @return a {@link TcpKeepalivePacketDataParcelable} object for current * tcp/ip information. */ private static TcpKeepalivePacketDataParcelable switchToRepairMode(FileDescriptor fd) diff --git a/tests/net/common/java/android/net/TcpKeepalivePacketDataTest.kt b/tests/net/common/java/android/net/TcpKeepalivePacketDataTest.kt new file mode 100644 index 0000000000..677006692f --- /dev/null +++ b/tests/net/common/java/android/net/TcpKeepalivePacketDataTest.kt @@ -0,0 +1,106 @@ +/* + * 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 + +import android.net.InetAddresses.parseNumericAddress +import android.os.Build +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertParcelSane +import org.junit.Test +import org.junit.runner.RunWith +import java.net.InetAddress +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +@RunWith(DevSdkIgnoreRunner::class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) // TcpKeepalivePacketData added to SDK in S +class TcpKeepalivePacketDataTest { + private fun makeData( + srcAddress: InetAddress = parseNumericAddress("192.0.2.123"), + srcPort: Int = 1234, + dstAddress: InetAddress = parseNumericAddress("192.0.2.231"), + dstPort: Int = 4321, + data: ByteArray = byteArrayOf(1, 2, 3), + tcpSeq: Int = 135, + tcpAck: Int = 246, + tcpWnd: Int = 1234, + tcpWndScale: Int = 2, + ipTos: Int = 0x12, + ipTtl: Int = 10 + ) = TcpKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, data, tcpSeq, tcpAck, + tcpWnd, tcpWndScale, ipTos, ipTtl) + + @Test + fun testEquals() { + val data1 = makeData() + val data2 = makeData() + assertEquals(data1, data2) + assertEquals(data1.hashCode(), data2.hashCode()) + } + + @Test + fun testNotEquals() { + assertNotEquals(makeData(srcAddress = parseNumericAddress("192.0.2.124")), makeData()) + assertNotEquals(makeData(srcPort = 1235), makeData()) + assertNotEquals(makeData(dstAddress = parseNumericAddress("192.0.2.232")), makeData()) + assertNotEquals(makeData(dstPort = 4322), makeData()) + // .equals does not test .packet, as it should be generated from the other fields + assertNotEquals(makeData(tcpSeq = 136), makeData()) + assertNotEquals(makeData(tcpAck = 247), makeData()) + assertNotEquals(makeData(tcpWnd = 1235), makeData()) + assertNotEquals(makeData(tcpWndScale = 3), makeData()) + assertNotEquals(makeData(ipTos = 0x14), makeData()) + assertNotEquals(makeData(ipTtl = 11), makeData()) + + // Update above assertions if field is added + assertFieldCountEquals(5, KeepalivePacketData::class.java) + assertFieldCountEquals(6, TcpKeepalivePacketData::class.java) + } + + @Test + fun testParcelUnparcel() { + assertParcelSane(makeData(), fieldCount = 6) { a, b -> + // .equals() does not verify .packet + a == b && a.packet contentEquals b.packet + } + } + + @Test + fun testToString() { + val data = makeData() + val str = data.toString() + + assertTrue(str.contains(data.srcAddress.hostAddress)) + assertTrue(str.contains(data.srcPort.toString())) + assertTrue(str.contains(data.dstAddress.hostAddress)) + assertTrue(str.contains(data.dstPort.toString())) + // .packet not included in toString() + assertTrue(str.contains(data.tcpSeq.toString())) + assertTrue(str.contains(data.tcpAck.toString())) + assertTrue(str.contains(data.tcpWindow.toString())) + assertTrue(str.contains(data.tcpWindowScale.toString())) + assertTrue(str.contains(data.ipTos.toString())) + assertTrue(str.contains(data.ipTtl.toString())) + + // Update above assertions if field is added + assertFieldCountEquals(5, KeepalivePacketData::class.java) + assertFieldCountEquals(6, TcpKeepalivePacketData::class.java) + } +} \ No newline at end of file diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/KeepalivePacketDataUtilTest.java similarity index 65% rename from tests/net/java/android/net/TcpKeepalivePacketDataTest.java rename to tests/net/java/android/net/KeepalivePacketDataUtilTest.java index c5b25bdcac..fc739fbfac 100644 --- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java +++ b/tests/net/java/android/net/KeepalivePacketDataUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * 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. @@ -20,8 +20,11 @@ import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.net.util.KeepalivePacketDataUtil; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +34,7 @@ import java.net.InetAddress; import java.nio.ByteBuffer; @RunWith(JUnit4.class) -public final class TcpKeepalivePacketDataTest { +public final class KeepalivePacketDataUtilTest { private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1}; private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5}; @@ -39,7 +42,7 @@ public final class TcpKeepalivePacketDataTest { public void setUp() {} @Test - public void testV4TcpKeepalivePacket() throws Exception { + public void testFromTcpKeepaliveStableParcelable() throws Exception { final int srcPort = 1234; final int dstPort = 4321; final int seq = 0x11111111; @@ -61,7 +64,7 @@ public final class TcpKeepalivePacketDataTest { testInfo.tos = tos; testInfo.ttl = ttl; try { - resultData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo); + resultData = KeepalivePacketDataUtil.fromStableParcelable(testInfo); } catch (InvalidPacketException e) { fail("InvalidPacketException: " + e); } @@ -72,8 +75,8 @@ public final class TcpKeepalivePacketDataTest { assertEquals(testInfo.dstPort, resultData.getDstPort()); assertEquals(testInfo.seq, resultData.tcpSeq); assertEquals(testInfo.ack, resultData.tcpAck); - assertEquals(testInfo.rcvWnd, resultData.tcpWnd); - assertEquals(testInfo.rcvWndScale, resultData.tcpWndScale); + assertEquals(testInfo.rcvWnd, resultData.tcpWindow); + assertEquals(testInfo.rcvWndScale, resultData.tcpWindowScale); assertEquals(testInfo.tos, resultData.ipTos); assertEquals(testInfo.ttl, resultData.ipTtl); @@ -113,7 +116,7 @@ public final class TcpKeepalivePacketDataTest { //TODO: add ipv6 test when ipv6 supported @Test - public void testParcel() throws Exception { + public void testToTcpKeepaliveStableParcelable() throws Exception { final int srcPort = 1234; final int dstPort = 4321; final int sequence = 0x11111111; @@ -135,8 +138,8 @@ public final class TcpKeepalivePacketDataTest { testInfo.ttl = ttl; TcpKeepalivePacketData testData = null; TcpKeepalivePacketDataParcelable resultData = null; - testData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo); - resultData = testData.toStableParcelable(); + testData = KeepalivePacketDataUtil.fromStableParcelable(testInfo); + resultData = KeepalivePacketDataUtil.toStableParcelable(testData); assertArrayEquals(resultData.srcAddress, IPV4_KEEPALIVE_SRC_ADDR); assertArrayEquals(resultData.dstAddress, IPV4_KEEPALIVE_DST_ADDR); assertEquals(resultData.srcPort, srcPort); @@ -154,4 +157,49 @@ public final class TcpKeepalivePacketDataTest { + " ack: 572662306, rcvWnd: 48000, rcvWndScale: 2, tos: 4, ttl: 64}"; assertEquals(expected, resultData.toString()); } + + @Test + public void testParseTcpKeepalivePacketData() throws Exception { + final int srcPort = 1234; + final int dstPort = 4321; + final int sequence = 0x11111111; + final int ack = 0x22222222; + final int wnd = 4800; + final int wndScale = 2; + final int tos = 4; + final int ttl = 64; + final TcpKeepalivePacketDataParcelable testParcel = new TcpKeepalivePacketDataParcelable(); + testParcel.srcAddress = IPV4_KEEPALIVE_SRC_ADDR; + testParcel.srcPort = srcPort; + testParcel.dstAddress = IPV4_KEEPALIVE_DST_ADDR; + testParcel.dstPort = dstPort; + testParcel.seq = sequence; + testParcel.ack = ack; + testParcel.rcvWnd = wnd; + testParcel.rcvWndScale = wndScale; + testParcel.tos = tos; + testParcel.ttl = ttl; + + final KeepalivePacketData testData = + KeepalivePacketDataUtil.fromStableParcelable(testParcel); + final TcpKeepalivePacketDataParcelable parsedParcelable = + KeepalivePacketDataUtil.parseTcpKeepalivePacketData(testData); + final TcpKeepalivePacketData roundTripData = + KeepalivePacketDataUtil.fromStableParcelable(parsedParcelable); + + // Generated packet is the same, but rcvWnd / wndScale will differ if scale is non-zero + assertTrue(testData.getPacket().length > 0); + assertArrayEquals(testData.getPacket(), roundTripData.getPacket()); + + testParcel.rcvWndScale = 0; + final KeepalivePacketData noScaleTestData = + KeepalivePacketDataUtil.fromStableParcelable(testParcel); + final TcpKeepalivePacketDataParcelable noScaleParsedParcelable = + KeepalivePacketDataUtil.parseTcpKeepalivePacketData(noScaleTestData); + final TcpKeepalivePacketData noScaleRoundTripData = + KeepalivePacketDataUtil.fromStableParcelable(noScaleParsedParcelable); + assertEquals(noScaleTestData, noScaleRoundTripData); + assertTrue(noScaleTestData.getPacket().length > 0); + assertArrayEquals(noScaleTestData.getPacket(), noScaleRoundTripData.getPacket()); + } }