| /* |
| * 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.net.SocketKeepalive.InvalidPacketException; |
| import android.net.util.IpUtils; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.system.OsConstants; |
| |
| import java.net.Inet4Address; |
| import java.net.InetAddress; |
| 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; |
| |
| 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(TcpSocketInfo tcpDetails, byte[] data) |
| throws InvalidPacketException { |
| super(tcpDetails.srcAddress, tcpDetails.srcPort, 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; |
| } |
| |
| /** |
| * Factory method to create tcp keepalive packet structure. |
| */ |
| public static TcpKeepalivePacketData tcpKeepalivePacket( |
| TcpSocketInfo tcpDetails) throws InvalidPacketException { |
| final byte[] packet; |
| if ((tcpDetails.srcAddress instanceof Inet4Address) |
| && (tcpDetails.dstAddress instanceof Inet4Address)) { |
| packet = buildV4Packet(tcpDetails); |
| } else { |
| // TODO: support ipv6 |
| throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); |
| } |
| |
| return new TcpKeepalivePacketData(tcpDetails, packet); |
| } |
| |
| /** |
| * 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(TcpSocketInfo tcpDetails) { |
| final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH; |
| ByteBuffer buf = ByteBuffer.allocate(length); |
| buf.order(ByteOrder.BIG_ENDIAN); |
| // IP version and TOS. TODO : fetch this from getsockopt(SOL_IP, IP_TOS) |
| buf.putShort((short) 0x4500); |
| buf.putShort((short) length); |
| buf.putInt(0x4000); // ID, flags=DF, offset |
| // TODO : fetch TTL from getsockopt(SOL_IP, IP_TTL) |
| buf.put((byte) 64); |
| buf.put((byte) OsConstants.IPPROTO_TCP); |
| final int ipChecksumOffset = buf.position(); |
| buf.putShort((short) 0); // IP checksum |
| buf.put(tcpDetails.srcAddress.getAddress()); |
| buf.put(tcpDetails.dstAddress.getAddress()); |
| 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 not included |
| 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. |
| |
| /** Represents tcp/ip information. */ |
| // TODO: Replace TcpSocketInfo with TcpKeepalivePacketDataParcelable. |
| public static class TcpSocketInfo { |
| public final InetAddress srcAddress; |
| public final InetAddress dstAddress; |
| public final int srcPort; |
| public final int dstPort; |
| public final int seq; |
| public final int ack; |
| public final int rcvWnd; |
| public final int rcvWndScale; |
| |
| public TcpSocketInfo(InetAddress sAddr, int sPort, InetAddress dAddr, |
| int dPort, int writeSeq, int readSeq, int rWnd, int rWndScale) { |
| srcAddress = sAddr; |
| dstAddress = dAddr; |
| srcPort = sPort; |
| dstPort = dPort; |
| seq = writeSeq; |
| ack = readSeq; |
| rcvWnd = rWnd; |
| rcvWndScale = rWndScale; |
| } |
| } |
| |
| @Override |
| public boolean equals(@Nullable final Object o) { |
| if (!(o instanceof TcpKeepalivePacketData)) return false; |
| final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o; |
| return this.srcAddress.equals(other.srcAddress) |
| && this.dstAddress.equals(other.dstAddress) |
| && this.srcPort == other.srcPort |
| && this.dstPort == other.dstPort |
| && this.tcpAck == other.tcpAck |
| && this.tcpSeq == other.tcpSeq |
| && this.tcpWnd == other.tcpWnd |
| && this.tcpWndScale == other.tcpWndScale; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(srcAddress, dstAddress, srcPort, dstPort, tcpAck, tcpSeq, tcpWnd, |
| tcpWndScale); |
| } |
| |
| /* 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) { |
| super.writeToParcel(out, flags); |
| out.writeInt(tcpSeq); |
| out.writeInt(tcpAck); |
| out.writeInt(tcpWnd); |
| out.writeInt(tcpWndScale); |
| } |
| |
| private TcpKeepalivePacketData(Parcel in) { |
| super(in); |
| tcpSeq = in.readInt(); |
| tcpAck = in.readInt(); |
| tcpWnd = in.readInt(); |
| tcpWndScale = in.readInt(); |
| } |
| |
| /** Parcelable Creator. */ |
| public static final Parcelable.Creator<TcpKeepalivePacketData> CREATOR = |
| new Parcelable.Creator<TcpKeepalivePacketData>() { |
| public TcpKeepalivePacketData createFromParcel(Parcel in) { |
| return new TcpKeepalivePacketData(in); |
| } |
| |
| public TcpKeepalivePacketData[] newArray(int size) { |
| return new TcpKeepalivePacketData[size]; |
| } |
| }; |
| |
| /** |
| * Convert this TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable. |
| */ |
| @NonNull |
| public TcpKeepalivePacketDataParcelable toStableParcelable() { |
| final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); |
| parcel.srcAddress = srcAddress.getAddress(); |
| parcel.srcPort = srcPort; |
| parcel.dstAddress = dstAddress.getAddress(); |
| parcel.dstPort = dstPort; |
| parcel.seq = tcpSeq; |
| parcel.ack = tcpAck; |
| return parcel; |
| } |
| |
| @Override |
| public String toString() { |
| return "saddr: " + srcAddress |
| + " daddr: " + dstAddress |
| + " sport: " + srcPort |
| + " dport: " + dstPort |
| + " seq: " + tcpSeq |
| + " ack: " + tcpAck |
| + " wnd: " + tcpWnd |
| + " wndScale: " + tcpWndScale; |
| } |
| } |