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.SocketKeepalive.DATA_RECEIVED;
19 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
20 import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
21 import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
22 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
23 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
24 import static android.system.OsConstants.ENOPROTOOPT;
25 import static android.system.OsConstants.FIONREAD;
26 import static android.system.OsConstants.IPPROTO_IP;
27 import static android.system.OsConstants.IPPROTO_TCP;
28 import static android.system.OsConstants.IP_TOS;
29 import static android.system.OsConstants.IP_TTL;
30 import static android.system.OsConstants.TIOCOUTQ;
31 
32 import android.annotation.NonNull;
33 import android.net.InvalidPacketException;
34 import android.net.NetworkUtils;
35 import android.net.SocketKeepalive.InvalidSocketException;
36 import android.net.TcpKeepalivePacketData;
37 import android.net.TcpKeepalivePacketDataParcelable;
38 import android.net.TcpRepairWindow;
39 import android.os.Handler;
40 import android.os.MessageQueue;
41 import android.os.Messenger;
42 import android.system.ErrnoException;
43 import android.system.Int32Ref;
44 import android.system.Os;
45 import android.util.Log;
46 import android.util.SparseArray;
47 
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
50 
51 import java.io.FileDescriptor;
52 import java.net.InetSocketAddress;
53 import java.net.SocketAddress;
54 import java.net.SocketException;
55 
56 /**
57  * Manage tcp socket which offloads tcp keepalive.
58  *
59  * The input socket will be changed to repair mode and the application
60  * will not have permission to read/write data. If the application wants
61  * to write data, it must stop tcp keepalive offload to leave repair mode
62  * first. If a remote packet arrives, repair mode will be turned off and
63  * offload will be stopped. The application will receive a callback to know
64  * it can start reading data.
65  *
66  * {start,stop}SocketMonitor are thread-safe, but care must be taken in the
67  * order in which they are called. Please note that while calling
68  * {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times
69  * with either the same slot or the same FileDescriptor without stopping it in
70  * between will result in an exception, calling {@link #stopSocketMonitor(int)}
71  * multiple times with the same int is explicitly a no-op.
72  * Please also note that switching the socket to repair mode is not synchronized
73  * with either of these operations and has to be done in an orderly fashion
74  * with stopSocketMonitor. Take care in calling these in the right order.
75  * @hide
76  */
77 public class TcpKeepaliveController {
78     private static final String TAG = "TcpKeepaliveController";
79     private static final boolean DBG = false;
80 
81     private final MessageQueue mFdHandlerQueue;
82 
83     private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
84 
85     // Reference include/uapi/linux/tcp.h
86     private static final int TCP_REPAIR = 19;
87     private static final int TCP_REPAIR_QUEUE = 20;
88     private static final int TCP_QUEUE_SEQ = 21;
89     private static final int TCP_NO_QUEUE = 0;
90     private static final int TCP_RECV_QUEUE = 1;
91     private static final int TCP_SEND_QUEUE = 2;
92     private static final int TCP_REPAIR_OFF = 0;
93     private static final int TCP_REPAIR_ON = 1;
94     // Reference include/uapi/linux/sockios.h
95     private static final int SIOCINQ = FIONREAD;
96     private static final int SIOCOUTQ = TIOCOUTQ;
97 
98     /**
99      * Keeps track of packet listeners.
100      * Key: slot number of keepalive offload.
101      * Value: {@link FileDescriptor} being listened to.
102      */
103     @GuardedBy("mListeners")
104     private final SparseArray<FileDescriptor> mListeners = new SparseArray<>();
105 
TcpKeepaliveController(final Handler connectivityServiceHandler)106     public TcpKeepaliveController(final Handler connectivityServiceHandler) {
107         mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue();
108     }
109 
110     /** Build tcp keepalive packet. */
getTcpKeepalivePacket(@onNull FileDescriptor fd)111     public static TcpKeepalivePacketData getTcpKeepalivePacket(@NonNull FileDescriptor fd)
112             throws InvalidPacketException, InvalidSocketException {
113         try {
114             final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd);
115             return TcpKeepalivePacketData.tcpKeepalivePacket(tcpDetails);
116         } catch (InvalidPacketException | InvalidSocketException e) {
117             switchOutOfRepairMode(fd);
118             throw e;
119         }
120     }
121     /**
122      * Switch the tcp socket to repair mode and query detail tcp information.
123      *
124      * @param fd the fd of socket on which to use keepalive offload.
125      * @return a {@link TcpKeepalivePacketData#TcpKeepalivePacketDataParcelable} object for current
126      * tcp/ip information.
127      */
switchToRepairMode(FileDescriptor fd)128     private static TcpKeepalivePacketDataParcelable switchToRepairMode(FileDescriptor fd)
129             throws InvalidSocketException {
130         if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd);
131         final TcpKeepalivePacketDataParcelable tcpDetails = new TcpKeepalivePacketDataParcelable();
132         final SocketAddress srcSockAddr;
133         final SocketAddress dstSockAddr;
134         final TcpRepairWindow trw;
135 
136         // Query source address and port.
137         try {
138             srcSockAddr = Os.getsockname(fd);
139         } catch (ErrnoException e) {
140             Log.e(TAG, "Get sockname fail: ", e);
141             throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
142         }
143         if (srcSockAddr instanceof InetSocketAddress) {
144             tcpDetails.srcAddress = getAddress((InetSocketAddress) srcSockAddr);
145             tcpDetails.srcPort = getPort((InetSocketAddress) srcSockAddr);
146         } else {
147             Log.e(TAG, "Invalid or mismatched SocketAddress");
148             throw new InvalidSocketException(ERROR_INVALID_SOCKET);
149         }
150         // Query destination address and port.
151         try {
152             dstSockAddr = Os.getpeername(fd);
153         } catch (ErrnoException e) {
154             Log.e(TAG, "Get peername fail: ", e);
155             throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
156         }
157         if (dstSockAddr instanceof InetSocketAddress) {
158             tcpDetails.dstAddress = getAddress((InetSocketAddress) dstSockAddr);
159             tcpDetails.dstPort = getPort((InetSocketAddress) dstSockAddr);
160         } else {
161             Log.e(TAG, "Invalid or mismatched peer SocketAddress");
162             throw new InvalidSocketException(ERROR_INVALID_SOCKET);
163         }
164 
165         // Query sequence and ack number
166         dropAllIncomingPackets(fd, true);
167         try {
168             // Switch to tcp repair mode.
169             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON);
170 
171             // Check if socket is idle.
172             if (!isSocketIdle(fd)) {
173                 Log.e(TAG, "Socket is not idle");
174                 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
175             }
176             // Query write sequence number from SEND_QUEUE.
177             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
178             tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
179             // Query read sequence number from RECV_QUEUE.
180             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
181             tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
182             // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
183             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
184             // Finally, check if socket is still idle. TODO : this check needs to move to
185             // after starting polling to prevent a race.
186             if (!isReceiveQueueEmpty(fd)) {
187                 Log.e(TAG, "Fatal: receive queue of this socket is not empty");
188                 throw new InvalidSocketException(ERROR_INVALID_SOCKET);
189             }
190             if (!isSendQueueEmpty(fd)) {
191                 Log.e(TAG, "Socket is not idle");
192                 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
193             }
194 
195             // Query tcp window size.
196             trw = NetworkUtils.getTcpRepairWindow(fd);
197             tcpDetails.rcvWnd = trw.rcvWnd;
198             tcpDetails.rcvWndScale = trw.rcvWndScale;
199             if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
200                 // Query TOS.
201                 tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
202                 // Query TTL.
203                 tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
204             }
205         } catch (ErrnoException e) {
206             Log.e(TAG, "Exception reading TCP state from socket", e);
207             if (e.errno == ENOPROTOOPT) {
208                 // ENOPROTOOPT may happen in kernel version lower than 4.8.
209                 // Treat it as ERROR_UNSUPPORTED.
210                 throw new InvalidSocketException(ERROR_UNSUPPORTED, e);
211             } else {
212                 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
213             }
214         } finally {
215             dropAllIncomingPackets(fd, false);
216         }
217 
218         // Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved,
219         // then it must be set to -1, so decrement in all cases.
220         tcpDetails.seq = tcpDetails.seq - 1;
221 
222         return tcpDetails;
223     }
224 
225     /**
226      * Switch the tcp socket out of repair mode.
227      *
228      * @param fd the fd of socket to switch back to normal.
229      */
switchOutOfRepairMode(@onNull final FileDescriptor fd)230     private static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) {
231         try {
232             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
233         } catch (ErrnoException e) {
234             Log.e(TAG, "Cannot switch socket out of repair mode", e);
235             // Well, there is not much to do here to recover
236         }
237     }
238 
239     /**
240      * Start monitoring incoming packets.
241      *
242      * @param fd socket fd to monitor.
243      * @param ki a {@link KeepaliveInfo} that tracks information about a socket keepalive.
244      * @param slot keepalive slot.
245      */
startSocketMonitor(@onNull final FileDescriptor fd, @NonNull final KeepaliveInfo ki, final int slot)246     public void startSocketMonitor(@NonNull final FileDescriptor fd,
247             @NonNull final KeepaliveInfo ki, final int slot)
248             throws IllegalArgumentException, InvalidSocketException {
249         synchronized (mListeners) {
250             if (null != mListeners.get(slot)) {
251                 throw new IllegalArgumentException("This slot is already taken");
252             }
253             for (int i = 0; i < mListeners.size(); ++i) {
254                 if (fd.equals(mListeners.valueAt(i))) {
255                     Log.e(TAG, "This fd is already registered.");
256                     throw new InvalidSocketException(ERROR_INVALID_SOCKET);
257                 }
258             }
259             mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> {
260                 // This can't be called twice because the queue guarantees that once the listener
261                 // is unregistered it can't be called again, even for a message that arrived
262                 // before it was unregistered.
263                 final int reason;
264                 if (0 != (events & EVENT_ERROR)) {
265                     reason = ERROR_INVALID_SOCKET;
266                 } else {
267                     reason = DATA_RECEIVED;
268                 }
269                 ki.onFileDescriptorInitiatedStop(reason);
270                 // The listener returns the new set of events to listen to. Because 0 means no
271                 // event, the listener gets unregistered.
272                 return 0;
273             });
274             mListeners.put(slot, fd);
275         }
276     }
277 
278     /** Stop socket monitor */
279     // This slot may have been stopped automatically already because the socket received data,
280     // was closed on the other end or otherwise suffered some error. In this case, this function
281     // is a no-op.
stopSocketMonitor(final int slot)282     public void stopSocketMonitor(final int slot) {
283         final FileDescriptor fd;
284         synchronized (mListeners) {
285             fd = mListeners.get(slot);
286             if (null == fd) return;
287             mListeners.remove(slot);
288         }
289         mFdHandlerQueue.removeOnFileDescriptorEventListener(fd);
290         if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd);
291         switchOutOfRepairMode(fd);
292     }
293 
getAddress(InetSocketAddress inetAddr)294     private static byte [] getAddress(InetSocketAddress inetAddr) {
295         return inetAddr.getAddress().getAddress();
296     }
297 
getPort(InetSocketAddress inetAddr)298     private static int getPort(InetSocketAddress inetAddr) {
299         return inetAddr.getPort();
300     }
301 
isSocketIdle(FileDescriptor fd)302     private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException {
303         return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd);
304     }
305 
isReceiveQueueEmpty(FileDescriptor fd)306     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
307             throws ErrnoException {
308         Int32Ref result = new Int32Ref(-1);
309         Os.ioctlInt(fd, SIOCINQ, result);
310         if (result.value != 0) {
311             Log.e(TAG, "Read queue has data");
312             return false;
313         }
314         return true;
315     }
316 
isSendQueueEmpty(FileDescriptor fd)317     private static boolean isSendQueueEmpty(FileDescriptor fd)
318             throws ErrnoException {
319         Int32Ref result = new Int32Ref(-1);
320         Os.ioctlInt(fd, SIOCOUTQ, result);
321         if (result.value != 0) {
322             Log.e(TAG, "Write queue has data");
323             return false;
324         }
325         return true;
326     }
327 
dropAllIncomingPackets(FileDescriptor fd, boolean enable)328     private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable)
329             throws InvalidSocketException {
330         try {
331             if (enable) {
332                 NetworkUtils.attachDropAllBPFFilter(fd);
333             } else {
334                 NetworkUtils.detachBPFFilter(fd);
335             }
336         } catch (SocketException e) {
337             Log.e(TAG, "Socket Exception: ", e);
338             throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
339         }
340     }
341 }
342