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