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