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