summaryrefslogtreecommitdiff
path: root/services/net/java/android/net/util/KeepalivePacketDataUtil.java
blob: 566698576026ceeb142d31991d68525935c83a6f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/*
 * 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.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;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * 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 {
    private static final int IPV4_HEADER_LENGTH = 20;
    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.
     */
    @NonNull
    public static NattKeepalivePacketDataParcelable toStableParcelable(
            @NonNull NattKeepalivePacketData pkt) {
        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
        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();
        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.getTcpSeq();
        parcel.ack = pkt.getTcpAck();
        parcel.rcvWnd = pkt.getTcpWindow();
        parcel.rcvWndScale = pkt.getTcpWindowScale();
        parcel.tos = pkt.getIpTos();
        parcel.ttl = pkt.getIpTtl();
        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.

    /**
     * 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;
    }
}