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