1 /* 2 * Copyright (C) 2023 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 17 package com.android.server.vcn.routeselection; 18 19 import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; 20 21 import static com.android.server.VcnManagementService.LOCAL_LOG; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.TargetApi; 26 import android.net.IpSecTransform; 27 import android.net.LinkProperties; 28 import android.net.Network; 29 import android.net.NetworkCapabilities; 30 import android.net.vcn.VcnManager; 31 import android.net.vcn.VcnUnderlyingNetworkTemplate; 32 import android.os.Build; 33 import android.os.Handler; 34 import android.os.ParcelUuid; 35 import android.util.IndentingPrintWriter; 36 import android.util.Slog; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.annotations.VisibleForTesting.Visibility; 40 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; 41 import com.android.server.vcn.VcnContext; 42 43 import java.util.ArrayList; 44 import java.util.Comparator; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.TimeUnit; 48 49 /** 50 * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for 51 * route selection. 52 * 53 * @hide 54 */ 55 @TargetApi(Build.VERSION_CODES.BAKLAVA) 56 public class UnderlyingNetworkEvaluator { 57 private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName(); 58 59 private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5}; 60 61 @NonNull private final VcnContext mVcnContext; 62 @NonNull private final Handler mHandler; 63 @NonNull private final Object mCancellationToken = new Object(); 64 65 @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder; 66 67 @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback; 68 @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>(); 69 70 @NonNull private final Dependencies mDependencies; 71 72 // TODO: Support back-off timeouts 73 private long mPenalizedTimeoutMs; 74 75 private boolean mIsSelected; 76 private boolean mIsPenalized; 77 private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID; 78 79 @VisibleForTesting(visibility = Visibility.PRIVATE) UnderlyingNetworkEvaluator( @onNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback, @NonNull Dependencies dependencies)80 public UnderlyingNetworkEvaluator( 81 @NonNull VcnContext vcnContext, 82 @NonNull Network network, 83 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 84 @NonNull ParcelUuid subscriptionGroup, 85 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 86 @Nullable PersistableBundleWrapper carrierConfig, 87 @NonNull NetworkEvaluatorCallback evaluatorCallback, 88 @NonNull Dependencies dependencies) { 89 mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); 90 mHandler = new Handler(mVcnContext.getLooper()); 91 92 mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies"); 93 mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps"); 94 95 Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates"); 96 Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); 97 Objects.requireNonNull(lastSnapshot, "Missing lastSnapshot"); 98 99 mNetworkRecordBuilder = 100 new UnderlyingNetworkRecord.Builder( 101 Objects.requireNonNull(network, "Missing network")); 102 mIsSelected = false; 103 mIsPenalized = false; 104 mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig); 105 106 updatePriorityClass( 107 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); 108 109 try { 110 mMetricMonitors.add( 111 mDependencies.newIpSecPacketLossDetector( 112 mVcnContext, 113 mNetworkRecordBuilder.getNetwork(), 114 carrierConfig, 115 new MetricMonitorCallbackImpl())); 116 } catch (IllegalAccessException e) { 117 // No action. Do not add anything to mMetricMonitors 118 } 119 } 120 UnderlyingNetworkEvaluator( @onNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback)121 public UnderlyingNetworkEvaluator( 122 @NonNull VcnContext vcnContext, 123 @NonNull Network network, 124 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 125 @NonNull ParcelUuid subscriptionGroup, 126 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 127 @Nullable PersistableBundleWrapper carrierConfig, 128 @NonNull NetworkEvaluatorCallback evaluatorCallback) { 129 this( 130 vcnContext, 131 network, 132 underlyingNetworkTemplates, 133 subscriptionGroup, 134 lastSnapshot, 135 carrierConfig, 136 evaluatorCallback, 137 new Dependencies()); 138 } 139 140 @VisibleForTesting(visibility = Visibility.PRIVATE) 141 public static class Dependencies { 142 /** Get an IpSecPacketLossDetector instance */ newIpSecPacketLossDetector( @onNull VcnContext vcnContext, @NonNull Network network, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)143 public IpSecPacketLossDetector newIpSecPacketLossDetector( 144 @NonNull VcnContext vcnContext, 145 @NonNull Network network, 146 @Nullable PersistableBundleWrapper carrierConfig, 147 @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback) 148 throws IllegalAccessException { 149 return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback); 150 } 151 } 152 153 /** Callback to notify caller to reevaluate network selection */ 154 public interface NetworkEvaluatorCallback { 155 /** 156 * Called when mIsPenalized changed 157 * 158 * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network 159 * candidates for VCN underlying network selection 160 */ onEvaluationResultChanged()161 void onEvaluationResultChanged(); 162 } 163 164 private class MetricMonitorCallbackImpl 165 implements NetworkMetricMonitor.NetworkMetricMonitorCallback { onValidationResultReceived()166 public void onValidationResultReceived() { 167 mVcnContext.ensureRunningOnLooperThread(); 168 169 handleValidationResult(); 170 } 171 } 172 updatePriorityClass( @onNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig)173 private void updatePriorityClass( 174 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 175 @NonNull ParcelUuid subscriptionGroup, 176 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 177 @Nullable PersistableBundleWrapper carrierConfig) { 178 if (mNetworkRecordBuilder.isValid()) { 179 mPriorityClass = 180 NetworkPriorityClassifier.calculatePriorityClass( 181 mVcnContext, 182 mNetworkRecordBuilder.build(), 183 underlyingNetworkTemplates, 184 subscriptionGroup, 185 lastSnapshot, 186 mIsSelected, 187 carrierConfig); 188 } else { 189 mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID; 190 } 191 } 192 193 /** Get the comparator for UnderlyingNetworkEvaluator */ getComparator(VcnContext vcnContext)194 public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) { 195 return (left, right) -> { 196 if (left.mIsPenalized != right.mIsPenalized) { 197 // A penalized network should have lower priority which means a larger index 198 return left.mIsPenalized ? 1 : -1; 199 } 200 201 final int leftIndex = left.mPriorityClass; 202 final int rightIndex = right.mPriorityClass; 203 204 // In the case of networks in the same priority class, prioritize based on other 205 // criteria (eg. actively selected network, link metrics, etc) 206 if (leftIndex == rightIndex) { 207 // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord 208 // fall into the same priority class. 209 if (left.mIsSelected) { 210 return -1; 211 } 212 if (right.mIsSelected) { 213 return 1; 214 } 215 } 216 return Integer.compare(leftIndex, rightIndex); 217 }; 218 } 219 220 private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) { 221 final int[] timeoutMinuteList; 222 223 if (carrierConfig != null) { 224 timeoutMinuteList = 225 carrierConfig.getIntArray( 226 VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY, 227 PENALTY_TIMEOUT_MINUTES_DEFAULT); 228 } else { 229 timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT; 230 } 231 232 // TODO: Add the support of back-off timeouts and return the full list 233 return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]); 234 } 235 236 private void handleValidationResult() { 237 final boolean wasPenalized = mIsPenalized; 238 mIsPenalized = false; 239 for (NetworkMetricMonitor monitor : mMetricMonitors) { 240 mIsPenalized |= monitor.isValidationFailed(); 241 } 242 243 if (wasPenalized == mIsPenalized) { 244 return; 245 } 246 247 logInfo( 248 "#handleValidationResult: wasPenalized " 249 + wasPenalized 250 + " mIsPenalized " 251 + mIsPenalized); 252 253 if (mIsPenalized) { 254 mHandler.postDelayed( 255 new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs); 256 } else { 257 // Exit the penalty box 258 mHandler.removeCallbacksAndEqualMessages(mCancellationToken); 259 } 260 mEvaluatorCallback.onEvaluationResultChanged(); 261 } 262 263 public class ExitPenaltyBoxRunnable implements Runnable { 264 @Override 265 public void run() { 266 if (!mIsPenalized) { 267 logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled"); 268 return; 269 } 270 271 // TODO: There might be a future metric monitor (e.g. ping) that will require the 272 // validation to pass before exiting the penalty box. 273 mIsPenalized = false; 274 mEvaluatorCallback.onEvaluationResultChanged(); 275 } 276 } 277 278 /** Set the NetworkCapabilities */ 279 public void setNetworkCapabilities( 280 @NonNull NetworkCapabilities nc, 281 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 282 @NonNull ParcelUuid subscriptionGroup, 283 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 284 @Nullable PersistableBundleWrapper carrierConfig) { 285 mNetworkRecordBuilder.setNetworkCapabilities(nc); 286 287 updatePriorityClass( 288 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); 289 290 for (NetworkMetricMonitor monitor : mMetricMonitors) { 291 monitor.onLinkPropertiesOrCapabilitiesChanged(); 292 } 293 } 294 295 /** Set the LinkProperties */ 296 public void setLinkProperties( 297 @NonNull LinkProperties lp, 298 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 299 @NonNull ParcelUuid subscriptionGroup, 300 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 301 @Nullable PersistableBundleWrapper carrierConfig) { 302 mNetworkRecordBuilder.setLinkProperties(lp); 303 304 updatePriorityClass( 305 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); 306 307 for (NetworkMetricMonitor monitor : mMetricMonitors) { 308 monitor.onLinkPropertiesOrCapabilitiesChanged(); 309 } 310 } 311 312 /** Set whether the network is blocked */ 313 public void setIsBlocked( 314 boolean isBlocked, 315 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 316 @NonNull ParcelUuid subscriptionGroup, 317 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 318 @Nullable PersistableBundleWrapper carrierConfig) { 319 mNetworkRecordBuilder.setIsBlocked(isBlocked); 320 321 updatePriorityClass( 322 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); 323 } 324 325 /** Set whether the network is selected as VCN's underlying network */ 326 public void setIsSelected( 327 boolean isSelected, 328 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 329 @NonNull ParcelUuid subscriptionGroup, 330 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 331 @Nullable PersistableBundleWrapper carrierConfig) { 332 mIsSelected = isSelected; 333 334 updatePriorityClass( 335 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); 336 337 for (NetworkMetricMonitor monitor : mMetricMonitors) { 338 monitor.setIsSelectedUnderlyingNetwork(isSelected); 339 } 340 } 341 342 /** 343 * Update the last TelephonySubscriptionSnapshot and carrier config to reevaluate the network 344 */ 345 public void reevaluate( 346 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, 347 @NonNull ParcelUuid subscriptionGroup, 348 @NonNull TelephonySubscriptionSnapshot lastSnapshot, 349 @Nullable PersistableBundleWrapper carrierConfig) { 350 updatePriorityClass( 351 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); 352 353 // The already scheduled event will not be affected. The followup events will be scheduled 354 // with the new timeout 355 mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig); 356 357 for (NetworkMetricMonitor monitor : mMetricMonitors) { 358 monitor.setCarrierConfig(carrierConfig); 359 } 360 } 361 362 /** Update the inbound IpSecTransform applied to the network */ 363 public void setInboundTransform(@NonNull IpSecTransform transform) { 364 if (!mIsSelected) { 365 logWtf("setInboundTransform on an unselected evaluator"); 366 return; 367 } 368 369 for (NetworkMetricMonitor monitor : mMetricMonitors) { 370 monitor.setInboundTransform(transform); 371 } 372 } 373 374 /** Close the evaluator and stop all the underlying network metric monitors */ 375 public void close() { 376 mHandler.removeCallbacksAndEqualMessages(mCancellationToken); 377 378 for (NetworkMetricMonitor monitor : mMetricMonitors) { 379 monitor.close(); 380 } 381 } 382 383 /** Return whether this network evaluator is valid */ 384 public boolean isValid() { 385 return mNetworkRecordBuilder.isValid(); 386 } 387 388 /** Return the network */ 389 public Network getNetwork() { 390 return mNetworkRecordBuilder.getNetwork(); 391 } 392 393 /** Return the network record */ 394 public UnderlyingNetworkRecord getNetworkRecord() { 395 return mNetworkRecordBuilder.build(); 396 } 397 398 /** Return the priority class for network selection */ 399 public int getPriorityClass() { 400 return mPriorityClass; 401 } 402 403 /** Return whether the network is being penalized */ 404 public boolean isPenalized() { 405 return mIsPenalized; 406 } 407 408 /** Dump the information of this instance */ 409 public void dump(IndentingPrintWriter pw) { 410 pw.println("UnderlyingNetworkEvaluator:"); 411 pw.increaseIndent(); 412 413 if (mNetworkRecordBuilder.isValid()) { 414 getNetworkRecord().dump(pw); 415 } else { 416 pw.println( 417 "UnderlyingNetworkRecord incomplete: mNetwork: " 418 + mNetworkRecordBuilder.getNetwork()); 419 } 420 421 pw.println("mIsSelected: " + mIsSelected); 422 pw.println("mPriorityClass: " + mPriorityClass); 423 pw.println("mIsPenalized: " + mIsPenalized); 424 425 pw.decreaseIndent(); 426 } 427 428 private String getLogPrefix() { 429 return "[Network " + mNetworkRecordBuilder.getNetwork() + "] "; 430 } 431 432 private void logInfo(String msg) { 433 Slog.i(TAG, getLogPrefix() + msg); 434 LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg); 435 } 436 437 private void logWtf(String msg) { 438 Slog.wtf(TAG, getLogPrefix() + msg); 439 LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg); 440 } 441 } 442