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