1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.connectivity;
17 
18 import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE;
19 import static android.net.SocketKeepalive.DATA_RECEIVED;
20 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
21 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
22 import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
23 import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
24 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
25 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
26 import static android.system.OsConstants.ENOPROTOOPT;
27 import static android.system.OsConstants.FIONREAD;
28 import static android.system.OsConstants.IPPROTO_IP;
29 import static android.system.OsConstants.IPPROTO_TCP;
30 import static android.system.OsConstants.IP_TOS;
31 import static android.system.OsConstants.IP_TTL;
32 import static android.system.OsConstants.TIOCOUTQ;
33 
34 import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
35 
36 import android.annotation.NonNull;
37 import android.annotation.SuppressLint;
38 import android.net.ISocketKeepaliveCallback;
39 import android.net.InvalidPacketException;
40 import android.net.NetworkUtils;
41 import android.net.SocketKeepalive.InvalidSocketException;
42 import android.net.TcpKeepalivePacketData;
43 import android.net.TcpKeepalivePacketDataParcelable;
44 import android.net.TcpRepairWindow;
45 import android.os.Handler;
46 import android.os.MessageQueue;
47 import android.os.Messenger;
48 import android.system.ErrnoException;
49 import android.system.Os;
50 import android.util.Log;
51 import android.util.SparseArray;
52 
53 import com.android.internal.annotations.GuardedBy;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.net.module.util.IpUtils;
56 
57 import java.io.FileDescriptor;
58 import java.net.InetAddress;
59 import java.net.InetSocketAddress;
60 import java.net.SocketAddress;
61 import java.net.SocketException;
62 import java.net.UnknownHostException;
63 import java.nio.ByteBuffer;
64 import java.nio.ByteOrder;
65 
66 /**
67  * Manage tcp socket which offloads tcp keepalive.
68  *
69  * The input socket will be changed to repair mode and the application
70  * will not have permission to read/write data. If the application wants
71  * to write data, it must stop tcp keepalive offload to leave repair mode
72  * first. If a remote packet arrives, repair mode will be turned off and
73  * offload will be stopped. The application will receive a callback to know
74  * it can start reading data.
75  *
76  * {start,stop}SocketMonitor are thread-safe, but care must be taken in the
77  * order in which they are called. Please note that while calling
78  * {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times
79  * with either the same slot or the same FileDescriptor without stopping it in
80  * between will result in an exception, calling {@link #stopSocketMonitor(int)}
81  * multiple times with the same int is explicitly a no-op.
82  * Please also note that switching the socket to repair mode is not synchronized
83  * with either of these operations and has to be done in an orderly fashion
84  * with stopSocketMonitor. Take care in calling these in the right order.
85  * @hide
86  */
87 public class TcpKeepaliveController {
88     private static final String TAG = "TcpKeepaliveController";
89     private static final boolean DBG = false;
90 
91     private final MessageQueue mFdHandlerQueue;
92 
93     private final Handler mConnectivityServiceHandler;
94 
95     private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
96 
97     private static final int TCP_HEADER_LENGTH = 20;
98 
99     // Reference include/uapi/linux/tcp.h
100     private static final int TCP_REPAIR = 19;
101     private static final int TCP_REPAIR_QUEUE = 20;
102     private static final int TCP_QUEUE_SEQ = 21;
103     private static final int TCP_NO_QUEUE = 0;
104     private static final int TCP_RECV_QUEUE = 1;
105     private static final int TCP_SEND_QUEUE = 2;
106     private static final int TCP_REPAIR_OFF = 0;
107     private static final int TCP_REPAIR_ON = 1;
108     // Reference include/uapi/linux/sockios.h
109     private static final int SIOCINQ = FIONREAD;
110     // arch specific BSD socket API constant that predates Linux and Android
111     @SuppressLint("NewApi")
112     private static final int SIOCOUTQ = TIOCOUTQ;
113 
114     /**
115      * Keeps track of packet listeners.
116      * Key: slot number of keepalive offload.
117      * Value: {@link FileDescriptor} being listened to.
118      */
119     @GuardedBy("mListeners")
120     private final SparseArray<FileDescriptor> mListeners = new SparseArray<>();
121 
TcpKeepaliveController(final Handler connectivityServiceHandler)122     public TcpKeepaliveController(final Handler connectivityServiceHandler) {
123         mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue();
124         mConnectivityServiceHandler = connectivityServiceHandler;
125     }
126 
127     /** Build tcp keepalive packet. */
getTcpKeepalivePacket(@onNull FileDescriptor fd)128     public static TcpKeepalivePacketData getTcpKeepalivePacket(@NonNull FileDescriptor fd)
129             throws InvalidPacketException, InvalidSocketException {
130         try {
131             final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd);
132             // TODO: consider building a TcpKeepalivePacketData directly from switchToRepairMode
133             return fromStableParcelable(tcpDetails);
134         // Use separate catch blocks: a combined catch would get wrongly optimized by R8
135         // (b/226127213).
136         } catch (InvalidSocketException e) {
137             switchOutOfRepairMode(fd);
138             throw e;
139         } catch (InvalidPacketException e) {
140             switchOutOfRepairMode(fd);
141             throw e;
142         }
143     }
144 
145     /**
146      * Factory method to create tcp keepalive packet structure.
147      */
148     @VisibleForTesting
fromStableParcelable( TcpKeepalivePacketDataParcelable tcpDetails)149     public static TcpKeepalivePacketData fromStableParcelable(
150             TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException {
151         final byte[] packet;
152         try {
153             if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null)
154                     && (tcpDetails.srcAddress.length == 4 /* V4 IP length */)
155                     && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) {
156                 packet = buildV4Packet(tcpDetails);
157             } else {
158                 // TODO: support ipv6
159                 throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
160             }
161             return new TcpKeepalivePacketData(
162                     InetAddress.getByAddress(tcpDetails.srcAddress),
163                     tcpDetails.srcPort,
164                     InetAddress.getByAddress(tcpDetails.dstAddress),
165                     tcpDetails.dstPort,
166                     packet,
167                     tcpDetails.seq, tcpDetails.ack, tcpDetails.rcvWnd, tcpDetails.rcvWndScale,
168                     tcpDetails.tos, tcpDetails.ttl);
169         } catch (UnknownHostException e) {
170             throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
171         }
172     }
173 
174     /**
175      * Build ipv4 tcp keepalive packet, not including the link-layer header.
176      */
177     // TODO : if this code is ever moved to the network stack, factorize constants with the ones
178     // over there.
179     // TODO: consider using Ipv4Utils.buildTcpv4Packet() instead
buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails)180     private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) {
181         final int length = IPV4_HEADER_MIN_LEN + TCP_HEADER_LENGTH;
182         ByteBuffer buf = ByteBuffer.allocate(length);
183         buf.order(ByteOrder.BIG_ENDIAN);
184         buf.put((byte) 0x45);                       // IP version and IHL
185         buf.put((byte) tcpDetails.tos);             // TOS
186         buf.putShort((short) length);
187         buf.putInt(0x00004000);                     // ID, flags=DF, offset
188         buf.put((byte) tcpDetails.ttl);             // TTL
189         buf.put((byte) IPPROTO_TCP);
190         final int ipChecksumOffset = buf.position();
191         buf.putShort((short) 0);                    // IP checksum
192         buf.put(tcpDetails.srcAddress);
193         buf.put(tcpDetails.dstAddress);
194         buf.putShort((short) tcpDetails.srcPort);
195         buf.putShort((short) tcpDetails.dstPort);
196         buf.putInt(tcpDetails.seq);                 // Sequence Number
197         buf.putInt(tcpDetails.ack);                 // ACK
198         buf.putShort((short) 0x5010);               // TCP length=5, flags=ACK
199         buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale));   // Window size
200         final int tcpChecksumOffset = buf.position();
201         buf.putShort((short) 0);                    // TCP checksum
202         // URG is not set therefore the urgent pointer is zero.
203         buf.putShort((short) 0);                    // Urgent pointer
204 
205         buf.putShort(ipChecksumOffset, com.android.net.module.util.IpUtils.ipChecksum(buf, 0));
206         buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
207                 buf, 0, IPV4_HEADER_MIN_LEN, TCP_HEADER_LENGTH));
208 
209         return buf.array();
210     }
211 
212     /**
213      * Switch the tcp socket to repair mode and query detail tcp information.
214      *
215      * @param fd the fd of socket on which to use keepalive offload.
216      * @return a {@link TcpKeepalivePacketDataParcelable} object for current
217      * tcp/ip information.
218      */
switchToRepairMode(FileDescriptor fd)219     private static TcpKeepalivePacketDataParcelable switchToRepairMode(FileDescriptor fd)
220             throws InvalidSocketException {
221         if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd);
222         final TcpKeepalivePacketDataParcelable tcpDetails = new TcpKeepalivePacketDataParcelable();
223         final SocketAddress srcSockAddr;
224         final SocketAddress dstSockAddr;
225         final TcpRepairWindow trw;
226 
227         // Query source address and port.
228         try {
229             srcSockAddr = Os.getsockname(fd);
230         } catch (ErrnoException e) {
231             Log.e(TAG, "Get sockname fail: ", e);
232             throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
233         }
234         if (srcSockAddr instanceof InetSocketAddress) {
235             tcpDetails.srcAddress = getAddress((InetSocketAddress) srcSockAddr);
236             tcpDetails.srcPort = getPort((InetSocketAddress) srcSockAddr);
237         } else {
238             Log.e(TAG, "Invalid or mismatched SocketAddress");
239             throw new InvalidSocketException(ERROR_INVALID_SOCKET);
240         }
241         // Query destination address and port.
242         try {
243             dstSockAddr = Os.getpeername(fd);
244         } catch (ErrnoException e) {
245             Log.e(TAG, "Get peername fail: ", e);
246             throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
247         }
248         if (dstSockAddr instanceof InetSocketAddress) {
249             tcpDetails.dstAddress = getAddress((InetSocketAddress) dstSockAddr);
250             tcpDetails.dstPort = getPort((InetSocketAddress) dstSockAddr);
251         } else {
252             Log.e(TAG, "Invalid or mismatched peer SocketAddress");
253             throw new InvalidSocketException(ERROR_INVALID_SOCKET);
254         }
255 
256         // Query sequence and ack number
257         dropAllIncomingPackets(fd, true);
258         try {
259             // Switch to tcp repair mode.
260             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON);
261 
262             // Check if socket is idle.
263             if (!isSocketIdle(fd)) {
264                 Log.e(TAG, "Socket is not idle");
265                 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
266             }
267             // Query write sequence number from SEND_QUEUE.
268             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
269             tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
270             // Query read sequence number from RECV_QUEUE.
271             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
272             tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
273             // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
274             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
275             // Finally, check if socket is still idle. TODO : this check needs to move to
276             // after starting polling to prevent a race.
277             if (!isReceiveQueueEmpty(fd)) {
278                 Log.e(TAG, "Fatal: receive queue of this socket is not empty");
279                 throw new InvalidSocketException(ERROR_INVALID_SOCKET);
280             }
281             if (!isSendQueueEmpty(fd)) {
282                 Log.e(TAG, "Socket is not idle");
283                 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
284             }
285 
286             // Query tcp window size.
287             trw = NetworkUtils.getTcpRepairWindow(fd);
288             tcpDetails.rcvWnd = trw.rcvWnd;
289             tcpDetails.rcvWndScale = trw.rcvWndScale;
290             if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
291                 // Query TOS.
292                 tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
293                 // Query TTL.
294                 tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
295             }
296         } catch (ErrnoException e) {
297             Log.e(TAG, "Exception reading TCP state from socket", e);
298             if (e.errno == ENOPROTOOPT) {
299                 // ENOPROTOOPT may happen in kernel version lower than 4.8.
300                 // Treat it as ERROR_UNSUPPORTED.
301                 throw new InvalidSocketException(ERROR_UNSUPPORTED, e);
302             } else {
303                 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
304             }
305         } finally {
306             dropAllIncomingPackets(fd, false);
307         }
308 
309         // Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved,
310         // then it must be set to -1, so decrement in all cases.
311         tcpDetails.seq = tcpDetails.seq - 1;
312 
313         return tcpDetails;
314     }
315 
316     /**
317      * Switch the tcp socket out of repair mode.
318      *
319      * @param fd the fd of socket to switch back to normal.
320      */
switchOutOfRepairMode(@onNull final FileDescriptor fd)321     private static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) {
322         try {
323             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
324         } catch (ErrnoException e) {
325             Log.e(TAG, "Cannot switch socket out of repair mode", e);
326             // Well, there is not much to do here to recover
327         }
328     }
329 
330     /**
331      * Start monitoring incoming packets.
332      *
333      * @param fd socket fd to monitor.
334      * @param callback a {@link ISocketKeepaliveCallback} that tracks information about a socket
335      *                 keepalive.
336      * @param slot keepalive slot.
337      */
startSocketMonitor( @onNull final FileDescriptor fd, @NonNull final ISocketKeepaliveCallback callback, final int slot)338     public void startSocketMonitor(
339             @NonNull final FileDescriptor fd, @NonNull final ISocketKeepaliveCallback callback,
340             final int slot) throws IllegalArgumentException, InvalidSocketException {
341         synchronized (mListeners) {
342             if (null != mListeners.get(slot)) {
343                 throw new IllegalArgumentException("This slot is already taken");
344             }
345             for (int i = 0; i < mListeners.size(); ++i) {
346                 if (fd.equals(mListeners.valueAt(i))) {
347                     Log.e(TAG, "This fd is already registered.");
348                     throw new InvalidSocketException(ERROR_INVALID_SOCKET);
349                 }
350             }
351             mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> {
352                 // This can't be called twice because the queue guarantees that once the listener
353                 // is unregistered it can't be called again, even for a message that arrived
354                 // before it was unregistered.
355                 final int reason;
356                 if (0 != (events & EVENT_ERROR)) {
357                     reason = ERROR_INVALID_SOCKET;
358                 } else {
359                     reason = DATA_RECEIVED;
360                 }
361                 mConnectivityServiceHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE,
362                         0 /* unused */, reason, callback.asBinder()).sendToTarget();
363                 // The listener returns the new set of events to listen to. Because 0 means no
364                 // event, the listener gets unregistered.
365                 return 0;
366             });
367             mListeners.put(slot, fd);
368         }
369     }
370 
371     /** Stop socket monitor */
372     // This slot may have been stopped automatically already because the socket received data,
373     // was closed on the other end or otherwise suffered some error. In this case, this function
374     // is a no-op.
stopSocketMonitor(final int slot)375     public void stopSocketMonitor(final int slot) {
376         final FileDescriptor fd;
377         synchronized (mListeners) {
378             fd = mListeners.get(slot);
379             if (null == fd) return;
380             mListeners.remove(slot);
381         }
382         mFdHandlerQueue.removeOnFileDescriptorEventListener(fd);
383         if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd);
384         switchOutOfRepairMode(fd);
385     }
386 
getAddress(InetSocketAddress inetAddr)387     private static byte [] getAddress(InetSocketAddress inetAddr) {
388         return inetAddr.getAddress().getAddress();
389     }
390 
getPort(InetSocketAddress inetAddr)391     private static int getPort(InetSocketAddress inetAddr) {
392         return inetAddr.getPort();
393     }
394 
isSocketIdle(FileDescriptor fd)395     private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException {
396         return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd);
397     }
398 
isReceiveQueueEmpty(FileDescriptor fd)399     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
400             throws ErrnoException {
401         final int result = Os.ioctlInt(fd, SIOCINQ);
402         if (result != 0) {
403             Log.e(TAG, "Read queue has data");
404             return false;
405         }
406         return true;
407     }
408 
isSendQueueEmpty(FileDescriptor fd)409     private static boolean isSendQueueEmpty(FileDescriptor fd)
410             throws ErrnoException {
411         final int result = Os.ioctlInt(fd, SIOCOUTQ);
412         if (result != 0) {
413             Log.e(TAG, "Write queue has data");
414             return false;
415         }
416         return true;
417     }
418 
dropAllIncomingPackets(FileDescriptor fd, boolean enable)419     private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable)
420             throws InvalidSocketException {
421         try {
422             if (enable) {
423                 NetworkUtils.attachDropAllBPFFilter(fd);
424             } else {
425                 NetworkUtils.detachBPFFilter(fd);
426             }
427         } catch (SocketException e) {
428             Log.e(TAG, "Socket Exception: ", e);
429             throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
430         }
431     }
432 }
433