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