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