From 103ec4aa8d2c9c573840346e18ea9a20a5adc356 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 --- core/api/system-current.txt | 13 + .../android/net/TcpKeepalivePacketData.java | 163 +++++++++++ .../connectivity/TcpKeepaliveController.java | 5 +- services/net/Android.bp | 1 - .../android/net/TcpKeepalivePacketData.java | 268 ------------------ .../java/android/net/ip/IpClientManager.java | 14 +- .../net/util/KeepalivePacketDataUtil.java | 120 +++++++- .../android/net/TcpKeepalivePacketDataTest.kt | 106 +++++++ ....java => KeepalivePacketDataUtilTest.java} | 20 +- 9 files changed, 425 insertions(+), 285 deletions(-) create mode 100644 core/java/android/net/TcpKeepalivePacketData.java delete mode 100644 services/net/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/api/system-current.txt b/core/api/system-current.txt index a48ad95ea40e5..3980774468abc 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6400,6 +6400,19 @@ package android.net { method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); } + public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public final int ipTos; + field public final int ipTtl; + field public final int tcpAck; + field public final int tcpSeq; + field public final int tcpWindow; + field public final int tcpWindowScale; + } + public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); diff --git a/core/java/android/net/TcpKeepalivePacketData.java b/core/java/android/net/TcpKeepalivePacketData.java new file mode 100644 index 0000000000000..ddb3a6a72fb40 --- /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 1129899ee3ff2..b5f20d70db7fe 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/services/net/Android.bp b/services/net/Android.bp index 29bf374605685..ae9bd41b9db0b 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -37,7 +37,6 @@ java_library { "java/android/net/util/NetworkConstants.java", "java/android/net/IpMemoryStore.java", "java/android/net/NetworkMonitorManager.java", - "java/android/net/TcpKeepalivePacketData.java", ], sdk_version: "module_current", min_sdk_version: "30", diff --git a/services/net/java/android/net/TcpKeepalivePacketData.java b/services/net/java/android/net/TcpKeepalivePacketData.java deleted file mode 100644 index 4875c7cc42635..0000000000000 --- a/services/net/java/android/net/TcpKeepalivePacketData.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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 android.net; - -import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.system.OsConstants; - -import com.android.net.module.util.IpUtils; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Objects; - -/** - * Represents the actual tcp keep alive packets which will be used for hardware offload. - * @hide - */ -public 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 tcpWnd; - - /** TCP RCV window scale. */ - public final int tcpWndScale; - - /** IP TOS. */ - public final int ipTos; - - /** IP TTL. */ - public final int ipTtl; - - private static final int IPV4_HEADER_LENGTH = 20; - private static final int IPV6_HEADER_LENGTH = 40; - private static final int TCP_HEADER_LENGTH = 20; - - // This should only be constructed via static factory methods, such as - // tcpKeepalivePacket. - private TcpKeepalivePacketData(final TcpKeepalivePacketDataParcelable tcpDetails, - final byte[] data) throws InvalidPacketException, UnknownHostException { - super(InetAddress.getByAddress(tcpDetails.srcAddress), tcpDetails.srcPort, - InetAddress.getByAddress(tcpDetails.dstAddress), tcpDetails.dstPort, data); - tcpSeq = tcpDetails.seq; - tcpAck = tcpDetails.ack; - // In the packet, the window is shifted right by the window scale. - tcpWnd = tcpDetails.rcvWnd; - tcpWndScale = tcpDetails.rcvWndScale; - ipTos = tcpDetails.tos; - ipTtl = tcpDetails.ttl; - } - - private TcpKeepalivePacketData(final InetAddress srcAddress, int srcPort, - final InetAddress dstAddress, int dstPort, final byte[] data, int tcpSeq, - int tcpAck, int tcpWnd, int tcpWndScale, int ipTos, int ipTtl) - throws InvalidPacketException { - super(srcAddress, srcPort, dstAddress, dstPort, data); - this.tcpSeq = tcpSeq; - this.tcpAck = tcpAck; - this.tcpWnd = tcpWnd; - this.tcpWndScale = tcpWndScale; - this.ipTos = ipTos; - this.ipTtl = ipTtl; - } - - /** - * Factory method to create tcp keepalive packet structure. - */ - public static TcpKeepalivePacketData tcpKeepalivePacket( - TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException { - final byte[] packet; - try { - if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null) - && (tcpDetails.srcAddress.length == 4 /* V4 IP length */) - && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) { - packet = buildV4Packet(tcpDetails); - } else { - // TODO: support ipv6 - throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); - } - return new TcpKeepalivePacketData(tcpDetails, packet); - } catch (UnknownHostException e) { - throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); - } - - } - - /** - * Build ipv4 tcp keepalive packet, not including the link-layer header. - */ - // TODO : if this code is ever moved to the network stack, factorize constants with the ones - // over there. - private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) { - final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH; - ByteBuffer buf = ByteBuffer.allocate(length); - buf.order(ByteOrder.BIG_ENDIAN); - buf.put((byte) 0x45); // IP version and IHL - buf.put((byte) tcpDetails.tos); // TOS - buf.putShort((short) length); - buf.putInt(0x00004000); // ID, flags=DF, offset - buf.put((byte) tcpDetails.ttl); // TTL - buf.put((byte) OsConstants.IPPROTO_TCP); - final int ipChecksumOffset = buf.position(); - buf.putShort((short) 0); // IP checksum - buf.put(tcpDetails.srcAddress); - buf.put(tcpDetails.dstAddress); - buf.putShort((short) tcpDetails.srcPort); - buf.putShort((short) tcpDetails.dstPort); - buf.putInt(tcpDetails.seq); // Sequence Number - buf.putInt(tcpDetails.ack); // ACK - buf.putShort((short) 0x5010); // TCP length=5, flags=ACK - buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size - final int tcpChecksumOffset = buf.position(); - buf.putShort((short) 0); // TCP checksum - // URG is not set therefore the urgent pointer is zero. - buf.putShort((short) 0); // Urgent pointer - - buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); - buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum( - buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH)); - - return buf.array(); - } - - // TODO: add buildV6Packet. - - @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.tcpWnd == other.tcpWnd - && this.tcpWndScale == other.tcpWndScale - && this.ipTos == other.ipTos - && this.ipTtl == other.ipTtl; - } - - @Override - public int hashCode() { - return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort(), - tcpAck, tcpSeq, tcpWnd, tcpWndScale, 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. - */ - public int describeContents() { - return 0; - } - - /** Write to parcel. */ - public void writeToParcel(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(tcpWnd); - out.writeInt(tcpWndScale); - 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 NAT-T keepalive data: " + e.getError()); - } - } - - public TcpKeepalivePacketData[] newArray(int size) { - return new TcpKeepalivePacketData[size]; - } - }; - - /** - * Convert this TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable. - */ - @NonNull - public TcpKeepalivePacketDataParcelable toStableParcelable() { - final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); - final InetAddress srcAddress = getSrcAddress(); - final InetAddress dstAddress = getDstAddress(); - parcel.srcAddress = srcAddress.getAddress(); - parcel.srcPort = getSrcPort(); - parcel.dstAddress = dstAddress.getAddress(); - parcel.dstPort = getDstPort(); - parcel.seq = tcpSeq; - parcel.ack = tcpAck; - parcel.rcvWnd = tcpWnd; - parcel.rcvWndScale = tcpWndScale; - parcel.tos = ipTos; - parcel.ttl = ipTtl; - return parcel; - } - - @Override - public String toString() { - return "saddr: " + getSrcAddress() - + " daddr: " + getDstAddress() - + " sport: " + getSrcPort() - + " dport: " + getDstPort() - + " seq: " + tcpSeq - + " ack: " + tcpAck - + " wnd: " + tcpWnd - + " wndScale: " + tcpWndScale - + " tos: " + ipTos - + " ttl: " + ipTtl; - } -} diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java index db464e732e916..274b6dc1769be 100644 --- a/services/net/java/android/net/ip/IpClientManager.java +++ b/services/net/java/android/net/ip/IpClientManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.net.NattKeepalivePacketData; import android.net.ProxyInfo; import android.net.TcpKeepalivePacketData; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.shared.Layer2Information; import android.net.shared.ProvisioningConfiguration; import android.net.util.KeepalivePacketDataUtil; @@ -215,9 +216,20 @@ public class IpClientManager { * Add a TCP keepalive packet filter before setting up keepalive offload. */ public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketData pkt) { + return addKeepalivePacketFilter(slot, KeepalivePacketDataUtil.toStableParcelable(pkt)); + } + + /** + * Add a TCP keepalive packet filter before setting up keepalive offload. + * @deprecated This method is for use on pre-S platforms where TcpKeepalivePacketData is not + * system API. On newer platforms use + * addKeepalivePacketFilter(int, TcpKeepalivePacketData) instead. + */ + @Deprecated + public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) { final long token = Binder.clearCallingIdentity(); try { - mIpClient.addKeepalivePacketFilter(slot, pkt.toStableParcelable()); + mIpClient.addKeepalivePacketFilter(slot, pkt); return true; } catch (RemoteException e) { log("Error adding Keepalive Packet Filter ", e); diff --git a/services/net/java/android/net/util/KeepalivePacketDataUtil.java b/services/net/java/android/net/util/KeepalivePacketDataUtil.java index 4466ea0abe0eb..f06070b6870d6 100644 --- a/services/net/java/android/net/util/KeepalivePacketDataUtil.java +++ b/services/net/java/android/net/util/KeepalivePacketDataUtil.java @@ -16,20 +16,41 @@ package android.net.util; +import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; + import android.annotation.NonNull; +import android.net.InvalidPacketException; import android.net.NattKeepalivePacketData; import android.net.NattKeepalivePacketDataParcelable; +import android.net.TcpKeepalivePacketData; +import android.net.TcpKeepalivePacketDataParcelable; +import android.system.OsConstants; + +import com.android.net.module.util.IpUtils; import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; -/** @hide */ +/** + * Utility class to convert to/from keepalive data parcelables. + * + * TODO: move to networkstack-client library when it is moved to frameworks/libs/net. + * This class cannot go into other shared libraries as it depends on NetworkStack AIDLs. + * @hide + */ public final class KeepalivePacketDataUtil { - /** - * Convert this NattKeepalivePacketData to a NattKeepalivePacketDataParcelable. + private static final int IPV4_HEADER_LENGTH = 20; + private static final int IPV6_HEADER_LENGTH = 40; + private static final int TCP_HEADER_LENGTH = 20; + + /** + * Convert a NattKeepalivePacketData to a NattKeepalivePacketDataParcelable. */ @NonNull public static NattKeepalivePacketDataParcelable toStableParcelable( - NattKeepalivePacketData pkt) { + @NonNull NattKeepalivePacketData pkt) { final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable(); final InetAddress srcAddress = pkt.getSrcAddress(); final InetAddress dstAddress = pkt.getDstAddress(); @@ -39,4 +60,95 @@ public final class KeepalivePacketDataUtil { parcel.dstPort = pkt.getDstPort(); return parcel; } + + /** + * Convert a TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable. + */ + @NonNull + public static TcpKeepalivePacketDataParcelable toStableParcelable( + @NonNull TcpKeepalivePacketData pkt) { + final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); + final InetAddress srcAddress = pkt.getSrcAddress(); + final InetAddress dstAddress = pkt.getDstAddress(); + parcel.srcAddress = srcAddress.getAddress(); + parcel.srcPort = pkt.getSrcPort(); + parcel.dstAddress = dstAddress.getAddress(); + parcel.dstPort = pkt.getDstPort(); + parcel.seq = pkt.tcpSeq; + parcel.ack = pkt.tcpAck; + parcel.rcvWnd = pkt.tcpWindow; + parcel.rcvWndScale = pkt.tcpWindowScale; + parcel.tos = pkt.ipTos; + parcel.ttl = pkt.ipTtl; + return parcel; + } + + /** + * Factory method to create tcp keepalive packet structure. + * @hide + */ + public static TcpKeepalivePacketData fromStableParcelable( + TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException { + final byte[] packet; + try { + if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null) + && (tcpDetails.srcAddress.length == 4 /* V4 IP length */) + && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) { + packet = buildV4Packet(tcpDetails); + } else { + // TODO: support ipv6 + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + return new TcpKeepalivePacketData( + InetAddress.getByAddress(tcpDetails.srcAddress), + tcpDetails.srcPort, + InetAddress.getByAddress(tcpDetails.dstAddress), + tcpDetails.dstPort, + packet, + tcpDetails.seq, tcpDetails.ack, tcpDetails.rcvWnd, tcpDetails.rcvWndScale, + tcpDetails.tos, tcpDetails.ttl); + } catch (UnknownHostException e) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + } + + /** + * Build ipv4 tcp keepalive packet, not including the link-layer header. + */ + // TODO : if this code is ever moved to the network stack, factorize constants with the ones + // over there. + private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) { + final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.put((byte) 0x45); // IP version and IHL + buf.put((byte) tcpDetails.tos); // TOS + buf.putShort((short) length); + buf.putInt(0x00004000); // ID, flags=DF, offset + buf.put((byte) tcpDetails.ttl); // TTL + buf.put((byte) OsConstants.IPPROTO_TCP); + final int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(tcpDetails.srcAddress); + buf.put(tcpDetails.dstAddress); + buf.putShort((short) tcpDetails.srcPort); + buf.putShort((short) tcpDetails.dstPort); + buf.putInt(tcpDetails.seq); // Sequence Number + buf.putInt(tcpDetails.ack); // ACK + buf.putShort((short) 0x5010); // TCP length=5, flags=ACK + buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size + final int tcpChecksumOffset = buf.position(); + buf.putShort((short) 0); // TCP checksum + // URG is not set therefore the urgent pointer is zero. + buf.putShort((short) 0); // Urgent pointer + + buf.putShort(ipChecksumOffset, com.android.net.module.util.IpUtils.ipChecksum(buf, 0)); + buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum( + buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH)); + + return buf.array(); + } + + // TODO: add buildV6Packet. } 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 0000000000000..677006692f849 --- /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 c5b25bdcac482..9ae3595adbdbd 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 dac4ac4fdc1b578d28f6482007ac35d7719ff163 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/util/KeepalivePacketDataUtil.java | 69 +++++++++++++++++++ .../net/KeepalivePacketDataUtilTest.java | 46 +++++++++++++ wifi/jarjar-rules.txt | 2 +- 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/services/net/java/android/net/util/KeepalivePacketDataUtil.java b/services/net/java/android/net/util/KeepalivePacketDataUtil.java index f06070b6870d6..6e539bbaf9fe8 100644 --- a/services/net/java/android/net/util/KeepalivePacketDataUtil.java +++ b/services/net/java/android/net/util/KeepalivePacketDataUtil.java @@ -19,12 +19,16 @@ package android.net.util; import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.InvalidPacketException; +import android.net.KeepalivePacketData; import android.net.NattKeepalivePacketData; import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketData; import android.net.TcpKeepalivePacketDataParcelable; +import android.os.Build; import android.system.OsConstants; +import android.util.Log; import com.android.net.module.util.IpUtils; @@ -45,6 +49,8 @@ public final class KeepalivePacketDataUtil { private static final int IPV6_HEADER_LENGTH = 40; private static final int TCP_HEADER_LENGTH = 20; + private static final String TAG = KeepalivePacketDataUtil.class.getSimpleName(); + /** * Convert a NattKeepalivePacketData to a NattKeepalivePacketDataParcelable. */ @@ -151,4 +157,67 @@ public final class KeepalivePacketDataUtil { } // TODO: add buildV6Packet. + + /** + * Get a {@link TcpKeepalivePacketDataParcelable} from {@link KeepalivePacketData}, if the + * generic class actually contains TCP keepalive data. + * + * @deprecated This method is used on R platforms where android.net.TcpKeepalivePacketData was + * not yet system API. Newer platforms should use android.net.TcpKeepalivePacketData directly. + * + * @param data A {@link KeepalivePacketData} that may contain TCP keepalive data. + * @return A parcelable containing TCP keepalive data, or null if the input data does not + * contain TCP keepalive data. + */ + @Deprecated + @SuppressWarnings("AndroidFrameworkCompatChange") // API version check used to Log.wtf + @Nullable + public static TcpKeepalivePacketDataParcelable parseTcpKeepalivePacketData( + @Nullable KeepalivePacketData data) { + if (data == null) return null; + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) { + Log.wtf(TAG, "parseTcpKeepalivePacketData should not be used after R, use " + + "TcpKeepalivePacketData instead."); + } + + // Reconstruct TcpKeepalivePacketData from the packet contained in KeepalivePacketData + final ByteBuffer buffer = ByteBuffer.wrap(data.getPacket()); + buffer.order(ByteOrder.BIG_ENDIAN); + + // Most of the fields are accessible from the KeepalivePacketData superclass: instead of + // using Struct to parse everything, just extract the extra fields necessary for + // TcpKeepalivePacketData. + final int tcpSeq; + final int tcpAck; + final int wndSize; + final int ipTos; + final int ttl; + try { + // This only support IPv4, because TcpKeepalivePacketData only supports IPv4 for R and + // below, and this method should not be used on newer platforms. + tcpSeq = buffer.getInt(IPV4_HEADER_LENGTH + 4); + tcpAck = buffer.getInt(IPV4_HEADER_LENGTH + 8); + wndSize = buffer.getShort(IPV4_HEADER_LENGTH + 14); + ipTos = buffer.get(1); + ttl = buffer.get(8); + } catch (IndexOutOfBoundsException e) { + return null; + } + + final TcpKeepalivePacketDataParcelable p = new TcpKeepalivePacketDataParcelable(); + p.srcAddress = data.getSrcAddress().getAddress(); + p.srcPort = data.getSrcPort(); + p.dstAddress = data.getDstAddress().getAddress(); + p.dstPort = data.getDstPort(); + p.seq = tcpSeq; + p.ack = tcpAck; + // TcpKeepalivePacketData could actually use non-zero wndScale, but this does not affect + // actual functionality as generated packets will be the same (no wndScale option added) + p.rcvWnd = wndSize; + p.rcvWndScale = 0; + p.tos = ipTos; + p.ttl = ttl; + return p; + } } diff --git a/tests/net/java/android/net/KeepalivePacketDataUtilTest.java b/tests/net/java/android/net/KeepalivePacketDataUtilTest.java index 9ae3595adbdbd..fc739fbfac617 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()); + } } diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt index 75b5e728eb0d0..4099c27a0fae1 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -21,6 +21,7 @@ rule android.net.ProvisioningConfigurationParcelable* com.android.wifi.x.@0 rule android.net.ResolverParamsParcel* com.android.wifi.x.@0 rule android.net.RouteInfoParcel* com.android.wifi.x.@0 rule android.net.ScanResultInfoParcelable* com.android.wifi.x.@0 +rule android.net.TcpKeepalivePacketDataParcelable* com.android.wifi.x.@0 rule android.net.TetherConfigParcel* com.android.wifi.x.@0 rule android.net.TetherOffloadRuleParcel* com.android.wifi.x.@0 rule android.net.TetherStatsParcel* com.android.wifi.x.@0 @@ -43,7 +44,6 @@ rule android.net.DhcpResults* com.android.wifi.x.@0 rule android.net.InterfaceConfiguration* com.android.wifi.x.@0 rule android.net.IpMemoryStore* com.android.wifi.x.@0 rule android.net.NetworkMonitorManager* com.android.wifi.x.@0 -rule android.net.TcpKeepalivePacketData* com.android.wifi.x.@0 rule android.net.NetworkFactory* com.android.wifi.x.@0 rule android.net.ip.IpClientCallbacks* com.android.wifi.x.@0 rule android.net.ip.IpClientManager* com.android.wifi.x.@0