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