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