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