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