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.internal.net.ipsec.ike;
17 
18 import static android.net.ipsec.ike.IkeManager.getIkeLog;
19 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
20 
21 import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD;
22 import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD;
23 import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD;
24 import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE;
25 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE;
26 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE;
27 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_DPD;
28 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_INFO;
29 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_MOBIKE;
30 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE;
31 
32 import android.annotation.IntDef;
33 import android.content.Context;
34 import android.net.ipsec.ike.ChildSessionCallback;
35 import android.net.ipsec.ike.ChildSessionParams;
36 import android.os.PowerManager;
37 import android.os.PowerManager.WakeLock;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.Comparator;
44 import java.util.PriorityQueue;
45 
46 /**
47  * IkeLocalRequestScheduler caches all local requests scheduled by an IKE Session and notify the IKE
48  * Session to process the request when it is allowed.
49  *
50  * <p>LocalRequestScheduler is running on the IkeSessionStateMachine thread.
51  */
52 public final class IkeLocalRequestScheduler {
53     private static final String TAG = "IkeLocalRequestScheduler";
54 
55     @VisibleForTesting static final String LOCAL_REQUEST_WAKE_LOCK_TAG = "LocalRequestWakeLock";
56 
57     private static final int DEFAULT_REQUEST_QUEUE_SIZE = 1;
58 
59     private static final int REQUEST_ID_NOT_ASSIGNED = -1;
60 
61     // Local request that must be handled immediately. Ex: CMD_LOCAL_REQUEST_DELETE_IKE
62     @VisibleForTesting static final int REQUEST_PRIORITY_URGENT = 0;
63 
64     // Local request that must be handled soon, but not necessarily immediately.
65     // Ex: CMD_LOCAL_REQUEST_MOBIKE
66     @VisibleForTesting static final int REQUEST_PRIORITY_HIGH = 1;
67 
68     // Local request that should be handled once nothing more urgent requires handling. Most
69     // LocalRequests will have this priority.
70     @VisibleForTesting static final int REQUEST_PRIORITY_NORMAL = 2;
71 
72     // Local request that has an unknown priority. This shouldn't happen in normal processing.
73     @VisibleForTesting static final int REQUEST_PRIORITY_UNKNOWN = Integer.MAX_VALUE;
74 
75     @Retention(RetentionPolicy.SOURCE)
76     @IntDef({
77         REQUEST_PRIORITY_URGENT,
78         REQUEST_PRIORITY_HIGH,
79         REQUEST_PRIORITY_NORMAL,
80         REQUEST_PRIORITY_UNKNOWN
81     })
82     @interface RequestPriority {}
83 
84     public static int SPI_NOT_INCLUDED = 0;
85 
86     private final PowerManager mPowerManager;
87 
88     private final PriorityQueue<LocalRequest> mRequestQueue =
89             new PriorityQueue<>(DEFAULT_REQUEST_QUEUE_SIZE, new LocalRequestComparator());
90 
91     private final IProcedureConsumer mConsumer;
92 
93     private int mNextRequestId;
94 
95     /**
96      * Construct an instance of IkeLocalRequestScheduler
97      *
98      * @param consumer the interface to initiate new procedure.
99      */
IkeLocalRequestScheduler(IProcedureConsumer consumer, Context context)100     public IkeLocalRequestScheduler(IProcedureConsumer consumer, Context context) {
101         mConsumer = consumer;
102         mPowerManager = context.getSystemService(PowerManager.class);
103 
104         mNextRequestId = 0;
105     }
106 
107     /** Add a new local request to the queue. */
addRequest(LocalRequest request)108     public void addRequest(LocalRequest request) {
109         request.acquireWakeLock(mPowerManager);
110         request.setRequestId(mNextRequestId++);
111         mRequestQueue.offer(request);
112     }
113 
114     /**
115      * Notifies the scheduler that the caller is ready for a new procedure
116      *
117      * <p>Synchronously triggers the call to onNewProcedureReady.
118      *
119      * @return whether or not a new procedure was scheduled.
120      */
readyForNextProcedure()121     public boolean readyForNextProcedure() {
122         if (!mRequestQueue.isEmpty()) {
123             mConsumer.onNewProcedureReady(mRequestQueue.poll());
124             return true;
125         }
126         return false;
127     }
128 
129     /** Release WakeLocks of all LocalRequests in the queue */
releaseAllLocalRequestWakeLocks()130     public void releaseAllLocalRequestWakeLocks() {
131         for (LocalRequest req : mRequestQueue) {
132             req.releaseWakeLock();
133         }
134         mRequestQueue.clear();
135     }
136 
137     /**
138      * This class represents the common information of procedures that will be locally initiated.
139      */
140     public abstract static class LocalRequest {
141         public final int procedureType;
142 
143         // Priority of this LocalRequest. Note that a lower 'priority' means higher urgency.
144         @RequestPriority private final int mPriority;
145 
146         // ID used to preserve insertion-order between requests in IkeLocalRequestScheduler with the
147         // same priority. Set when the LocalRequest is added to the IkeLocalRequestScheduler.
148         private int mRequestId = REQUEST_ID_NOT_ASSIGNED;
149         private WakeLock mWakeLock;
150 
LocalRequest(int type, int priority)151         LocalRequest(int type, int priority) {
152             validateTypeOrThrow(type);
153             procedureType = type;
154             mPriority = priority;
155         }
156 
157         @VisibleForTesting
getPriority()158         int getPriority() {
159             return mPriority;
160         }
161 
setRequestId(int requestId)162         private void setRequestId(int requestId) {
163             mRequestId = requestId;
164         }
165 
166         @VisibleForTesting
getRequestId()167         int getRequestId() {
168             return mRequestId;
169         }
170 
171         /**
172          * Acquire a WakeLock for the LocalRequest.
173          *
174          * <p>This method will only be called from IkeLocalRequestScheduler#addRequest or
175          * IkeLocalRequestScheduler#addRequestAtFront
176          */
acquireWakeLock(PowerManager powerManager)177         private void acquireWakeLock(PowerManager powerManager) {
178             if (mWakeLock != null && mWakeLock.isHeld()) {
179                 getIkeLog().wtf(TAG, "This LocalRequest already acquired a WakeLock");
180                 return;
181             }
182 
183             mWakeLock =
184                     powerManager.newWakeLock(
185                             PARTIAL_WAKE_LOCK,
186                             TAG + LOCAL_REQUEST_WAKE_LOCK_TAG + "_" + procedureType);
187             mWakeLock.setReferenceCounted(false);
188             mWakeLock.acquire();
189         }
190 
191         /** Release WakeLock of the LocalRequest */
releaseWakeLock()192         public void releaseWakeLock() {
193             if (mWakeLock != null) {
194                 mWakeLock.release();
195                 mWakeLock = null;
196             }
197         }
198 
validateTypeOrThrow(int type)199         protected abstract void validateTypeOrThrow(int type);
200 
isChildRequest()201         protected abstract boolean isChildRequest();
202     }
203 
204     /** LocalRequestComparator is a comparator for comparing LocalRequest instances. */
205     private class LocalRequestComparator implements Comparator<LocalRequest> {
206         @Override
compare(LocalRequest requestA, LocalRequest requestB)207         public int compare(LocalRequest requestA, LocalRequest requestB) {
208             int relativePriorities =
209                     Integer.compare(requestA.getPriority(), requestB.getPriority());
210             if (relativePriorities != 0) return relativePriorities;
211 
212             return Integer.compare(requestA.getRequestId(), requestB.getRequestId());
213         }
214     }
215 
216     /**
217      * This class represents a user requested or internally scheduled IKE procedure that will be
218      * initiated locally.
219      */
220     public static class IkeLocalRequest extends LocalRequest {
221         public long remoteSpi;
222 
223         /** Schedule a request for an IKE SA that is identified by the remoteIkeSpi */
IkeLocalRequest(int type, long remoteIkeSpi, int priority)224         private IkeLocalRequest(int type, long remoteIkeSpi, int priority) {
225             super(type, priority);
226             remoteSpi = remoteIkeSpi;
227         }
228 
229         @Override
validateTypeOrThrow(int type)230         protected void validateTypeOrThrow(int type) {
231             if (type >= CMD_LOCAL_REQUEST_CREATE_IKE && type <= CMD_LOCAL_REQUEST_MOBIKE) return;
232             throw new IllegalArgumentException("Invalid IKE procedure type: " + type);
233         }
234 
235         @Override
isChildRequest()236         protected boolean isChildRequest() {
237             return false;
238         }
239     }
240 
241     /**
242      * This class represents a user requested or internally scheduled Child procedure that will be
243      * initiated locally.
244      */
245     public static class ChildLocalRequest extends LocalRequest {
246         public int remoteSpi;
247         public final ChildSessionCallback childSessionCallback;
248         public final ChildSessionParams childSessionParams;
249 
ChildLocalRequest( int type, int remoteChildSpi, ChildSessionCallback childCallback, ChildSessionParams childParams, int priority)250         private ChildLocalRequest(
251                 int type,
252                 int remoteChildSpi,
253                 ChildSessionCallback childCallback,
254                 ChildSessionParams childParams,
255                 int priority) {
256             super(type, priority);
257             childSessionParams = childParams;
258             childSessionCallback = childCallback;
259             remoteSpi = remoteChildSpi;
260         }
261 
262         @Override
validateTypeOrThrow(int type)263         protected void validateTypeOrThrow(int type) {
264             if (type >= CMD_LOCAL_REQUEST_CREATE_CHILD
265                     && type <= CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE) {
266                 return;
267             }
268 
269             throw new IllegalArgumentException("Invalid Child procedure type: " + type);
270         }
271 
272         @Override
isChildRequest()273         protected boolean isChildRequest() {
274             return true;
275         }
276     }
277 
278     /** Interface to initiate a new IKE procedure */
279     public interface IProcedureConsumer {
280         /**
281          * Called when a new IKE procedure can be initiated.
282          *
283          * @param localRequest the request to be initiated.
284          */
onNewProcedureReady(LocalRequest localRequest)285         void onNewProcedureReady(LocalRequest localRequest);
286     }
287 
288     /** package-protected */
289     static class LocalRequestFactory {
290         /** Create a request for the IKE Session */
getIkeLocalRequest(int type)291         IkeLocalRequest getIkeLocalRequest(int type) {
292             return getIkeLocalRequest(type, SPI_NOT_INCLUDED);
293         }
294 
295         /** Create a request for an IKE SA that is identified by the remoteIkeSpi */
getIkeLocalRequest(int type, long remoteIkeSpi)296         IkeLocalRequest getIkeLocalRequest(int type, long remoteIkeSpi) {
297             return new IkeLocalRequest(type, remoteIkeSpi, procedureTypeToPriority(type));
298         }
299 
300         /** Create a request for a Child Session that is identified by the childCallback */
getChildLocalRequest( int type, ChildSessionCallback childCallback, ChildSessionParams childParams)301         ChildLocalRequest getChildLocalRequest(
302                 int type, ChildSessionCallback childCallback, ChildSessionParams childParams) {
303             return new ChildLocalRequest(
304                     type,
305                     SPI_NOT_INCLUDED,
306                     childCallback,
307                     childParams,
308                     procedureTypeToPriority(type));
309         }
310 
311         /** Create a request for a Child SA that is identified by the remoteChildSpi */
getChildLocalRequest(int type, int remoteChildSpi)312         ChildLocalRequest getChildLocalRequest(int type, int remoteChildSpi) {
313             return new ChildLocalRequest(
314                     type,
315                     remoteChildSpi,
316                     null /*childCallback*/,
317                     null /*childParams*/,
318                     procedureTypeToPriority(type));
319         }
320 
321         /** Returns the request priority for the specified procedure type. */
322         @VisibleForTesting
323         @RequestPriority
procedureTypeToPriority(int procedureType)324         static int procedureTypeToPriority(int procedureType) {
325             switch (procedureType) {
326                 case CMD_LOCAL_REQUEST_DELETE_IKE:
327                     return REQUEST_PRIORITY_URGENT;
328 
329                 case CMD_LOCAL_REQUEST_MOBIKE:
330                 case CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE:
331                     return REQUEST_PRIORITY_HIGH;
332 
333                 case CMD_LOCAL_REQUEST_CREATE_IKE: // Fallthrough
334                 case CMD_LOCAL_REQUEST_REKEY_IKE: // Fallthrough
335                 case CMD_LOCAL_REQUEST_INFO: // Fallthrough
336                 case CMD_LOCAL_REQUEST_DPD: // Fallthrough
337                 case CMD_LOCAL_REQUEST_CREATE_CHILD: // Fallthrough
338                 case CMD_LOCAL_REQUEST_DELETE_CHILD: // Fallthrough
339                 case CMD_LOCAL_REQUEST_REKEY_CHILD:
340                     return REQUEST_PRIORITY_NORMAL;
341 
342                 default:
343                     // unknown procedure type - assign it the lowest priority
344                     getIkeLog().wtf(TAG, "Unknown procedureType: " + procedureType);
345                     return REQUEST_PRIORITY_UNKNOWN;
346             }
347         }
348     }
349 }
350