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