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