blob: 1129899ee3ff22c3dabb200789e31246ad13ea28 [file] [log] [blame]
markchien150e1912018-12-27 22:49:51 +08001/*
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 */
16package com.android.server.connectivity;
17
markchien150e1912018-12-27 22:49:51 +080018import static android.net.SocketKeepalive.DATA_RECEIVED;
19import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
20import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
junyulai0c666972019-03-04 22:45:36 +080021import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
markchien150e1912018-12-27 22:49:51 +080022import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
23import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
markchience7fdd42019-02-20 22:47:08 +080024import static android.system.OsConstants.ENOPROTOOPT;
markchien150e1912018-12-27 22:49:51 +080025import static android.system.OsConstants.FIONREAD;
markchien1fc82b22019-03-21 22:26:54 +080026import static android.system.OsConstants.IPPROTO_IP;
markchien150e1912018-12-27 22:49:51 +080027import static android.system.OsConstants.IPPROTO_TCP;
markchien1fc82b22019-03-21 22:26:54 +080028import static android.system.OsConstants.IP_TOS;
29import static android.system.OsConstants.IP_TTL;
markchien150e1912018-12-27 22:49:51 +080030import static android.system.OsConstants.TIOCOUTQ;
31
32import android.annotation.NonNull;
Aaron Huang17c660d2019-10-02 01:39:46 +080033import android.net.InvalidPacketException;
markchien150e1912018-12-27 22:49:51 +080034import android.net.NetworkUtils;
35import android.net.SocketKeepalive.InvalidSocketException;
markchien656fbe92019-02-20 21:16:44 +080036import android.net.TcpKeepalivePacketData;
markchiend42fca62019-03-19 21:25:33 +080037import android.net.TcpKeepalivePacketDataParcelable;
markchien150e1912018-12-27 22:49:51 +080038import android.net.TcpRepairWindow;
39import android.os.Handler;
markchien150e1912018-12-27 22:49:51 +080040import android.os.MessageQueue;
41import android.os.Messenger;
markchien150e1912018-12-27 22:49:51 +080042import android.system.ErrnoException;
43import android.system.Int32Ref;
44import android.system.Os;
45import android.util.Log;
46import android.util.SparseArray;
47
48import com.android.internal.annotations.GuardedBy;
junyulai352dc2f2019-01-08 20:04:33 +080049import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
markchien150e1912018-12-27 22:49:51 +080050
51import java.io.FileDescriptor;
markchien150e1912018-12-27 22:49:51 +080052import java.net.InetSocketAddress;
53import java.net.SocketAddress;
54import 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 */
77public 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
106 public TcpKeepaliveController(final Handler connectivityServiceHandler) {
107 mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue();
108 }
109
markchien656fbe92019-02-20 21:16:44 +0800110 /** Build tcp keepalive packet. */
111 public static TcpKeepalivePacketData getTcpKeepalivePacket(@NonNull FileDescriptor fd)
112 throws InvalidPacketException, InvalidSocketException {
113 try {
markchiend42fca62019-03-19 21:25:33 +0800114 final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd);
115 return TcpKeepalivePacketData.tcpKeepalivePacket(tcpDetails);
markchien656fbe92019-02-20 21:16:44 +0800116 } catch (InvalidPacketException | InvalidSocketException e) {
117 switchOutOfRepairMode(fd);
118 throw e;
119 }
120 }
markchien150e1912018-12-27 22:49:51 +0800121 /**
markchien656fbe92019-02-20 21:16:44 +0800122 * Switch the tcp socket to repair mode and query detail tcp information.
markchien150e1912018-12-27 22:49:51 +0800123 *
markchien656fbe92019-02-20 21:16:44 +0800124 * @param fd the fd of socket on which to use keepalive offload.
markchiend42fca62019-03-19 21:25:33 +0800125 * @return a {@link TcpKeepalivePacketData#TcpKeepalivePacketDataParcelable} object for current
markchien150e1912018-12-27 22:49:51 +0800126 * tcp/ip information.
127 */
markchiend42fca62019-03-19 21:25:33 +0800128 private static TcpKeepalivePacketDataParcelable switchToRepairMode(FileDescriptor fd)
markchien150e1912018-12-27 22:49:51 +0800129 throws InvalidSocketException {
130 if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd);
markchiend42fca62019-03-19 21:25:33 +0800131 final TcpKeepalivePacketDataParcelable tcpDetails = new TcpKeepalivePacketDataParcelable();
markchien150e1912018-12-27 22:49:51 +0800132 final SocketAddress srcSockAddr;
133 final SocketAddress dstSockAddr;
markchien150e1912018-12-27 22:49:51 +0800134 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) {
markchiend42fca62019-03-19 21:25:33 +0800144 tcpDetails.srcAddress = getAddress((InetSocketAddress) srcSockAddr);
145 tcpDetails.srcPort = getPort((InetSocketAddress) srcSockAddr);
markchien150e1912018-12-27 22:49:51 +0800146 } 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) {
markchiend42fca62019-03-19 21:25:33 +0800158 tcpDetails.dstAddress = getAddress((InetSocketAddress) dstSockAddr);
159 tcpDetails.dstPort = getPort((InetSocketAddress) dstSockAddr);
markchien150e1912018-12-27 22:49:51 +0800160 } 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 {
markchien656fbe92019-02-20 21:16:44 +0800168 // Switch to tcp repair mode.
markchien150e1912018-12-27 22:49:51 +0800169 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON);
markchien656fbe92019-02-20 21:16:44 +0800170
markchien150e1912018-12-27 22:49:51 +0800171 // Check if socket is idle.
172 if (!isSocketIdle(fd)) {
markchien656fbe92019-02-20 21:16:44 +0800173 Log.e(TAG, "Socket is not idle");
markchien150e1912018-12-27 22:49:51 +0800174 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);
markchiend42fca62019-03-19 21:25:33 +0800178 tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
markchien150e1912018-12-27 22:49:51 +0800179 // Query read sequence number from RECV_QUEUE.
180 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
markchiend42fca62019-03-19 21:25:33 +0800181 tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
markchien150e1912018-12-27 22:49:51 +0800182 // 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.
markchien656fbe92019-02-20 21:16:44 +0800186 if (!isReceiveQueueEmpty(fd)) {
187 Log.e(TAG, "Fatal: receive queue of this socket is not empty");
markchien150e1912018-12-27 22:49:51 +0800188 throw new InvalidSocketException(ERROR_INVALID_SOCKET);
189 }
markchien656fbe92019-02-20 21:16:44 +0800190 if (!isSendQueueEmpty(fd)) {
191 Log.e(TAG, "Socket is not idle");
192 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
193 }
markchien150e1912018-12-27 22:49:51 +0800194
195 // Query tcp window size.
196 trw = NetworkUtils.getTcpRepairWindow(fd);
markchiend42fca62019-03-19 21:25:33 +0800197 tcpDetails.rcvWnd = trw.rcvWnd;
198 tcpDetails.rcvWndScale = trw.rcvWndScale;
markchien1fc82b22019-03-21 22:26:54 +0800199 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 }
markchien150e1912018-12-27 22:49:51 +0800205 } catch (ErrnoException e) {
206 Log.e(TAG, "Exception reading TCP state from socket", e);
markchience7fdd42019-02-20 22:47:08 +0800207 if (e.errno == ENOPROTOOPT) {
208 // ENOPROTOOPT may happen in kernel version lower than 4.8.
junyulai0c666972019-03-04 22:45:36 +0800209 // Treat it as ERROR_UNSUPPORTED.
210 throw new InvalidSocketException(ERROR_UNSUPPORTED, e);
markchience7fdd42019-02-20 22:47:08 +0800211 } else {
212 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
markchien150e1912018-12-27 22:49:51 +0800213 }
markchien150e1912018-12-27 22:49:51 +0800214 } 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.
markchiend42fca62019-03-19 21:25:33 +0800220 tcpDetails.seq = tcpDetails.seq - 1;
markchien150e1912018-12-27 22:49:51 +0800221
markchiend42fca62019-03-19 21:25:33 +0800222 return tcpDetails;
markchien150e1912018-12-27 22:49:51 +0800223 }
224
junyulai352dc2f2019-01-08 20:04:33 +0800225 /**
226 * Switch the tcp socket out of repair mode.
227 *
228 * @param fd the fd of socket to switch back to normal.
229 */
markchien656fbe92019-02-20 21:16:44 +0800230 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 }
markchien150e1912018-12-27 22:49:51 +0800237 }
238
239 /**
240 * Start monitoring incoming packets.
241 *
242 * @param fd socket fd to monitor.
junyulai1d5cd192019-03-04 11:49:17 +0800243 * @param ki a {@link KeepaliveInfo} that tracks information about a socket keepalive.
markchien150e1912018-12-27 22:49:51 +0800244 * @param slot keepalive slot.
245 */
246 public void startSocketMonitor(@NonNull final FileDescriptor fd,
junyulai1d5cd192019-03-04 11:49:17 +0800247 @NonNull final KeepaliveInfo ki, final int slot)
248 throws IllegalArgumentException, InvalidSocketException {
markchien150e1912018-12-27 22:49:51 +0800249 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))) {
junyulai1d5cd192019-03-04 11:49:17 +0800255 Log.e(TAG, "This fd is already registered.");
256 throw new InvalidSocketException(ERROR_INVALID_SOCKET);
markchien150e1912018-12-27 22:49:51 +0800257 }
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.
junyulai352dc2f2019-01-08 20:04:33 +0800263 final int reason;
264 if (0 != (events & EVENT_ERROR)) {
265 reason = ERROR_INVALID_SOCKET;
266 } else {
267 reason = DATA_RECEIVED;
markchien150e1912018-12-27 22:49:51 +0800268 }
junyulai352dc2f2019-01-08 20:04:33 +0800269 ki.onFileDescriptorInitiatedStop(reason);
markchien150e1912018-12-27 22:49:51 +0800270 // 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.
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);
markchien656fbe92019-02-20 21:16:44 +0800290 if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd);
291 switchOutOfRepairMode(fd);
markchien150e1912018-12-27 22:49:51 +0800292 }
293
markchiend42fca62019-03-19 21:25:33 +0800294 private static byte [] getAddress(InetSocketAddress inetAddr) {
295 return inetAddr.getAddress().getAddress();
markchien150e1912018-12-27 22:49:51 +0800296 }
297
298 private static int getPort(InetSocketAddress inetAddr) {
299 return inetAddr.getPort();
300 }
301
302 private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException {
303 return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd);
304 }
305
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
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
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}