• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.server.vcn.routeselection;
17 
18 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
21 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
22 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
23 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
24 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
25 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
26 import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
27 
28 import static com.android.server.VcnManagementService.LOCAL_LOG;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.TargetApi;
33 import android.net.NetworkCapabilities;
34 import android.net.TelephonyNetworkSpecifier;
35 import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
36 import android.net.vcn.VcnManager;
37 import android.net.vcn.VcnUnderlyingNetworkTemplate;
38 import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
39 import android.os.Build;
40 import android.os.ParcelUuid;
41 import android.telephony.SubscriptionManager;
42 import android.telephony.TelephonyManager;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.annotations.VisibleForTesting.Visibility;
47 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
48 import com.android.server.vcn.VcnContext;
49 
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Set;
53 
54 /** @hide */
55 @TargetApi(Build.VERSION_CODES.BAKLAVA)
56 class NetworkPriorityClassifier {
57     @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName();
58     /**
59      * Minimum signal strength for a WiFi network to be eligible for switching to
60      *
61      * <p>A network that satisfies this is eligible to become the selected underlying network with
62      * no additional conditions
63      */
64     @VisibleForTesting(visibility = Visibility.PRIVATE)
65     static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
66     /**
67      * Minimum signal strength to continue using a WiFi network
68      *
69      * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
70      * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
71      * prospective-network RSSI threshold CANNOT be switched to.
72      */
73     @VisibleForTesting(visibility = Visibility.PRIVATE)
74     static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
75 
76     /**
77      * Priority for networks that VCN can fall back to.
78      *
79      * <p>If none of the network candidates are validated or match any template, VCN will fall back
80      * to any INTERNET network.
81      */
82     @VisibleForTesting(visibility = Visibility.PRIVATE)
83     static final int PRIORITY_FALLBACK = Integer.MAX_VALUE;
84 
85     /**
86      * Priority for networks that cannot be selected as VCN's underlying networks.
87      *
88      * <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
89      * template as the underlying network.
90      */
91     static final int PRIORITY_INVALID = -1;
92 
93     /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
calculatePriorityClass( VcnContext vcnContext, UnderlyingNetworkRecord networkRecord, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, boolean isSelected, PersistableBundleWrapper carrierConfig)94     public static int calculatePriorityClass(
95             VcnContext vcnContext,
96             UnderlyingNetworkRecord networkRecord,
97             List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
98             ParcelUuid subscriptionGroup,
99             TelephonySubscriptionSnapshot snapshot,
100             boolean isSelected,
101             PersistableBundleWrapper carrierConfig) {
102         // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
103 
104         if (networkRecord.isBlocked) {
105             logWtf("Network blocked for System Server: " + networkRecord.network);
106             return PRIORITY_INVALID;
107         }
108 
109         if (snapshot == null) {
110             logWtf("Got null snapshot");
111             return PRIORITY_INVALID;
112         }
113 
114         int priorityIndex = 0;
115         for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
116             if (checkMatchesPriorityRule(
117                     vcnContext,
118                     nwPriority,
119                     networkRecord,
120                     subscriptionGroup,
121                     snapshot,
122                     isSelected,
123                     carrierConfig)) {
124                 return priorityIndex;
125             }
126             priorityIndex++;
127         }
128 
129         final NetworkCapabilities caps = networkRecord.networkCapabilities;
130         if (caps.hasCapability(NET_CAPABILITY_INTERNET)
131                 || (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST))) {
132             return PRIORITY_FALLBACK;
133         }
134         return PRIORITY_INVALID;
135     }
136 
137     @VisibleForTesting(visibility = Visibility.PRIVATE)
checkMatchesPriorityRule( VcnContext vcnContext, VcnUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, boolean isSelected, PersistableBundleWrapper carrierConfig)138     public static boolean checkMatchesPriorityRule(
139             VcnContext vcnContext,
140             VcnUnderlyingNetworkTemplate networkPriority,
141             UnderlyingNetworkRecord networkRecord,
142             ParcelUuid subscriptionGroup,
143             TelephonySubscriptionSnapshot snapshot,
144             boolean isSelected,
145             PersistableBundleWrapper carrierConfig) {
146         final NetworkCapabilities caps = networkRecord.networkCapabilities;
147 
148         final int meteredMatch = networkPriority.getMetered();
149         final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
150         if (meteredMatch == MATCH_REQUIRED && !isMetered
151                 || meteredMatch == MATCH_FORBIDDEN && isMetered) {
152             return false;
153         }
154 
155         // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not
156         // selected, but less than entry threshold
157         if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
158                 || (caps.getLinkUpstreamBandwidthKbps()
159                                 < networkPriority.getMinEntryUpstreamBandwidthKbps()
160                         && !isSelected)) {
161             return false;
162         }
163 
164         if (caps.getLinkDownstreamBandwidthKbps()
165                         < networkPriority.getMinExitDownstreamBandwidthKbps()
166                 || (caps.getLinkDownstreamBandwidthKbps()
167                                 < networkPriority.getMinEntryDownstreamBandwidthKbps()
168                         && !isSelected)) {
169             return false;
170         }
171 
172         for (Map.Entry<Integer, Integer> entry :
173                 networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
174             final int cap = entry.getKey();
175             final int matchCriteria = entry.getValue();
176 
177             if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
178                 return false;
179             } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
180                 return false;
181             }
182         }
183 
184         if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
185             return true;
186         }
187 
188         if (networkPriority instanceof VcnWifiUnderlyingNetworkTemplate) {
189             return checkMatchesWifiPriorityRule(
190                     (VcnWifiUnderlyingNetworkTemplate) networkPriority,
191                     networkRecord,
192                     isSelected,
193                     carrierConfig);
194         }
195 
196         if (networkPriority instanceof VcnCellUnderlyingNetworkTemplate) {
197             return checkMatchesCellPriorityRule(
198                     vcnContext,
199                     (VcnCellUnderlyingNetworkTemplate) networkPriority,
200                     networkRecord,
201                     subscriptionGroup,
202                     snapshot);
203         }
204 
205         logWtf(
206                 "Got unknown VcnUnderlyingNetworkTemplate class: "
207                         + networkPriority.getClass().getSimpleName());
208         return false;
209     }
210 
211     @VisibleForTesting(visibility = Visibility.PRIVATE)
checkMatchesWifiPriorityRule( VcnWifiUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, boolean isSelected, PersistableBundleWrapper carrierConfig)212     public static boolean checkMatchesWifiPriorityRule(
213             VcnWifiUnderlyingNetworkTemplate networkPriority,
214             UnderlyingNetworkRecord networkRecord,
215             boolean isSelected,
216             PersistableBundleWrapper carrierConfig) {
217         final NetworkCapabilities caps = networkRecord.networkCapabilities;
218 
219         if (!caps.hasTransport(TRANSPORT_WIFI)) {
220             return false;
221         }
222 
223         // TODO: Move the Network Quality check to the network metric monitor framework.
224         if (!isWifiRssiAcceptable(networkRecord, isSelected, carrierConfig)) {
225             return false;
226         }
227 
228         if (!networkPriority.getSsids().isEmpty()
229                 && !networkPriority.getSsids().contains(caps.getSsid())) {
230             return false;
231         }
232 
233         return true;
234     }
235 
isWifiRssiAcceptable( UnderlyingNetworkRecord networkRecord, boolean isSelected, PersistableBundleWrapper carrierConfig)236     private static boolean isWifiRssiAcceptable(
237             UnderlyingNetworkRecord networkRecord,
238             boolean isSelected,
239             PersistableBundleWrapper carrierConfig) {
240         final NetworkCapabilities caps = networkRecord.networkCapabilities;
241 
242         if (isSelected && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
243             return true;
244         }
245 
246         if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
247             return true;
248         }
249 
250         return false;
251     }
252 
253     @VisibleForTesting(visibility = Visibility.PRIVATE)
checkMatchesCellPriorityRule( VcnContext vcnContext, VcnCellUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot)254     public static boolean checkMatchesCellPriorityRule(
255             VcnContext vcnContext,
256             VcnCellUnderlyingNetworkTemplate networkPriority,
257             UnderlyingNetworkRecord networkRecord,
258             ParcelUuid subscriptionGroup,
259             TelephonySubscriptionSnapshot snapshot) {
260         final NetworkCapabilities caps = networkRecord.networkCapabilities;
261 
262         if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
263             return false;
264         }
265 
266         final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
267                 ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
268         if (telephonyNetworkSpecifier == null) {
269             logWtf("Got null NetworkSpecifier");
270             return false;
271         }
272 
273         final int subId = telephonyNetworkSpecifier.getSubscriptionId();
274         final TelephonyManager subIdSpecificTelephonyMgr =
275                 vcnContext
276                         .getContext()
277                         .getSystemService(TelephonyManager.class)
278                         .createForSubscriptionId(subId);
279 
280         if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
281             final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
282             if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
283                 return false;
284             }
285         }
286 
287         if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
288             final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
289             if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
290                 return false;
291             }
292         }
293 
294         final int roamingMatch = networkPriority.getRoaming();
295         final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
296         if (roamingMatch == MATCH_REQUIRED && !isRoaming
297                 || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
298             return false;
299         }
300 
301         final int opportunisticMatch = networkPriority.getOpportunistic();
302         final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
303         if (opportunisticMatch == MATCH_REQUIRED) {
304             if (!isOpportunistic) {
305                 return false;
306             }
307 
308             // If this carrier is the active data provider, ensure that opportunistic is only
309             // ever prioritized if it is also the active data subscription. This ensures that
310             // if an opportunistic subscription is still in the process of being switched to,
311             // or switched away from, the VCN does not attempt to continue using it against the
312             // decision made at the telephony layer. Failure to do so may result in the modem
313             // switching back and forth.
314             //
315             // Allow the following two cases:
316             // 1. Active subId is NOT in the group that this VCN is supporting
317             // 2. This opportunistic subscription is for the active subId
318             if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
319                             .contains(SubscriptionManager.getActiveDataSubscriptionId())
320                     && !caps.getSubscriptionIds()
321                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
322                 return false;
323             }
324         } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
325             return false;
326         }
327 
328         return true;
329     }
330 
isOpportunistic( @onNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds)331     static boolean isOpportunistic(
332             @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
333         if (snapshot == null) {
334             logWtf("Got null snapshot");
335             return false;
336         }
337         for (int subId : subIds) {
338             if (snapshot.isOpportunistic(subId)) {
339                 return true;
340             }
341         }
342         return false;
343     }
344 
getWifiEntryRssiThreshold(@ullable PersistableBundleWrapper carrierConfig)345     static int getWifiEntryRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
346         if (carrierConfig != null) {
347             return carrierConfig.getInt(
348                     VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
349                     WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
350         }
351         return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
352     }
353 
getWifiExitRssiThreshold(@ullable PersistableBundleWrapper carrierConfig)354     static int getWifiExitRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
355         if (carrierConfig != null) {
356             return carrierConfig.getInt(
357                     VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
358                     WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
359         }
360         return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
361     }
362 
logWtf(String msg)363     private static void logWtf(String msg) {
364         Slog.wtf(TAG, msg);
365         LOCAL_LOG.log(TAG + " WTF: " + msg);
366     }
367 }
368