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