From 5e6b51b8956060858a2dddfb2444b6be01fbb858 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 10 Nov 2020 15:29:01 +0900 Subject: [PATCH 1/2] Add TcpKeepalivePacketData to SystemApi This is consistent with NattKeepalivePacketData, which is also a subclass of KeepalivePacketData. TcpKeepalivePacketData is already used by the wifi module, but statically linked. Bug: 172789687 Test: m Change-Id: I6aee1ae205987521bea4a3838bbece279ffa0e37 --- .../android/net/TcpKeepalivePacketData.java | 163 ++++++++++++++++++ .../connectivity/TcpKeepaliveController.java | 5 +- .../android/net/TcpKeepalivePacketDataTest.kt | 106 ++++++++++++ ....java => KeepalivePacketDataUtilTest.java} | 20 ++- 4 files changed, 283 insertions(+), 11 deletions(-) create mode 100644 core/java/android/net/TcpKeepalivePacketData.java create mode 100644 tests/net/common/java/android/net/TcpKeepalivePacketDataTest.kt rename tests/net/java/android/net/{TcpKeepalivePacketDataTest.java => KeepalivePacketDataUtilTest.java} (89%) 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 89% rename from tests/net/java/android/net/TcpKeepalivePacketDataTest.java rename to tests/net/java/android/net/KeepalivePacketDataUtilTest.java index c5b25bdcac..9ae3595adb 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. @@ -22,6 +22,8 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; 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 +33,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 +41,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 +63,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 +74,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 +115,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 +137,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); From 0956b8a3de1a293ef6edce8c143fc04a289418a0 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 10 Nov 2020 13:11:06 +0900 Subject: [PATCH 2/2] Use API TcpKeepalivePacketData in ClientModeImpl Instead of statically linking against and jarjaring TcpKeepalivePacketData, use the new android.net.TcpKeepalivePacketData API for S. On R, build the KeepalivePacketDataParcelable from the base KeepalivePacketData class. The current ClientModeImpl code that uses a statically linked TcpKeepalivePacketData is actually broken, as since R the system_server has been sending a @hide android.net.TcpKeepalivePacketData, and ClientModeImpl was testing it against com.android.wifi.x.android.net.*. To fix this on R, this change rebuilds a TcpKeepalivePacketDataParcelable class from the packet data included in the base KeepalivePacketData class. Bug: 172789687 Test: atest ConnectivityManagerTest#testCreateTcpKeepalive See associated test change Change-Id: Ia32b4444dbf90306b2cfd37ec13d4ba4e90cd1e8 --- .../net/KeepalivePacketDataUtilTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/net/java/android/net/KeepalivePacketDataUtilTest.java b/tests/net/java/android/net/KeepalivePacketDataUtilTest.java index 9ae3595adb..fc739fbfac 100644 --- a/tests/net/java/android/net/KeepalivePacketDataUtilTest.java +++ b/tests/net/java/android/net/KeepalivePacketDataUtilTest.java @@ -20,6 +20,7 @@ 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; @@ -156,4 +157,49 @@ public final class KeepalivePacketDataUtilTest { + " 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()); + } }