• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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