• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.server.connectivity;
18 
19 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
20 import static android.net.NattSocketKeepalive.NATT_PORT;
21 import static android.net.SocketKeepalive.BINDER_DIED;
22 import static android.net.SocketKeepalive.DATA_RECEIVED;
23 import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
24 import static android.net.SocketKeepalive.ERROR_INVALID_INTERVAL;
25 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
26 import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
27 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
28 import static android.net.SocketKeepalive.ERROR_NO_SUCH_SLOT;
29 import static android.net.SocketKeepalive.ERROR_STOP_REASON_UNINITIALIZED;
30 import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
31 import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
32 import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
33 import static android.net.SocketKeepalive.NO_KEEPALIVE;
34 import static android.net.SocketKeepalive.SUCCESS;
35 import static android.net.SocketKeepalive.SUCCESS_PAUSED;
36 
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.content.Context;
40 import android.net.ISocketKeepaliveCallback;
41 import android.net.InetAddresses;
42 import android.net.InvalidPacketException;
43 import android.net.KeepalivePacketData;
44 import android.net.NattKeepalivePacketData;
45 import android.net.NetworkAgent;
46 import android.net.SocketKeepalive.InvalidSocketException;
47 import android.net.TcpKeepalivePacketData;
48 import android.net.util.KeepaliveUtils;
49 import android.os.Binder;
50 import android.os.Handler;
51 import android.os.IBinder;
52 import android.os.Process;
53 import android.os.RemoteException;
54 import android.system.ErrnoException;
55 import android.system.Os;
56 import android.util.Log;
57 
58 import com.android.connectivity.resources.R;
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.util.IndentingPrintWriter;
61 import com.android.net.module.util.HexDump;
62 import com.android.net.module.util.IpUtils;
63 
64 import java.io.FileDescriptor;
65 import java.net.InetAddress;
66 import java.net.InetSocketAddress;
67 import java.net.SocketAddress;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.HashMap;
71 
72 /**
73  * Manages socket keepalive requests.
74  *
75  * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all
76  * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its
77  * handle* methods must be called only from the ConnectivityService handler thread.
78  */
79 public class KeepaliveTracker {
80 
81     private static final String TAG = "KeepaliveTracker";
82     private static final boolean DBG = false;
83 
84     public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
85 
86     /** Keeps track of keepalive requests. */
87     private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
88             new HashMap<> ();
89     @NonNull
90     private final TcpKeepaliveController mTcpController;
91     @NonNull
92     private final Context mContext;
93 
94     // Supported keepalive count for each transport type, can be configured through
95     // config_networkSupportedKeepaliveCount. For better error handling, use
96     // {@link getSupportedKeepalivesForNetworkCapabilities} instead of direct access.
97     @NonNull
98     private final int[] mSupportedKeepalives;
99 
100     // Reserved privileged keepalive slots per transport. Caller's permission will be enforced if
101     // the number of remaining keepalive slots is less than or equal to the threshold.
102     private final int mReservedPrivilegedSlots;
103 
104     // Allowed unprivileged keepalive slots per uid. Caller's permission will be enforced if
105     // the number of remaining keepalive slots is less than or equal to the threshold.
106     private final int mAllowedUnprivilegedSlotsForUid;
107 
KeepaliveTracker(Context context, Handler handler)108     public KeepaliveTracker(Context context, Handler handler) {
109         this(context, handler, new TcpKeepaliveController(handler));
110     }
111 
112     @VisibleForTesting
KeepaliveTracker(Context context, Handler handler, TcpKeepaliveController tcpController)113     KeepaliveTracker(Context context, Handler handler, TcpKeepaliveController tcpController) {
114         mTcpController = tcpController;
115         mContext = context;
116 
117         mSupportedKeepalives = KeepaliveResourceUtil.getSupportedKeepalives(context);
118 
119         final ConnectivityResources res = new ConnectivityResources(mContext);
120         mReservedPrivilegedSlots = res.get().getInteger(
121                 R.integer.config_reservedPrivilegedKeepaliveSlots);
122         mAllowedUnprivilegedSlotsForUid = res.get().getInteger(
123                 R.integer.config_allowedUnprivilegedKeepalivePerUid);
124     }
125 
126     /**
127      * Tracks information about a socket keepalive.
128      *
129      * All information about this keepalive is known at construction time except the slot number,
130      * which is only returned when the hardware has successfully started the keepalive.
131      */
132     @VisibleForTesting
133     public class KeepaliveInfo implements IBinder.DeathRecipient {
134         // TODO : remove this member. Only AutoOnOffKeepalive should have a reference to this.
135         public final ISocketKeepaliveCallback mCallback;
136         // Bookkeeping data.
137         private final int mUid;
138         private final int mPid;
139         private final boolean mPrivileged;
140         public final NetworkAgentInfo mNai;
141         private final int mType;
142         public final FileDescriptor mFd;
143         // True if this was resumed from a previously turned off keepalive, otherwise false.
144         // This is necessary to send the correct callbacks.
145         public final boolean mResumed;
146 
147         public static final int TYPE_NATT = 1;
148         public static final int TYPE_TCP = 2;
149 
150         // Keepalive slot. A small integer that identifies this keepalive among the ones handled
151         // by this network. This is initialized to NO_KEEPALIVE for new keepalives, but to the
152         // old slot for resumed keepalives.
153         private int mSlot;
154 
155         // Packet data.
156         private final KeepalivePacketData mPacket;
157         private final int mInterval;
158 
159         // Whether the keepalive is started or not. The initial state is NOT_STARTED.
160         private static final int NOT_STARTED = 1;
161         private static final int STARTING = 2;
162         private static final int STARTED = 3;
163         private static final int STOPPING = 4;
164         private int mStartedState = NOT_STARTED;
165         private int mStopReason = ERROR_STOP_REASON_UNINITIALIZED;
166 
KeepaliveInfo(@onNull ISocketKeepaliveCallback callback, @NonNull NetworkAgentInfo nai, @NonNull KeepalivePacketData packet, int interval, int type, @Nullable FileDescriptor fd)167         KeepaliveInfo(@NonNull ISocketKeepaliveCallback callback,
168                 @NonNull NetworkAgentInfo nai,
169                 @NonNull KeepalivePacketData packet,
170                 int interval,
171                 int type,
172                 @Nullable FileDescriptor fd) throws InvalidSocketException {
173             this(callback, nai, packet, Binder.getCallingPid(), Binder.getCallingUid(), interval,
174                     type, fd, NO_KEEPALIVE /* slot */, false /* resumed */);
175         }
176 
KeepaliveInfo(@onNull ISocketKeepaliveCallback callback, @NonNull NetworkAgentInfo nai, @NonNull KeepalivePacketData packet, int pid, int uid, int interval, int type, @Nullable FileDescriptor fd, int slot, boolean resumed)177         KeepaliveInfo(@NonNull ISocketKeepaliveCallback callback,
178                 @NonNull NetworkAgentInfo nai,
179                 @NonNull KeepalivePacketData packet,
180                 int pid,
181                 int uid,
182                 int interval,
183                 int type,
184                 @Nullable FileDescriptor fd,
185                 int slot,
186                 boolean resumed) throws InvalidSocketException {
187             mCallback = callback;
188             mPid = pid;
189             mUid = uid;
190             mPrivileged = (PERMISSION_GRANTED == mContext.checkPermission(PERMISSION, mPid, mUid));
191 
192             mNai = nai;
193             mPacket = packet;
194             mInterval = interval;
195             mType = type;
196             mSlot = slot;
197             mResumed = resumed;
198 
199             // For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the
200             // keepalives are sent cannot be reused by another app even if the fd gets closed by
201             // the user. A null is acceptable here for backward compatibility of PacketKeepalive
202             // API.
203             try {
204                 if (fd != null) {
205                     mFd = Os.dup(fd);
206                 }  else {
207                     Log.d(TAG, toString() + " calls with null fd");
208                     if (!mPrivileged) {
209                         throw new SecurityException(
210                                 "null fd is not allowed for unprivileged access.");
211                     }
212                     if (mType == TYPE_TCP) {
213                         throw new IllegalArgumentException(
214                                 "null fd is not allowed for tcp socket keepalives.");
215                     }
216                     mFd = null;
217                 }
218             } catch (ErrnoException e) {
219                 Log.e(TAG, "Cannot dup fd: ", e);
220                 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
221             }
222 
223             try {
224                 mCallback.asBinder().linkToDeath(this, 0);
225             } catch (RemoteException e) {
226                 binderDied();
227             }
228         }
229 
getNai()230         public NetworkAgentInfo getNai() {
231             return mNai;
232         }
233 
startedStateString(final int state)234         private String startedStateString(final int state) {
235             switch (state) {
236                 case NOT_STARTED : return "NOT_STARTED";
237                 case STARTING : return "STARTING";
238                 case STARTED : return "STARTED";
239                 case STOPPING : return "STOPPING";
240             }
241             throw new IllegalArgumentException("Unknown state");
242         }
243 
toString()244         public String toString() {
245             return "KeepaliveInfo ["
246                     + " type=" + mType
247                     + " network=" + mNai.network
248                     + " startedState=" + startedStateString(mStartedState)
249                     + " "
250                     + IpUtils.addressAndPortToString(mPacket.getSrcAddress(), mPacket.getSrcPort())
251                     + "->"
252                     + IpUtils.addressAndPortToString(mPacket.getDstAddress(), mPacket.getDstPort())
253                     + " interval=" + mInterval
254                     + " uid=" + mUid + " pid=" + mPid + " privileged=" + mPrivileged
255                     + " packetData=" + HexDump.toHexString(mPacket.getPacket())
256                     + " ]";
257         }
258 
259         /** Called when the application process is killed. */
binderDied()260         public void binderDied() {
261             // TODO b/267106526 : this is not called on the handler thread but stop() happily
262             // assumes it is, which means this is a pretty dangerous race condition.
263             stop(BINDER_DIED);
264         }
265 
unlinkDeathRecipient()266         void unlinkDeathRecipient() {
267             if (mCallback != null) {
268                 mCallback.asBinder().unlinkToDeath(this, 0);
269             }
270         }
271 
getSlot()272         public int getSlot() {
273             return mSlot;
274         }
275 
getKeepaliveIntervalSec()276         int getKeepaliveIntervalSec() {
277             return mInterval;
278         }
279 
getUid()280         public int getUid() {
281             return mUid;
282         }
283 
checkNetworkConnected()284         private int checkNetworkConnected() {
285             if (!mNai.networkInfo.isConnectedOrConnecting()) {
286                 return ERROR_INVALID_NETWORK;
287             }
288             return SUCCESS;
289         }
290 
checkSourceAddress()291         private int checkSourceAddress() {
292             // Check that we have the source address.
293             for (InetAddress address : mNai.linkProperties.getAddresses()) {
294                 if (address.equals(mPacket.getSrcAddress())) {
295                     return SUCCESS;
296                 }
297             }
298             return ERROR_INVALID_IP_ADDRESS;
299         }
300 
checkInterval()301         private int checkInterval() {
302             if (mInterval < MIN_INTERVAL_SEC || mInterval > MAX_INTERVAL_SEC) {
303                 return ERROR_INVALID_INTERVAL;
304             }
305             return SUCCESS;
306         }
307 
checkPermission()308         private int checkPermission() {
309             final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
310             if (networkKeepalives == null) {
311                 return ERROR_INVALID_NETWORK;
312             }
313 
314             if (mPrivileged) return SUCCESS;
315 
316             final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
317                     mSupportedKeepalives, mNai.networkCapabilities);
318 
319             int takenUnprivilegedSlots = 0;
320             for (final KeepaliveInfo ki : networkKeepalives.values()) {
321                 if (!ki.mPrivileged) ++takenUnprivilegedSlots;
322             }
323             if (takenUnprivilegedSlots > supported - mReservedPrivilegedSlots) {
324                 return ERROR_INSUFFICIENT_RESOURCES;
325             }
326 
327             // Count unprivileged keepalives for the same uid across networks.
328             int unprivilegedCountSameUid = 0;
329             for (final HashMap<Integer, KeepaliveInfo> kaForNetwork : mKeepalives.values()) {
330                 for (final KeepaliveInfo ki : kaForNetwork.values()) {
331                     if (ki.mUid == mUid) {
332                         unprivilegedCountSameUid++;
333                     }
334                 }
335             }
336             if (unprivilegedCountSameUid > mAllowedUnprivilegedSlotsForUid) {
337                 return ERROR_INSUFFICIENT_RESOURCES;
338             }
339             return SUCCESS;
340         }
341 
checkLimit()342         private int checkLimit() {
343             final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
344             if (networkKeepalives == null) {
345                 return ERROR_INVALID_NETWORK;
346             }
347             final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
348                     mSupportedKeepalives, mNai.networkCapabilities);
349             if (supported == 0) return ERROR_UNSUPPORTED;
350             if (networkKeepalives.size() > supported) return ERROR_INSUFFICIENT_RESOURCES;
351             return SUCCESS;
352         }
353 
354         /**
355          * Checks if the keepalive info is valid to start.
356          *
357          * @return SUCCESS if the keepalive is valid and the error reason otherwise.
358          */
isValid()359         public int isValid() {
360             synchronized (mNai) {
361                 int error = checkInterval();
362                 if (error == SUCCESS) error = checkLimit();
363                 if (error == SUCCESS) error = checkPermission();
364                 if (error == SUCCESS) error = checkNetworkConnected();
365                 if (error == SUCCESS) error = checkSourceAddress();
366                 return error;
367             }
368         }
369 
370         /**
371          * Attempt to start the keepalive on the given slot.
372          *
373          * @param slot the slot to start the keepalive on.
374          * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
375          */
start(int slot)376         int start(int slot) {
377             // BINDER_DIED can happen if the binder died before the KeepaliveInfo was created and
378             // the constructor set the state to BINDER_DIED. If that's the case, the KI is already
379             // cleaned up.
380             if (BINDER_DIED == mStartedState) return BINDER_DIED;
381             mSlot = slot;
382             int error = isValid();
383             if (error == SUCCESS) {
384                 Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.toShortString());
385                 switch (mType) {
386                     case TYPE_NATT:
387                         final NattKeepalivePacketData nattData = (NattKeepalivePacketData) mPacket;
388                         mNai.onAddNattKeepalivePacketFilter(slot, nattData);
389                         mNai.onStartNattSocketKeepalive(slot, mInterval, nattData);
390                         break;
391                     case TYPE_TCP:
392                         try {
393                             mTcpController.startSocketMonitor(mFd, mCallback, mSlot);
394                         } catch (InvalidSocketException e) {
395                             handleStopKeepalive(mNai, mSlot, ERROR_INVALID_SOCKET);
396                             return ERROR_INVALID_SOCKET;
397                         }
398                         final TcpKeepalivePacketData tcpData = (TcpKeepalivePacketData) mPacket;
399                         mNai.onAddTcpKeepalivePacketFilter(slot, tcpData);
400                         // TODO: check result from apf and notify of failure as needed.
401                         mNai.onStartTcpSocketKeepalive(slot, mInterval, tcpData);
402                         break;
403                     default:
404                         Log.wtf(TAG, "Starting keepalive with unknown type: " + mType);
405                         handleStopKeepalive(mNai, mSlot, ERROR_UNSUPPORTED);
406                         return ERROR_UNSUPPORTED;
407                 }
408                 mStartedState = STARTING;
409                 return SUCCESS;
410             } else {
411                 handleStopKeepalive(mNai, mSlot, error);
412                 return error;
413             }
414         }
415 
stop(int reason)416         void stop(int reason) {
417             int uid = Binder.getCallingUid();
418             if (uid != mUid && uid != Process.SYSTEM_UID) {
419                 if (DBG) {
420                     Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
421                 }
422             }
423             // To prevent races from re-entrance of stop(), return if the state is already stopping.
424             // This might happen if multiple event sources stop keepalive in a short time. Such as
425             // network disconnect after user calls stop(), or tear down socket after binder died.
426             // Note that it's always possible this method is called by the auto keepalive timer
427             // or any other way after the binder died, hence the check for BINDER_DIED. If the
428             // binder has died, then the KI has already been cleaned up.
429             if (mStartedState == STOPPING || mStartedState == BINDER_DIED) return;
430 
431             // Store the reason of stopping, and report it after the keepalive is fully stopped.
432             if (mStopReason != ERROR_STOP_REASON_UNINITIALIZED) {
433                 throw new IllegalStateException("Unexpected stop reason: " + mStopReason);
434             }
435             mStopReason = reason;
436             Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.toShortString()
437                     + ": " + reason);
438             switch (mStartedState) {
439                 case NOT_STARTED:
440                     // Remove the reference to this keepalive that had an error before starting,
441                     // e.g. invalid parameter.
442                     cleanupStoppedKeepalive(mNai, mSlot);
443                     if (BINDER_DIED == reason) mStartedState = BINDER_DIED;
444                     break;
445                 default:
446                     mStartedState = STOPPING;
447                     switch (mType) {
448                         case TYPE_TCP:
449                             mTcpController.stopSocketMonitor(mSlot);
450                             // fall through
451                         case TYPE_NATT:
452                             mNai.onStopSocketKeepalive(mSlot);
453                             mNai.onRemoveKeepalivePacketFilter(mSlot);
454                             break;
455                         default:
456                             Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
457                     }
458             }
459 
460             // Close the duplicated fd that maintains the lifecycle of socket whenever
461             // keepalive is running.
462             if (mFd != null) {
463                 try {
464                     Os.close(mFd);
465                 } catch (ErrnoException e) {
466                     // This should not happen since system server controls the lifecycle of fd when
467                     // keepalive offload is running.
468                     Log.wtf(TAG, "Error closing fd for keepalive " + mSlot + ": " + e);
469                 }
470             }
471         }
472 
473         /**
474          * Construct a new KeepaliveInfo from existing KeepaliveInfo with a new fd.
475          */
withFd(@onNull FileDescriptor fd)476         public KeepaliveInfo withFd(@NonNull FileDescriptor fd) throws InvalidSocketException {
477             return new KeepaliveInfo(mCallback, mNai, mPacket, mPid, mUid, mInterval, mType,
478                     fd, mSlot, true /* resumed */);
479         }
480     }
481 
notifyErrorCallback(ISocketKeepaliveCallback cb, int error)482     void notifyErrorCallback(ISocketKeepaliveCallback cb, int error) {
483         if (DBG) Log.w(TAG, "Sending onError(" + error + ") callback");
484         try {
485             cb.onError(error);
486         } catch (RemoteException e) {
487             Log.w(TAG, "Discarded onError(" + error + ") callback");
488         }
489     }
490 
findFirstFreeSlot(NetworkAgentInfo nai)491     private  int findFirstFreeSlot(NetworkAgentInfo nai) {
492         HashMap networkKeepalives = mKeepalives.get(nai);
493         if (networkKeepalives == null) {
494             networkKeepalives = new HashMap<Integer, KeepaliveInfo>();
495             mKeepalives.put(nai, networkKeepalives);
496         }
497 
498         // Find the lowest-numbered free slot. Slot numbers start from 1, because that's what two
499         // separate chipset implementations independently came up with.
500         int slot;
501         for (slot = 1; slot <= networkKeepalives.size(); slot++) {
502             if (networkKeepalives.get(slot) == null) {
503                 return slot;
504             }
505         }
506         return slot;
507     }
508 
509     /**
510      * Handle start keepalives with the message.
511      *
512      * @param ki the keepalive to start.
513      * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
514      */
handleStartKeepalive(KeepaliveInfo ki)515     public int handleStartKeepalive(KeepaliveInfo ki) {
516         NetworkAgentInfo nai = ki.getNai();
517         // If this was a paused keepalive, then reuse the same slot that was kept for it. Otherwise,
518         // use the first free slot for this network agent.
519         final int slot = NO_KEEPALIVE != ki.mSlot ? ki.mSlot : findFirstFreeSlot(nai);
520         mKeepalives.get(nai).put(slot, ki);
521         return ki.start(slot);
522     }
523 
handleStopAllKeepalives(NetworkAgentInfo nai, int reason)524     public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
525         final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
526         if (networkKeepalives != null) {
527             final ArrayList<KeepaliveInfo> kalist = new ArrayList(networkKeepalives.values());
528             for (KeepaliveInfo ki : kalist) {
529                 // Check if keepalive is already stopped
530                 if (ki.mStopReason == SUCCESS_PAUSED) continue;
531                 ki.stop(reason);
532                 // Clean up keepalives since the network agent is disconnected and unable to pass
533                 // back asynchronous result of stop().
534                 cleanupStoppedKeepalive(nai, ki.mSlot);
535             }
536         }
537     }
538 
handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason)539     public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
540         final String networkName = NetworkAgentInfo.toShortString(nai);
541         HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
542         if (networkKeepalives == null) {
543             Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName);
544             return;
545         }
546         KeepaliveInfo ki = networkKeepalives.get(slot);
547         if (ki == null) {
548             Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName);
549             return;
550         }
551         ki.stop(reason);
552         // Clean up keepalives will be done as a result of calling ki.stop() after the slots are
553         // freed.
554     }
555 
cleanupStoppedKeepalive(NetworkAgentInfo nai, int slot)556     private void cleanupStoppedKeepalive(NetworkAgentInfo nai, int slot) {
557         final String networkName = NetworkAgentInfo.toShortString(nai);
558         HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
559         if (networkKeepalives == null) {
560             Log.e(TAG, "Attempt to remove keepalive on nonexistent network " + networkName);
561             return;
562         }
563         KeepaliveInfo ki = networkKeepalives.get(slot);
564         if (ki == null) {
565             Log.e(TAG, "Attempt to remove nonexistent keepalive " + slot + " on " + networkName);
566             return;
567         }
568 
569         // If the keepalive was stopped for good, remove it from the hash table so the slot can
570         // be considered available when reusing it. If it was only a pause, let it sit in the map
571         // so it sits on the slot.
572         final int reason = ki.mStopReason;
573         if (reason != SUCCESS_PAUSED) {
574             networkKeepalives.remove(slot);
575             Log.d(TAG, "Remove keepalive " + slot + " on " + networkName + ", "
576                     + networkKeepalives.size() + " remains.");
577         } else {
578             Log.d(TAG, "Pause keepalive " + slot + " on " + networkName + ", keep slot reserved");
579         }
580         if (networkKeepalives.isEmpty()) {
581             mKeepalives.remove(nai);
582         }
583 
584         // Notify app that the keepalive is stopped.
585         if (reason == SUCCESS) {
586             try {
587                 ki.mCallback.onStopped();
588             } catch (RemoteException e) {
589                 Log.w(TAG, "Discarded onStop callback: " + reason);
590             }
591         } else if (reason == SUCCESS_PAUSED) {
592             try {
593                 ki.mCallback.onPaused();
594             } catch (RemoteException e) {
595                 Log.w(TAG, "Discarded onPaused callback: " + reason);
596             }
597         } else if (reason == DATA_RECEIVED) {
598             try {
599                 ki.mCallback.onDataReceived();
600             } catch (RemoteException e) {
601                 Log.w(TAG, "Discarded onDataReceived callback: " + reason);
602             }
603         } else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
604             throw new IllegalStateException("Unexpected stop reason: " + reason);
605         } else if (reason == ERROR_NO_SUCH_SLOT) {
606             // There are multiple independent reasons a keepalive can stop. Some
607             // are software (e.g. the app stops the keepalive) and some are hardware
608             // (e.g. the SIM card gets removed). Therefore, there is a very low
609             // probability that both of these happen at the same time, which would
610             // result in the first stop attempt returning SUCCESS and the second
611             // stop attempt returning NO_SUCH_SLOT. Such a race condition can be
612             // ignored with a log.
613             // This should still be reported because if it happens with any frequency
614             // it probably means there is a bug where the system server is trying
615             // to use a non-existing hardware slot.
616             // TODO : separate the non-existing hardware slot from the case where
617             // there is no keepalive running on this slot.
618             Log.wtf(TAG, "Keepalive on slot " + slot + " can't be stopped : " + reason);
619             notifyErrorCallback(ki.mCallback, reason);
620         } else {
621             notifyErrorCallback(ki.mCallback, reason);
622         }
623 
624         ki.unlinkDeathRecipient();
625     }
626 
627     /**
628      * Finalize a paused keepalive.
629      *
630      * This will send the appropriate callback after checking that this keepalive is indeed paused,
631      * and free the slot.
632      *
633      * @param ki the keepalive to finalize
634      * @param reason the reason the keepalive is stopped
635      */
finalizePausedKeepalive(@onNull final KeepaliveInfo ki, int reason)636     public void finalizePausedKeepalive(@NonNull final KeepaliveInfo ki, int reason) {
637         if (SUCCESS_PAUSED != ki.mStopReason) {
638             throw new IllegalStateException("Keepalive is not paused");
639         }
640         if (reason == SUCCESS) {
641             try {
642                 ki.mCallback.onStopped();
643             } catch (RemoteException e) {
644                 Log.w(TAG, "Discarded onStopped callback while finalizing paused keepalive");
645             }
646         } else {
647             notifyErrorCallback(ki.mCallback, reason);
648         }
649 
650         final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(ki.mNai);
651         if (networkKeepalives == null) {
652             Log.e(TAG, "Attempt to finalize keepalive on nonexistent network " + ki.mNai);
653             return;
654         }
655         networkKeepalives.remove(ki.mSlot);
656     }
657 
658     /**
659      * Handle keepalive events from lower layer.
660      *
661      * @return false if the event caused handleStopKeepalive to be called, i.e. the keepalive is
662      *     forced to stop. Otherwise, return true.
663      */
handleEventSocketKeepalive(@onNull NetworkAgentInfo nai, int slot, int reason)664     public boolean handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
665         KeepaliveInfo ki = null;
666         try {
667             ki = mKeepalives.get(nai).get(slot);
668         } catch(NullPointerException e) {}
669         if (ki == null) {
670             Log.e(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason
671                     + " for unknown keepalive " + slot + " on " + nai.toShortString());
672             return true;
673         }
674 
675         // This can be called in a number of situations :
676         // - startedState is STARTING.
677         //   - reason is SUCCESS => go to STARTED.
678         //   - reason isn't SUCCESS => it's an error starting. Go to NOT_STARTED and stop keepalive.
679         // - startedState is STARTED.
680         //   - reason is SUCCESS => it's a success stopping. Go to NOT_STARTED and stop keepalive.
681         //   - reason isn't SUCCESS => it's an error in exec. Go to NOT_STARTED and stop keepalive.
682         // The control is not supposed to ever come here if the state is NOT_STARTED. This is
683         // because in NOT_STARTED state, the code will switch to STARTING before sending messages
684         // to start, and the only way to NOT_STARTED is this function, through the edges outlined
685         // above : in all cases, keepalive gets stopped and can't restart without going into
686         // STARTING as messages are ordered. This also depends on the hardware processing the
687         // messages in order.
688         // TODO : clarify this code and get rid of mStartedState. Using a StateMachine is an
689         // option.
690         if (KeepaliveInfo.STARTING == ki.mStartedState) {
691             if (SUCCESS == reason) {
692                 // Keepalive successfully started.
693                 Log.d(TAG, "Started keepalive " + slot + " on " + nai.toShortString());
694                 ki.mStartedState = KeepaliveInfo.STARTED;
695                 try {
696                     if (ki.mResumed) {
697                         ki.mCallback.onResumed();
698                     } else {
699                         ki.mCallback.onStarted();
700                     }
701                 } catch (RemoteException e) {
702                     Log.w(TAG, "Discarded " + (ki.mResumed ? "onResumed" : "onStarted")
703                             + " callback for slot " + slot);
704                 }
705                 return true;
706             } else {
707                 Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.toShortString()
708                         + ": " + reason);
709                 // The message indicated some error trying to start: do call handleStopKeepalive.
710                 handleStopKeepalive(nai, slot, reason);
711                 return false;
712             }
713         } else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
714             // The message indicated result of stopping : clean up keepalive slots.
715             Log.d(TAG, "Stopped keepalive " + slot + " on " + nai.toShortString()
716                     + " stopped: " + reason);
717             ki.mStartedState = KeepaliveInfo.NOT_STARTED;
718             cleanupStoppedKeepalive(nai, slot);
719             return true;
720         } else {
721             Log.wtf(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason
722                     + " for keepalive in wrong state: " + ki.toString());
723             // Although this is an unexpected event, the keepalive is not stopped here.
724             return true;
725         }
726     }
727 
728     /**
729      * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
730      * {@link android.net.SocketKeepalive}.
731      **/
732     @Nullable
makeNattKeepaliveInfo(@ullable NetworkAgentInfo nai, @Nullable FileDescriptor fd, int intervalSeconds, @NonNull ISocketKeepaliveCallback cb, @NonNull String srcAddrString, int srcPort, @NonNull String dstAddrString, int dstPort)733     public KeepaliveInfo makeNattKeepaliveInfo(@Nullable NetworkAgentInfo nai,
734             @Nullable FileDescriptor fd,
735             int intervalSeconds,
736             @NonNull ISocketKeepaliveCallback cb,
737             @NonNull String srcAddrString,
738             int srcPort,
739             @NonNull String dstAddrString,
740             int dstPort) {
741         if (nai == null) {
742             notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
743             return null;
744         }
745 
746         InetAddress srcAddress, dstAddress;
747         try {
748             srcAddress = InetAddresses.parseNumericAddress(srcAddrString);
749             dstAddress = InetAddresses.parseNumericAddress(dstAddrString);
750         } catch (IllegalArgumentException e) {
751             notifyErrorCallback(cb, ERROR_INVALID_IP_ADDRESS);
752             return null;
753         }
754 
755         KeepalivePacketData packet;
756         try {
757             packet = NattKeepalivePacketData.nattKeepalivePacket(
758                     srcAddress, srcPort, dstAddress, NATT_PORT);
759         } catch (InvalidPacketException e) {
760             notifyErrorCallback(cb, e.getError());
761             return null;
762         }
763         KeepaliveInfo ki = null;
764         try {
765             ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
766                     KeepaliveInfo.TYPE_NATT, fd);
767         } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
768             Log.e(TAG, "Fail to construct keepalive", e);
769             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
770             return null;
771         }
772         Log.d(TAG, "Created keepalive: " + ki);
773         return ki;
774     }
775 
776     /**
777      * Make a KeepaliveInfo for a TCP socket.
778      *
779      * In order to offload keepalive for application correctly, sequence number, ack number and
780      * other fields are needed to form the keepalive packet. Thus, this function synchronously
781      * puts the socket into repair mode to get the necessary information. After the socket has been
782      * put into repair mode, the application cannot access the socket until reverted to normal.
783      *
784      * See {@link android.net.SocketKeepalive}.
785      **/
786     @Nullable
makeTcpKeepaliveInfo(@ullable NetworkAgentInfo nai, @NonNull FileDescriptor fd, int intervalSeconds, @NonNull ISocketKeepaliveCallback cb)787     public KeepaliveInfo makeTcpKeepaliveInfo(@Nullable NetworkAgentInfo nai,
788             @NonNull FileDescriptor fd,
789             int intervalSeconds,
790             @NonNull ISocketKeepaliveCallback cb) {
791         if (nai == null) {
792             notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
793             return null;
794         }
795 
796         final TcpKeepalivePacketData packet;
797         try {
798             packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
799         } catch (InvalidSocketException e) {
800             notifyErrorCallback(cb, e.error);
801             return null;
802         } catch (InvalidPacketException e) {
803             notifyErrorCallback(cb, e.getError());
804             return null;
805         }
806         KeepaliveInfo ki = null;
807         try {
808             ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
809                     KeepaliveInfo.TYPE_TCP, fd);
810         } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
811             Log.e(TAG, "Fail to construct keepalive e=" + e);
812             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
813             return null;
814         }
815         Log.d(TAG, "Created keepalive: " + ki.toString());
816         return ki;
817     }
818 
819     /**
820      * Make a KeepaliveInfo for an IPSec NAT-T socket.
821      *
822      * This function is identical to {@link #makeNattKeepaliveInfo}, but also takes a
823      * {@code resourceId}, which is the resource index bound to the {@link UdpEncapsulationSocket}
824      * when creating by {@link com.android.server.IpSecService} to verify whether the given
825      * {@link UdpEncapsulationSocket} is legitimate.
826      **/
827     @Nullable
makeNattKeepaliveInfo(@ullable NetworkAgentInfo nai, @Nullable FileDescriptor fd, int resourceId, int intervalSeconds, @NonNull ISocketKeepaliveCallback cb, @NonNull String srcAddrString, @NonNull String dstAddrString, int dstPort)828     public KeepaliveInfo makeNattKeepaliveInfo(@Nullable NetworkAgentInfo nai,
829             @Nullable FileDescriptor fd,
830             int resourceId,
831             int intervalSeconds,
832             @NonNull ISocketKeepaliveCallback cb,
833             @NonNull String srcAddrString,
834             @NonNull String dstAddrString,
835             int dstPort) {
836         // Ensure that the socket is created by IpSecService.
837         if (!isNattKeepaliveSocketValid(fd, resourceId)) {
838             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
839             return null;
840         }
841 
842         // Get src port to adopt old API.
843         int srcPort = 0;
844         try {
845             final SocketAddress srcSockAddr = Os.getsockname(fd);
846             srcPort = ((InetSocketAddress) srcSockAddr).getPort();
847         } catch (ErrnoException e) {
848             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
849             return null;
850         }
851 
852         // Forward request to old API.
853         return makeNattKeepaliveInfo(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
854                 dstAddrString, dstPort);
855     }
856 
857     /**
858      * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
859      **/
isNattKeepaliveSocketValid(@ullable FileDescriptor fd, int resourceId)860     public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
861         // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
862         //       2. If the fd is created from the system api, check that it's bounded. And
863         //          call dup to keep the fd open.
864         //       3. If the fd is created from IpSecService, check if the resource ID is valid. And
865         //          hold the resource needed in IpSecService.
866         if (null == fd) {
867             return false;
868         }
869         return true;
870     }
871 
872     /**
873      * Dump KeepaliveTracker state.
874      */
dump(IndentingPrintWriter pw)875     public void dump(IndentingPrintWriter pw) {
876         pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
877         pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
878         pw.println("Allowed Unprivileged keepalives per uid: " + mAllowedUnprivilegedSlotsForUid);
879         pw.println("Socket keepalives:");
880         pw.increaseIndent();
881         for (NetworkAgentInfo nai : mKeepalives.keySet()) {
882             pw.println(nai.toShortString());
883             pw.increaseIndent();
884             for (int slot : mKeepalives.get(nai).keySet()) {
885                 KeepaliveInfo ki = mKeepalives.get(nai).get(slot);
886                 pw.println(slot + ": " + ki.toString());
887             }
888             pw.decreaseIndent();
889         }
890         pw.decreaseIndent();
891     }
892 }
893