1 /* 2 * Copyright 2020 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 17 package com.google.android.iwlan.epdg; 18 19 import android.content.Context; 20 import android.net.DnsResolver; 21 import android.net.DnsResolver.DnsException; 22 import android.net.InetAddresses; 23 import android.net.Network; 24 import android.support.annotation.IntDef; 25 import android.support.annotation.NonNull; 26 import android.support.annotation.Nullable; 27 import android.telephony.CarrierConfigManager; 28 import android.telephony.CellIdentityGsm; 29 import android.telephony.CellIdentityLte; 30 import android.telephony.CellIdentityNr; 31 import android.telephony.CellIdentityWcdma; 32 import android.telephony.CellInfo; 33 import android.telephony.CellInfoGsm; 34 import android.telephony.CellInfoLte; 35 import android.telephony.CellInfoNr; 36 import android.telephony.CellInfoTdscdma; 37 import android.telephony.CellInfoWcdma; 38 import android.telephony.DataFailCause; 39 import android.telephony.SubscriptionInfo; 40 import android.telephony.SubscriptionManager; 41 import android.telephony.TelephonyManager; 42 import android.text.TextUtils; 43 import android.util.Log; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import com.google.android.iwlan.ErrorPolicyManager; 48 import com.google.android.iwlan.IwlanCarrierConfig; 49 import com.google.android.iwlan.IwlanError; 50 import com.google.android.iwlan.IwlanHelper; 51 import com.google.android.iwlan.epdg.NaptrDnsResolver.NaptrTarget; 52 import com.google.android.iwlan.flags.FeatureFlags; 53 import com.google.android.iwlan.flags.FeatureFlagsImpl; 54 55 import java.net.Inet4Address; 56 import java.net.Inet6Address; 57 import java.net.InetAddress; 58 import java.net.UnknownHostException; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.Comparator; 62 import java.util.HashSet; 63 import java.util.LinkedHashMap; 64 import java.util.LinkedHashSet; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Objects; 68 import java.util.Set; 69 import java.util.concurrent.ArrayBlockingQueue; 70 import java.util.concurrent.BlockingQueue; 71 import java.util.concurrent.CompletableFuture; 72 import java.util.concurrent.ConcurrentHashMap; 73 import java.util.concurrent.ExecutionException; 74 import java.util.concurrent.Executor; 75 import java.util.concurrent.ExecutorService; 76 import java.util.concurrent.Future; 77 import java.util.concurrent.SynchronousQueue; 78 import java.util.concurrent.ThreadPoolExecutor; 79 import java.util.concurrent.TimeUnit; 80 import java.util.concurrent.TimeoutException; 81 import java.util.regex.Pattern; 82 import java.util.stream.Collectors; 83 84 public class EpdgSelector { 85 private final FeatureFlags mFeatureFlags; 86 private static final String TAG = "EpdgSelector"; 87 private final Context mContext; 88 private final int mSlotId; 89 private static final ConcurrentHashMap<Integer, EpdgSelector> mSelectorInstances = 90 new ConcurrentHashMap<>(); 91 private int mV4PcoId = -1; 92 private int mV6PcoId = -1; 93 private List<byte[]> mV4PcoData; 94 private List<byte[]> mV6PcoData; 95 @NonNull private final ErrorPolicyManager mErrorPolicyManager; 96 97 // Temporary excluded IP addresses due to recent failures. Cleared after tunnel opened 98 // successfully or all resolved IP addresses are tried and excluded. 99 private final Set<InetAddress> mTemporaryExcludedAddresses; 100 101 // The default DNS timeout in the DNS module is set to 5 seconds. To account for IPC overhead, 102 // IWLAN applies an internal timeout of 6 seconds, slightly longer than the default timeout 103 private static final long DNS_RESOLVER_TIMEOUT_DURATION_SEC = 6L; 104 105 private static final long PARALLEL_STATIC_RESOLUTION_TIMEOUT_DURATION_SEC = 6L; 106 private static final long PARALLEL_PLMN_RESOLUTION_TIMEOUT_DURATION_SEC = 20L; 107 private static final int NUM_EPDG_SELECTION_EXECUTORS = 2; // 1 each for normal selection, SOS. 108 private static final int MAX_DNS_RESOLVER_THREADS = 25; // Do not expect > 25 FQDNs per carrier. 109 110 private static final int PCO_MCC_MNC_LEN = 3; // 3 bytes for MCC and MNC in PCO data. 111 private static final int PCO_IPV4_LEN = 4; // 4 bytes for IPv4 address in PCO data. 112 private static final int PCO_IPV6_LEN = 16; // 16 bytes for IPv6 address in PCO data. 113 114 private static final String NO_DOMAIN = "NO_DOMAIN"; 115 private static final Pattern PLMN_PATTERN = Pattern.compile("\\d{5,6}"); 116 117 BlockingQueue<Runnable> dnsResolutionQueue; 118 119 Executor mDnsResolutionExecutor; 120 121 ExecutorService mEpdgSelectionExecutor; 122 Future<?> mDnsPrefetchFuture; 123 124 ExecutorService mSosEpdgSelectionExecutor; 125 Future<?> mSosDnsPrefetchFuture; 126 127 final Comparator<InetAddress> inetAddressComparator = 128 (ip1, ip2) -> { 129 if ((ip1 instanceof Inet4Address) && (ip2 instanceof Inet6Address)) { 130 return -1; 131 } else if ((ip1 instanceof Inet6Address) && (ip2 instanceof Inet4Address)) { 132 return 1; 133 } else { 134 return 0; 135 } 136 }; 137 138 public static final int PROTO_FILTER_IPV4 = 0; 139 public static final int PROTO_FILTER_IPV6 = 1; 140 public static final int PROTO_FILTER_IPV4V6 = 2; 141 142 @IntDef({PROTO_FILTER_IPV4, PROTO_FILTER_IPV6, PROTO_FILTER_IPV4V6}) 143 @interface ProtoFilter {} 144 145 public static final int IPV4_PREFERRED = 0; 146 public static final int IPV6_PREFERRED = 1; 147 public static final int SYSTEM_PREFERRED = 2; 148 149 @IntDef({IPV4_PREFERRED, IPV6_PREFERRED, SYSTEM_PREFERRED}) 150 @interface EpdgAddressOrder {} 151 152 public interface EpdgSelectorCallback { 153 /*gives priority ordered list of addresses*/ onServerListChanged(int transactionId, List<InetAddress> validIPList)154 void onServerListChanged(int transactionId, List<InetAddress> validIPList); 155 onError(int transactionId, IwlanError error)156 void onError(int transactionId, IwlanError error); 157 } 158 159 @VisibleForTesting EpdgSelector(Context context, int slotId, FeatureFlags featureFlags)160 EpdgSelector(Context context, int slotId, FeatureFlags featureFlags) { 161 mContext = context; 162 mSlotId = slotId; 163 mFeatureFlags = featureFlags; 164 165 mV4PcoData = new ArrayList<>(); 166 mV6PcoData = new ArrayList<>(); 167 168 mV4PcoData = new ArrayList<>(); 169 mV6PcoData = new ArrayList<>(); 170 171 mErrorPolicyManager = ErrorPolicyManager.getInstance(mContext, mSlotId); 172 173 mTemporaryExcludedAddresses = new HashSet<>(); 174 initializeExecutors(); 175 } 176 initializeExecutors()177 private void initializeExecutors() { 178 int maxEpdgSelectionThreads = mFeatureFlags.preventEpdgSelectionThreadsExhausted() ? 3 : 2; 179 180 dnsResolutionQueue = 181 new ArrayBlockingQueue<>( 182 MAX_DNS_RESOLVER_THREADS 183 * maxEpdgSelectionThreads 184 * NUM_EPDG_SELECTION_EXECUTORS); 185 186 mDnsResolutionExecutor = 187 new ThreadPoolExecutor( 188 0, MAX_DNS_RESOLVER_THREADS, 60L, TimeUnit.SECONDS, dnsResolutionQueue); 189 190 mEpdgSelectionExecutor = 191 new ThreadPoolExecutor( 192 0, 193 maxEpdgSelectionThreads, 194 60L, 195 TimeUnit.SECONDS, 196 new SynchronousQueue<>()); 197 198 mSosEpdgSelectionExecutor = 199 new ThreadPoolExecutor( 200 0, 201 maxEpdgSelectionThreads, 202 60L, 203 TimeUnit.SECONDS, 204 new SynchronousQueue<>()); 205 } 206 getSelectorInstance(Context context, int slotId)207 public static EpdgSelector getSelectorInstance(Context context, int slotId) { 208 mSelectorInstances.computeIfAbsent( 209 slotId, k -> new EpdgSelector(context, slotId, new FeatureFlagsImpl())); 210 return mSelectorInstances.get(slotId); 211 } 212 setPcoData(int pcoId, @NonNull byte[] pcoData)213 public boolean setPcoData(int pcoId, @NonNull byte[] pcoData) { 214 Log.d( 215 TAG, 216 "onReceive PcoId:" 217 + String.format("0x%04x", pcoId) 218 + " PcoData:" 219 + Arrays.toString(pcoData)); 220 221 int PCO_ID_IPV6 = 222 IwlanCarrierConfig.getConfigInt( 223 mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV6_INT); 224 int PCO_ID_IPV4 = 225 IwlanCarrierConfig.getConfigInt( 226 mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV4_INT); 227 228 Log.d( 229 TAG, 230 "PCO_ID_IPV6:" 231 + String.format("0x%04x", PCO_ID_IPV6) 232 + " PCO_ID_IPV4:" 233 + String.format("0x%04x", PCO_ID_IPV4)); 234 235 if (pcoId == PCO_ID_IPV4) { 236 mV4PcoId = pcoId; 237 mV4PcoData.add(pcoData); 238 return true; 239 } else if (pcoId == PCO_ID_IPV6) { 240 mV6PcoId = pcoId; 241 mV6PcoData.add(pcoData); 242 return true; 243 } 244 245 return false; 246 } 247 clearPcoData()248 public void clearPcoData() { 249 Log.d(TAG, "Clear PCO data"); 250 mV4PcoId = -1; 251 mV6PcoId = -1; 252 mV4PcoData.clear(); 253 mV6PcoData.clear(); 254 } 255 256 /** 257 * Notify {@link EpdgSelector} that ePDG is connected successfully. The excluded ip addresses 258 * will be cleared so that next ePDG selection will retry all ip addresses. 259 */ onEpdgConnectedSuccessfully()260 void onEpdgConnectedSuccessfully() { 261 clearExcludedIpAddresses(); 262 } 263 264 /** 265 * Notify {@link EpdgSelector} that failed to connect to an ePDG. EpdgSelector will add the 266 * {@code ipAddress} into excluded list and will not retry until any ePDG connected successfully 267 * or all ip addresses candidates are tried. 268 * 269 * @param ipAddress the ePDG ip address that failed to connect 270 */ onEpdgConnectionFailed(InetAddress ipAddress)271 void onEpdgConnectionFailed(InetAddress ipAddress) { 272 excludeIpAddress(ipAddress); 273 } 274 excludeIpAddress(InetAddress ipAddress)275 private void excludeIpAddress(InetAddress ipAddress) { 276 if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { 277 return; 278 } 279 Log.d(TAG, "Added " + ipAddress + " into temporary excluded addresses"); 280 mTemporaryExcludedAddresses.add(ipAddress); 281 } 282 clearExcludedIpAddresses()283 private void clearExcludedIpAddresses() { 284 if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { 285 return; 286 } 287 Log.d(TAG, "Cleared temporary excluded addresses"); 288 mTemporaryExcludedAddresses.clear(); 289 } 290 isInExcludedIpAddresses(InetAddress ipAddress)291 private boolean isInExcludedIpAddresses(InetAddress ipAddress) { 292 return mTemporaryExcludedAddresses.contains(ipAddress); 293 } 294 submitDnsResolverQuery( String domainName, Network network, int queryType, Executor executor)295 private CompletableFuture<Map.Entry<String, List<InetAddress>>> submitDnsResolverQuery( 296 String domainName, Network network, int queryType, Executor executor) { 297 CompletableFuture<Map.Entry<String, List<InetAddress>>> result = new CompletableFuture(); 298 299 final DnsResolver.Callback<List<InetAddress>> cb = 300 new DnsResolver.Callback<List<InetAddress>>() { 301 @Override 302 public void onAnswer(@NonNull final List<InetAddress> answer, final int rcode) { 303 if (rcode != 0) { 304 Log.e( 305 TAG, 306 "DnsResolver Response Code = " 307 + rcode 308 + " for domain " 309 + domainName); 310 } 311 Map.Entry<String, List<InetAddress>> entry = Map.entry(domainName, answer); 312 result.complete(entry); 313 } 314 315 @Override 316 public void onError(@Nullable final DnsResolver.DnsException error) { 317 Log.e( 318 TAG, 319 "Resolve DNS with error: " + error + " for domain: " + domainName); 320 result.complete(null); 321 } 322 }; 323 DnsResolver.getInstance() 324 .query(network, domainName, queryType, DnsResolver.FLAG_EMPTY, executor, null, cb); 325 return result; 326 } 327 v4v6ProtocolFilter(List<InetAddress> ipList, int filter)328 private List<InetAddress> v4v6ProtocolFilter(List<InetAddress> ipList, int filter) { 329 List<InetAddress> validIpList = new ArrayList<>(); 330 for (InetAddress ipAddress : ipList) { 331 if (IwlanHelper.isIpv4EmbeddedIpv6Address(ipAddress)) { 332 continue; 333 } 334 switch (filter) { 335 case PROTO_FILTER_IPV4: 336 if (ipAddress instanceof Inet4Address) { 337 validIpList.add(ipAddress); 338 } 339 break; 340 case PROTO_FILTER_IPV6: 341 if (ipAddress instanceof Inet6Address) { 342 validIpList.add(ipAddress); 343 } 344 break; 345 case PROTO_FILTER_IPV4V6: 346 validIpList.add(ipAddress); 347 break; 348 default: 349 Log.d(TAG, "Invalid ProtoFilter : " + filter); 350 } 351 } 352 return validIpList; 353 } 354 355 // Converts a list of CompletableFutures of type T into a single CompletableFuture containing a 356 // list of T. The resulting CompletableFuture waits for all futures to complete, 357 // even if any future throw an exception. allOf(List<CompletableFuture<T>> futuresList)358 private <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futuresList) { 359 CompletableFuture<Void> allFuturesResult = 360 CompletableFuture.allOf( 361 futuresList.toArray(new CompletableFuture[futuresList.size()])); 362 return allFuturesResult.thenApply( 363 v -> 364 futuresList.stream() 365 .map(CompletableFuture::join) 366 .filter(Objects::nonNull) 367 .collect(Collectors.<T>toList())); 368 } 369 370 @VisibleForTesting hasIpv4Address(Network network)371 protected boolean hasIpv4Address(Network network) { 372 return IwlanHelper.hasIpv4Address(IwlanHelper.getAllAddressesForNetwork(mContext, network)); 373 } 374 375 @VisibleForTesting hasIpv6Address(Network network)376 protected boolean hasIpv6Address(Network network) { 377 return IwlanHelper.hasIpv6Address(IwlanHelper.getAllAddressesForNetwork(mContext, network)); 378 } 379 printParallelDnsResult(Map<String, List<InetAddress>> domainNameToIpAddresses)380 private void printParallelDnsResult(Map<String, List<InetAddress>> domainNameToIpAddresses) { 381 Log.d(TAG, "Parallel DNS resolution result:"); 382 for (String domain : domainNameToIpAddresses.keySet()) { 383 Log.d(TAG, domain + ": " + domainNameToIpAddresses.get(domain)); 384 } 385 } 386 filterExcludedAddresses(List<InetAddress> ipList)387 private List<InetAddress> filterExcludedAddresses(List<InetAddress> ipList) { 388 if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { 389 return ipList; 390 } 391 if (mTemporaryExcludedAddresses.containsAll(ipList)) { 392 Log.d( 393 TAG, 394 "All valid ip are tried and excluded, clear all" 395 + " excluded address and retry entire list again"); 396 clearExcludedIpAddresses(); 397 } 398 399 var filteredIpList = 400 ipList.stream().filter(ipAddress -> !isInExcludedIpAddresses(ipAddress)).toList(); 401 402 int excludedIpNum = filteredIpList.size() - ipList.size(); 403 if (excludedIpNum > 0) { 404 Log.d( 405 TAG, 406 "Excluded " 407 + excludedIpNum 408 + " out of " 409 + ipList.size() 410 + " addresses from the list due to recent failures"); 411 } 412 413 return filteredIpList; 414 } 415 416 /** 417 * Returns a list of unique IP addresses corresponding to the given domain names, in the same 418 * order of the input. Runs DNS resolution across parallel threads. 419 * 420 * @param domainNames Domain names for which DNS resolution needs to be performed. 421 * @param filter Selects for IPv4, IPv6 (or both) addresses from the resulting DNS records 422 * @param network {@link Network} Network on which to run the DNS query. 423 * @param timeout timeout in seconds. 424 * @return List of unique IP addresses corresponding to the domainNames. 425 */ getIP( List<String> domainNames, int filter, Network network, long timeout)426 private LinkedHashMap<String, List<InetAddress>> getIP( 427 List<String> domainNames, int filter, Network network, long timeout) { 428 // LinkedHashMap preserves insertion order (and hence priority) of domain names passed in. 429 LinkedHashMap<String, List<InetAddress>> domainNameToIpAddr = new LinkedHashMap<>(); 430 431 List<CompletableFuture<Map.Entry<String, List<InetAddress>>>> futuresList = 432 new ArrayList<>(); 433 for (String domainName : domainNames) { 434 if (InetAddresses.isNumericAddress(domainName)) { 435 Log.d(TAG, domainName + " is a numeric IP address!"); 436 InetAddress inetAddr = InetAddresses.parseNumericAddress(domainName); 437 domainNameToIpAddr.put(NO_DOMAIN, new ArrayList<>(List.of(inetAddr))); 438 continue; 439 } 440 441 domainNameToIpAddr.put(domainName, new ArrayList<>()); 442 // Dispatches separate IPv4 and IPv6 queries to avoid being blocked on either result. 443 if (hasIpv4Address(network)) { 444 futuresList.add( 445 submitDnsResolverQuery( 446 domainName, network, DnsResolver.TYPE_A, mDnsResolutionExecutor)); 447 } 448 if (hasIpv6Address(network)) { 449 futuresList.add( 450 submitDnsResolverQuery( 451 domainName, 452 network, 453 DnsResolver.TYPE_AAAA, 454 mDnsResolutionExecutor)); 455 } 456 } 457 CompletableFuture<List<Map.Entry<String, List<InetAddress>>>> allFuturesResult = 458 allOf(futuresList); 459 460 List<Map.Entry<String, List<InetAddress>>> resultList = null; 461 try { 462 resultList = allFuturesResult.get(timeout, TimeUnit.SECONDS); 463 } catch (ExecutionException e) { 464 Log.e(TAG, "Cause of ExecutionException: ", e.getCause()); 465 } catch (InterruptedException e) { 466 Thread.currentThread().interrupt(); 467 Log.e(TAG, "InterruptedException: ", e); 468 } catch (TimeoutException e) { 469 Log.e(TAG, "TimeoutException: ", e); 470 } finally { 471 if (resultList == null) { 472 Log.w(TAG, "No IP addresses in parallel DNS query!"); 473 } else { 474 for (Map.Entry<String, List<InetAddress>> entry : resultList) { 475 String resultDomainName = entry.getKey(); 476 List<InetAddress> resultIpAddr = v4v6ProtocolFilter(entry.getValue(), filter); 477 478 if (!domainNameToIpAddr.containsKey(resultDomainName)) { 479 Log.w( 480 TAG, 481 "Unexpected domain name in DnsResolver result: " 482 + resultDomainName); 483 continue; 484 } 485 domainNameToIpAddr.get(resultDomainName).addAll(resultIpAddr); 486 } 487 } 488 } 489 return domainNameToIpAddr; 490 } 491 492 /** 493 * Updates the validIpList with the IP addresses corresponding to this domainName. Runs blocking 494 * DNS resolution on the same thread. 495 * 496 * @param domainName Domain name for which DNS resolution needs to be performed. 497 * @param filter Selects for IPv4, IPv6 (or both) addresses from the resulting DNS records 498 * @param validIpList A running list of IP addresses that needs to be updated. 499 * @param network {@link Network} Network on which to run the DNS query. 500 */ getIP( String domainName, int filter, List<InetAddress> validIpList, Network network)501 private void getIP( 502 String domainName, int filter, List<InetAddress> validIpList, Network network) { 503 List<InetAddress> ipList = new ArrayList<InetAddress>(); 504 505 // Get All IP for each domain name 506 Log.d(TAG, "Input domainName : " + domainName); 507 508 if (InetAddresses.isNumericAddress(domainName)) { 509 Log.d(TAG, domainName + " is a numeric IP address!"); 510 ipList.add(InetAddresses.parseNumericAddress(domainName)); 511 } else { 512 try { 513 CompletableFuture<List<InetAddress>> result = new CompletableFuture(); 514 final DnsResolver.Callback<List<InetAddress>> cb = 515 new DnsResolver.Callback<List<InetAddress>>() { 516 @Override 517 public void onAnswer( 518 @NonNull final List<InetAddress> answer, final int rcode) { 519 if (rcode != 0) { 520 Log.e(TAG, "DnsResolver Response Code = " + rcode); 521 } 522 result.complete(answer); 523 } 524 525 @Override 526 public void onError(@Nullable final DnsResolver.DnsException error) { 527 Log.e(TAG, "Resolve DNS with error : " + error); 528 result.completeExceptionally(error); 529 } 530 }; 531 DnsResolver.getInstance() 532 .query( 533 network, 534 domainName, 535 DnsResolver.FLAG_EMPTY, 536 Runnable::run, 537 null, 538 cb); 539 ipList = 540 new ArrayList<>( 541 result.get(DNS_RESOLVER_TIMEOUT_DURATION_SEC, TimeUnit.SECONDS)); 542 } catch (ExecutionException e) { 543 Log.e(TAG, "Cause of ExecutionException: ", e.getCause()); 544 } catch (InterruptedException e) { 545 Thread thread = Thread.currentThread(); 546 if (Thread.interrupted()) { 547 thread.interrupt(); 548 } 549 Log.e(TAG, "InterruptedException: ", e); 550 } catch (TimeoutException e) { 551 Log.e(TAG, "TimeoutException: ", e); 552 } 553 } 554 555 List<InetAddress> filteredIpList = v4v6ProtocolFilter(ipList, filter); 556 validIpList.addAll(filteredIpList); 557 } 558 getPlmnList()559 private String[] getPlmnList() { 560 List<String> plmnsFromCarrierConfig = getPlmnsFromCarrierConfig(); 561 Log.d(TAG, "plmnsFromCarrierConfig:" + plmnsFromCarrierConfig); 562 563 // Get Ehplmns & mccmnc from SubscriptionManager 564 SubscriptionManager subscriptionManager = 565 mContext.getSystemService(SubscriptionManager.class); 566 if (subscriptionManager == null) { 567 Log.e(TAG, "SubscriptionManager is NULL"); 568 return plmnsFromCarrierConfig.toArray(new String[plmnsFromCarrierConfig.size()]); 569 } 570 571 SubscriptionInfo subInfo = 572 subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(mSlotId); 573 if (subInfo == null) { 574 Log.e(TAG, "SubscriptionInfo is NULL"); 575 return plmnsFromCarrierConfig.toArray(new String[plmnsFromCarrierConfig.size()]); 576 } 577 578 // Get MCCMNC from IMSI 579 String plmnFromImsi = subInfo.getMccString() + subInfo.getMncString(); 580 581 int[] prioritizedPlmnTypes = 582 IwlanCarrierConfig.getConfigIntArray( 583 mContext, 584 mSlotId, 585 CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY); 586 587 List<String> ehplmns = getEhplmns(); 588 String registeredPlmn = getRegisteredPlmn(); 589 590 List<String> combinedList = new ArrayList<>(); 591 for (int plmnType : prioritizedPlmnTypes) { 592 switch (plmnType) { 593 case CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN: 594 if (isInEpdgSelectionInfo(registeredPlmn)) { 595 combinedList.add(registeredPlmn); 596 } 597 break; 598 case CarrierConfigManager.Iwlan.EPDG_PLMN_HPLMN: 599 combinedList.add(plmnFromImsi); 600 break; 601 case CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_ALL: 602 combinedList.addAll(getEhplmns()); 603 break; 604 case CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_FIRST: 605 if (!ehplmns.isEmpty()) { 606 combinedList.add(ehplmns.get(0)); 607 } 608 break; 609 default: 610 Log.e(TAG, "Unknown PLMN type: " + plmnType); 611 break; 612 } 613 } 614 615 combinedList = 616 combinedList.stream() 617 .distinct() 618 .filter(EpdgSelector::isValidPlmn) 619 .map(plmn -> new StringBuilder(plmn).insert(3, "-").toString()) 620 .toList(); 621 622 Log.d(TAG, "Final plmn list:" + combinedList); 623 return combinedList.toArray(new String[combinedList.size()]); 624 } 625 getPlmnsFromCarrierConfig()626 private List<String> getPlmnsFromCarrierConfig() { 627 return Arrays.asList( 628 IwlanCarrierConfig.getConfigStringArray( 629 mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY)); 630 } 631 isInEpdgSelectionInfo(String plmn)632 private boolean isInEpdgSelectionInfo(String plmn) { 633 if (!isValidPlmn(plmn)) { 634 return false; 635 } 636 List<String> plmnsFromCarrierConfig = getPlmnsFromCarrierConfig(); 637 return plmnsFromCarrierConfig.contains(new StringBuilder(plmn).insert(3, "-").toString()); 638 } 639 removeDuplicateIp(List<InetAddress> validIpList)640 private ArrayList<InetAddress> removeDuplicateIp(List<InetAddress> validIpList) { 641 ArrayList<InetAddress> resultIpList = new ArrayList<InetAddress>(); 642 643 for (InetAddress validIp : validIpList) { 644 if (!resultIpList.contains(validIp)) { 645 resultIpList.add(validIp); 646 } 647 } 648 649 return resultIpList; 650 } 651 prioritizeIp( @onNull List<InetAddress> validIpList, @EpdgAddressOrder int order)652 private List<InetAddress> prioritizeIp( 653 @NonNull List<InetAddress> validIpList, @EpdgAddressOrder int order) { 654 return switch (order) { 655 case IPV4_PREFERRED -> validIpList.stream().sorted(inetAddressComparator).toList(); 656 case IPV6_PREFERRED -> validIpList.stream() 657 .sorted(inetAddressComparator.reversed()) 658 .toList(); 659 case SYSTEM_PREFERRED -> validIpList; 660 default -> { 661 Log.w(TAG, "Invalid EpdgAddressOrder : " + order); 662 yield validIpList; 663 } 664 }; 665 } 666 splitMccMnc(String plmn)667 private String[] splitMccMnc(String plmn) { 668 String[] mccmnc = plmn.split("-"); 669 mccmnc[1] = String.format("%03d", Integer.parseInt(mccmnc[1])); 670 return mccmnc; 671 } 672 673 /** 674 * @return the registered PLMN, null if not registered with 3gpp or failed to get telephony 675 * manager 676 */ 677 @Nullable getRegisteredPlmn()678 private String getRegisteredPlmn() { 679 TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); 680 if (telephonyManager == null) { 681 Log.e(TAG, "TelephonyManager is NULL"); 682 return null; 683 } 684 685 telephonyManager = 686 telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); 687 688 String registeredPlmn = telephonyManager.getNetworkOperator(); 689 return registeredPlmn.isEmpty() ? null : registeredPlmn; 690 } 691 getEhplmns()692 private List<String> getEhplmns() { 693 TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 694 mTelephonyManager = 695 Objects.requireNonNull(mTelephonyManager) 696 .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); 697 698 if (mTelephonyManager == null) { 699 Log.e(TAG, "TelephonyManager is NULL"); 700 return new ArrayList<String>(); 701 } else { 702 return mTelephonyManager.getEquivalentHomePlmns(); 703 } 704 } 705 resolutionMethodStatic( int filter, List<InetAddress> validIpList, Network network)706 private void resolutionMethodStatic( 707 int filter, List<InetAddress> validIpList, Network network) { 708 String[] domainNames = null; 709 710 Log.d(TAG, "STATIC Method"); 711 712 // Get the static domain names from carrier config 713 // Config obtained in form of a list of domain names separated by 714 // a delimiter is only used for testing purpose. 715 if (!inSameCountry()) { 716 domainNames = 717 getDomainNames( 718 CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_ROAMING_STRING); 719 } 720 if (domainNames == null 721 && (domainNames = 722 getDomainNames( 723 CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING)) 724 == null) { 725 Log.d(TAG, "Static address string is null"); 726 return; 727 } 728 729 Log.d(TAG, "Static Domain Names: " + Arrays.toString(domainNames)); 730 LinkedHashMap<String, List<InetAddress>> domainNameToIpAddr = 731 getIP( 732 Arrays.asList(domainNames), 733 filter, 734 network, 735 PARALLEL_STATIC_RESOLUTION_TIMEOUT_DURATION_SEC); 736 printParallelDnsResult(domainNameToIpAddr); 737 domainNameToIpAddr.values().forEach(validIpList::addAll); 738 } 739 getDomainNames(String key)740 private String[] getDomainNames(String key) { 741 String configValue = IwlanCarrierConfig.getConfigString(mContext, mSlotId, key); 742 if (configValue == null || configValue.isEmpty()) { 743 Log.d(TAG, key + " string is null"); 744 return null; 745 } 746 return configValue.split(","); 747 } 748 inSameCountry()749 private boolean inSameCountry() { 750 boolean inSameCountry = true; 751 752 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 753 tm = 754 Objects.requireNonNull(tm) 755 .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); 756 757 if (tm != null) { 758 String simCountry = tm.getSimCountryIso(); 759 String currentCountry = IwlanHelper.getLastKnownCountryCode(mContext); 760 if (!TextUtils.isEmpty(simCountry) && !TextUtils.isEmpty(currentCountry)) { 761 Log.d(TAG, "simCountry = " + simCountry + ", currentCountry = " + currentCountry); 762 inSameCountry = simCountry.equalsIgnoreCase(currentCountry); 763 } 764 } 765 766 return inSameCountry; 767 } 768 resolutionMethodPlmn( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network)769 private Map<String, List<InetAddress>> resolutionMethodPlmn( 770 int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) { 771 String[] plmnList; 772 StringBuilder domainName = new StringBuilder(); 773 774 Log.d(TAG, "PLMN Method"); 775 776 plmnList = getPlmnList(); 777 List<String> domainNames = new ArrayList<>(); 778 for (String plmn : plmnList) { 779 String[] mccmnc = splitMccMnc(plmn); 780 /* 781 * Operator Identifier based ePDG FQDN format: 782 * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 783 * 784 * Operator Identifier based Emergency ePDG FQDN format: 785 * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 786 */ 787 if (isEmergency) { 788 domainName = new StringBuilder(); 789 domainName 790 .append("sos.") 791 .append("epdg.epc.mnc") 792 .append(mccmnc[1]) 793 .append(".mcc") 794 .append(mccmnc[0]) 795 .append(".pub.3gppnetwork.org"); 796 domainNames.add(domainName.toString()); 797 domainName.setLength(0); 798 } 799 // For emergency PDN setup, still adding FQDN without "sos" header as second priority 800 // because some operator doesn't support hostname with "sos" prefix. 801 domainName 802 .append("epdg.epc.mnc") 803 .append(mccmnc[1]) 804 .append(".mcc") 805 .append(mccmnc[0]) 806 .append(".pub.3gppnetwork.org"); 807 domainNames.add(domainName.toString()); 808 domainName.setLength(0); 809 } 810 811 LinkedHashMap<String, List<InetAddress>> domainNameToIpAddr = 812 getIP(domainNames, filter, network, PARALLEL_PLMN_RESOLUTION_TIMEOUT_DURATION_SEC); 813 printParallelDnsResult(domainNameToIpAddr); 814 domainNameToIpAddr.values().forEach(validIpList::addAll); 815 return domainNameToIpAddr; 816 } 817 resolutionMethodCellularLoc( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network)818 private void resolutionMethodCellularLoc( 819 int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) { 820 String[] plmnList; 821 StringBuilder domainName = new StringBuilder(); 822 823 Log.d(TAG, "CELLULAR_LOC Method"); 824 825 TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 826 mTelephonyManager = 827 Objects.requireNonNull(mTelephonyManager) 828 .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); 829 830 if (mTelephonyManager == null) { 831 Log.e(TAG, "TelephonyManager is NULL"); 832 return; 833 } 834 835 List<CellInfo> cellInfoList = mTelephonyManager.getAllCellInfo(); 836 if (cellInfoList == null) { 837 Log.e(TAG, "cellInfoList is NULL"); 838 return; 839 } 840 841 for (CellInfo cellInfo : cellInfoList) { 842 if (!cellInfo.isRegistered()) { 843 continue; 844 } 845 846 if (cellInfo instanceof CellInfoGsm) { 847 CellIdentityGsm gsmCellId = ((CellInfoGsm) cellInfo).getCellIdentity(); 848 String lacString = String.format("%04x", gsmCellId.getLac()); 849 850 lacDomainNameResolution(filter, validIpList, lacString, isEmergency, network); 851 } else if (cellInfo instanceof CellInfoWcdma) { 852 CellIdentityWcdma wcdmaCellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); 853 String lacString = String.format("%04x", wcdmaCellId.getLac()); 854 855 lacDomainNameResolution(filter, validIpList, lacString, isEmergency, network); 856 } else if (cellInfo instanceof CellInfoLte) { 857 CellIdentityLte lteCellId = ((CellInfoLte) cellInfo).getCellIdentity(); 858 String tacString = String.format("%04x", lteCellId.getTac()); 859 String[] tacSubString = new String[2]; 860 tacSubString[0] = tacString.substring(0, 2); 861 tacSubString[1] = tacString.substring(2); 862 863 plmnList = getPlmnList(); 864 for (String plmn : plmnList) { 865 String[] mccmnc = splitMccMnc(plmn); 866 /** 867 * Tracking Area Identity based ePDG FQDN format: 868 * tac-lb<TAC-low-byte>.tac-hb<TAC-high-byte>.tac. 869 * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 870 * 871 * <p>Tracking Area Identity based Emergency ePDG FQDN format: 872 * tac-lb<TAC-low-byte>.tac-hb<TAC-highbyte>.tac. 873 * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org" 874 */ 875 domainName 876 .append("tac-lb") 877 .append(tacSubString[1]) 878 .append(".tac-hb") 879 .append(tacSubString[0]); 880 if (isEmergency) { 881 domainName.append(".tac.sos.epdg.epc.mnc"); 882 } else { 883 domainName.append(".tac.epdg.epc.mnc"); 884 } 885 domainName 886 .append(mccmnc[1]) 887 .append(".mcc") 888 .append(mccmnc[0]) 889 .append(".pub.3gppnetwork.org"); 890 getIP(domainName.toString(), filter, validIpList, network); 891 domainName.setLength(0); 892 } 893 } else if (cellInfo instanceof CellInfoNr) { 894 CellIdentityNr nrCellId = (CellIdentityNr) cellInfo.getCellIdentity(); 895 String tacString = String.format("%06x", nrCellId.getTac()); 896 String[] tacSubString = new String[3]; 897 tacSubString[0] = tacString.substring(0, 2); 898 tacSubString[1] = tacString.substring(2, 4); 899 tacSubString[2] = tacString.substring(4); 900 901 plmnList = getPlmnList(); 902 for (String plmn : plmnList) { 903 String[] mccmnc = splitMccMnc(plmn); 904 /** 905 * 5GS Tracking Area Identity based ePDG FQDN format: 906 * tac-lb<TAC-low-byte>.tac-mb<TAC-middle-byte>.tac-hb<TAC-high-byte>. 907 * 5gstac.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 908 * 909 * <p>5GS Tracking Area Identity based Emergency ePDG FQDN format: 910 * tac-lb<TAC-low-byte>.tac-mb<TAC-middle-byte>.tac-hb<TAC-high-byte>. 911 * 5gstac.sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 912 */ 913 domainName 914 .append("tac-lb") 915 .append(tacSubString[2]) 916 .append(".tac-mb") 917 .append(tacSubString[1]) 918 .append(".tac-hb") 919 .append(tacSubString[0]); 920 if (isEmergency) { 921 domainName.append(".5gstac.sos.epdg.epc.mnc"); 922 } else { 923 domainName.append(".5gstac.epdg.epc.mnc"); 924 } 925 domainName 926 .append(mccmnc[1]) 927 .append(".mcc") 928 .append(mccmnc[0]) 929 .append(".pub.3gppnetwork.org"); 930 getIP(domainName.toString(), filter, validIpList, network); 931 domainName.setLength(0); 932 } 933 } else { 934 Log.d(TAG, "This cell doesn't contain LAC/TAC info"); 935 } 936 } 937 } 938 lacDomainNameResolution( int filter, List<InetAddress> validIpList, String lacString, boolean isEmergency, Network network)939 private void lacDomainNameResolution( 940 int filter, 941 List<InetAddress> validIpList, 942 String lacString, 943 boolean isEmergency, 944 Network network) { 945 String[] plmnList; 946 StringBuilder domainName = new StringBuilder(); 947 948 plmnList = getPlmnList(); 949 for (String plmn : plmnList) { 950 String[] mccmnc = splitMccMnc(plmn); 951 /** 952 * Location Area Identity based ePDG FQDN format: 953 * lac<LAC>.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 954 * 955 * <p>Location Area Identity based Emergency ePDG FQDN format: 956 * lac<LAC>.sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 957 */ 958 domainName.append("lac").append(lacString); 959 if (isEmergency) { 960 domainName.append(".sos.epdg.epc.mnc"); 961 } else { 962 domainName.append(".epdg.epc.mnc"); 963 } 964 domainName 965 .append(mccmnc[1]) 966 .append(".mcc") 967 .append(mccmnc[0]) 968 .append(".pub.3gppnetwork.org"); 969 970 getIP(domainName.toString(), filter, validIpList, network); 971 domainName.setLength(0); 972 } 973 } 974 resolutionMethodPco(int filter, @NonNull List<InetAddress> validIpList)975 private void resolutionMethodPco(int filter, @NonNull List<InetAddress> validIpList) { 976 Log.d(TAG, "PCO Method"); 977 978 int PCO_ID_IPV6 = 979 IwlanCarrierConfig.getConfigInt( 980 mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV6_INT); 981 int PCO_ID_IPV4 = 982 IwlanCarrierConfig.getConfigInt( 983 mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV4_INT); 984 985 switch (filter) { 986 case PROTO_FILTER_IPV4: 987 if (mV4PcoId != PCO_ID_IPV4) { 988 clearPcoData(); 989 } else { 990 getInetAddressWithPcoData(mV4PcoData, validIpList); 991 } 992 break; 993 case PROTO_FILTER_IPV6: 994 if (mV6PcoId != PCO_ID_IPV6) { 995 clearPcoData(); 996 } else { 997 getInetAddressWithPcoData(mV6PcoData, validIpList); 998 } 999 break; 1000 case PROTO_FILTER_IPV4V6: 1001 if ((mV4PcoId != PCO_ID_IPV4) || (mV6PcoId != PCO_ID_IPV6)) { 1002 clearPcoData(); 1003 } else { 1004 getInetAddressWithPcoData(mV4PcoData, validIpList); 1005 getInetAddressWithPcoData(mV6PcoData, validIpList); 1006 } 1007 break; 1008 default: 1009 Log.d(TAG, "Invalid ProtoFilter : " + filter); 1010 } 1011 } 1012 getInetAddressWithPcoData( List<byte[]> pcoData, @NonNull List<InetAddress> validIpList)1013 private void getInetAddressWithPcoData( 1014 List<byte[]> pcoData, @NonNull List<InetAddress> validIpList) { 1015 for (byte[] data : pcoData) { 1016 int ipAddressLen = 0; 1017 /* 1018 * The PCO container contents starts with the operator MCC and MNC of size 3 bytes 1019 * combined followed by one IPv6 or IPv4 address. 1020 * IPv6 address is encoded as a 128-bit address and 1021 * IPv4 address is encoded as 32-bit address. 1022 */ 1023 if (data.length > PCO_MCC_MNC_LEN) { 1024 ipAddressLen = data.length - PCO_MCC_MNC_LEN; 1025 } 1026 if ((ipAddressLen == PCO_IPV4_LEN) || (ipAddressLen == PCO_IPV6_LEN)) { 1027 byte[] ipAddressData = Arrays.copyOfRange(data, PCO_MCC_MNC_LEN, data.length); 1028 try { 1029 validIpList.add(InetAddress.getByAddress(ipAddressData)); 1030 } catch (UnknownHostException e) { 1031 Log.e( 1032 TAG, 1033 "Exception when querying IP address(" 1034 + Arrays.toString(ipAddressData) 1035 + "): " 1036 + e); 1037 } 1038 } else { 1039 Log.e(TAG, "Invalid PCO data:" + Arrays.toString(data)); 1040 } 1041 } 1042 } 1043 composeFqdnWithMccMnc(String mcc, String mnc, boolean isEmergency)1044 private String composeFqdnWithMccMnc(String mcc, String mnc, boolean isEmergency) { 1045 StringBuilder domainName = new StringBuilder(); 1046 1047 /* 1048 * Operator Identifier based ePDG FQDN format: 1049 * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 1050 * 1051 * Operator Identifier based Emergency ePDG FQDN format: 1052 * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org 1053 */ 1054 domainName.setLength(0); 1055 if (isEmergency) { 1056 domainName.append("sos."); 1057 } 1058 domainName 1059 .append("epdg.epc.mnc") 1060 .append(mnc) 1061 .append(".mcc") 1062 .append(mcc) 1063 .append(".pub.3gppnetwork.org"); 1064 1065 return domainName.toString(); 1066 } 1067 isRegisteredWith3GPP(TelephonyManager telephonyManager)1068 private boolean isRegisteredWith3GPP(TelephonyManager telephonyManager) { 1069 List<CellInfo> cellInfoList = telephonyManager.getAllCellInfo(); 1070 if (cellInfoList == null) { 1071 Log.e(TAG, "cellInfoList is NULL"); 1072 } else { 1073 for (CellInfo cellInfo : cellInfoList) { 1074 if (!cellInfo.isRegistered()) { 1075 continue; 1076 } 1077 if (cellInfo instanceof CellInfoGsm 1078 || cellInfo instanceof CellInfoTdscdma 1079 || cellInfo instanceof CellInfoWcdma 1080 || cellInfo instanceof CellInfoLte 1081 || cellInfo instanceof CellInfoNr) { 1082 return true; 1083 } 1084 } 1085 } 1086 return false; 1087 } 1088 processNaptrResponse( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network, boolean isRegisteredWith3GPP, List<NaptrTarget> naptrResponse, Set<String> plmnsFromCarrierConfig, String registeredhostName)1089 private void processNaptrResponse( 1090 int filter, 1091 List<InetAddress> validIpList, 1092 boolean isEmergency, 1093 Network network, 1094 boolean isRegisteredWith3GPP, 1095 List<NaptrTarget> naptrResponse, 1096 Set<String> plmnsFromCarrierConfig, 1097 String registeredhostName) { 1098 Set<String> resultSet = new LinkedHashSet<>(); 1099 1100 for (NaptrTarget target : naptrResponse) { 1101 Log.d(TAG, "NaptrTarget - name: " + target.mName); 1102 Log.d(TAG, "NaptrTarget - type: " + target.mType); 1103 if (target.mType == NaptrDnsResolver.TYPE_A) { 1104 resultSet.add(target.mName); 1105 } 1106 } 1107 1108 /* 1109 * As 3GPP TS 23.402 4.5.4.5 bullet 2a, 1110 * if the device registers via 3GPP and its PLMN info is in the NAPTR response, 1111 * try to connect ePDG with this PLMN info. 1112 */ 1113 if (isRegisteredWith3GPP) { 1114 if (resultSet.contains(registeredhostName)) { 1115 getIP(registeredhostName, filter, validIpList, network); 1116 resultSet.remove(registeredhostName); 1117 } 1118 } 1119 1120 /* 1121 * As 3GPP TS 23.402 4.5.4.5 bullet 2b 1122 * Check if there is any PLMN in both ePDG selection information and the DNS response 1123 */ 1124 for (String plmn : plmnsFromCarrierConfig) { 1125 String[] mccmnc = splitMccMnc(plmn); 1126 String carrierConfighostName = composeFqdnWithMccMnc(mccmnc[0], mccmnc[1], isEmergency); 1127 1128 if (resultSet.contains(carrierConfighostName)) { 1129 getIP(carrierConfighostName, filter, validIpList, network); 1130 resultSet.remove(carrierConfighostName); 1131 } 1132 } 1133 1134 /* 1135 * Do FQDN with the remaining PLMNs in the ResultSet 1136 */ 1137 for (String result : resultSet) { 1138 getIP(result, filter, validIpList, network); 1139 } 1140 } 1141 resolutionMethodVisitedCountry( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network)1142 private void resolutionMethodVisitedCountry( 1143 int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) { 1144 StringBuilder domainName = new StringBuilder(); 1145 1146 TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); 1147 telephonyManager = 1148 Objects.requireNonNull(telephonyManager) 1149 .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); 1150 1151 if (telephonyManager == null) { 1152 Log.e(TAG, "TelephonyManager is NULL"); 1153 return; 1154 } 1155 1156 final boolean isRegisteredWith3GPP = isRegisteredWith3GPP(telephonyManager); 1157 1158 // Get ePDG selection information from CarrierConfig 1159 final Set<String> plmnsFromCarrierConfig = 1160 new LinkedHashSet<>( 1161 Arrays.asList( 1162 IwlanCarrierConfig.getConfigStringArray( 1163 mContext, 1164 mSlotId, 1165 CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY))); 1166 1167 final String cellMcc = telephonyManager.getNetworkOperator().substring(0, 3); 1168 final String cellMnc = telephonyManager.getNetworkOperator().substring(3); 1169 final String plmnFromNetwork = cellMcc + "-" + cellMnc; 1170 final String registeredhostName = composeFqdnWithMccMnc(cellMcc, cellMnc, isEmergency); 1171 1172 /* 1173 * As TS 23 402 4.5.4.4 bullet 3a 1174 * If the UE determines to be located in a country other than its home country 1175 * If the UE is registered via 3GPP access to a PLMN and this PLMN matches an entry 1176 in the ePDG selection information, then the UE shall select an ePDG in this PLMN. 1177 */ 1178 if (isRegisteredWith3GPP) { 1179 if (plmnsFromCarrierConfig.contains(plmnFromNetwork)) { 1180 getIP(registeredhostName, filter, validIpList, network); 1181 } 1182 } 1183 1184 /* 1185 * Visited Country FQDN format: 1186 * epdg.epc.mcc<MCC>.visited-country.pub.3gppnetwork.org 1187 * 1188 * Visited Country Emergency ePDG FQDN format: 1189 * sos.epdg.epc.mcc<MCC>.visited-country.pub.3gppnetwork.org 1190 */ 1191 if (isEmergency) { 1192 domainName.append("sos."); 1193 } 1194 domainName 1195 .append("epdg.epc.mcc") 1196 .append(cellMcc) 1197 .append(".visited-country.pub.3gppnetwork.org"); 1198 1199 Log.d(TAG, "Visited Country FQDN with " + domainName); 1200 1201 CompletableFuture<List<NaptrTarget>> naptrDnsResult = new CompletableFuture<>(); 1202 DnsResolver.Callback<List<NaptrTarget>> naptrDnsCb = 1203 new DnsResolver.Callback<List<NaptrTarget>>() { 1204 @Override 1205 public void onAnswer(@NonNull final List<NaptrTarget> answer, final int rcode) { 1206 if (rcode == 0 && answer.size() != 0) { 1207 naptrDnsResult.complete(answer); 1208 } else { 1209 naptrDnsResult.completeExceptionally(new UnknownHostException()); 1210 } 1211 } 1212 1213 @Override 1214 public void onError(@Nullable final DnsException error) { 1215 naptrDnsResult.completeExceptionally(error); 1216 } 1217 }; 1218 NaptrDnsResolver.query(network, domainName.toString(), Runnable::run, null, naptrDnsCb); 1219 1220 try { 1221 final List<NaptrTarget> naptrResponse = 1222 naptrDnsResult.get(DNS_RESOLVER_TIMEOUT_DURATION_SEC, TimeUnit.SECONDS); 1223 // Check if there is any record in the NAPTR response 1224 if (naptrResponse != null && naptrResponse.size() > 0) { 1225 processNaptrResponse( 1226 filter, 1227 validIpList, 1228 isEmergency, 1229 network, 1230 isRegisteredWith3GPP, 1231 naptrResponse, 1232 plmnsFromCarrierConfig, 1233 registeredhostName); 1234 } 1235 } catch (ExecutionException e) { 1236 Log.e(TAG, "Cause of ExecutionException: ", e.getCause()); 1237 } catch (InterruptedException e) { 1238 Thread thread = Thread.currentThread(); 1239 if (Thread.interrupted()) { 1240 thread.interrupt(); 1241 } 1242 Log.e(TAG, "InterruptedException: ", e); 1243 } catch (TimeoutException e) { 1244 Log.e(TAG, "TimeoutException: ", e); 1245 } 1246 } 1247 1248 // Cancels duplicate prefetches if a prefetch is already running. Always schedules tunnel 1249 // bringup. trySubmitEpdgSelectionExecutor( Runnable runnable, boolean isPrefetch, boolean isEmergency)1250 protected void trySubmitEpdgSelectionExecutor( 1251 Runnable runnable, boolean isPrefetch, boolean isEmergency) { 1252 if (isEmergency) { 1253 if (isPrefetch) { 1254 if (mSosDnsPrefetchFuture == null || mSosDnsPrefetchFuture.isDone()) { 1255 mSosDnsPrefetchFuture = mSosEpdgSelectionExecutor.submit(runnable); 1256 } 1257 } else { 1258 mSosEpdgSelectionExecutor.execute(runnable); 1259 } 1260 } else { 1261 if (isPrefetch) { 1262 if (mDnsPrefetchFuture == null || mDnsPrefetchFuture.isDone()) { 1263 mDnsPrefetchFuture = mEpdgSelectionExecutor.submit(runnable); 1264 } 1265 } else { 1266 mEpdgSelectionExecutor.execute(runnable); 1267 } 1268 } 1269 } 1270 1271 /** 1272 * Asynchronously runs DNS resolution on a carrier-specific list of ePDG servers into IP 1273 * addresses, and passes them to the caller via the {@link EpdgSelectorCallback}. 1274 * 1275 * @param transactionId A unique ID passed in to match the response with the request. If this 1276 * value is 0, the caller is not interested in the result. 1277 * @param filter Allows the caller to filter for IPv4 or IPv6 servers, or both. 1278 * @param isRoaming Specifies whether the subscription is currently in roaming state. 1279 * @param isEmergency Specifies whether the ePDG server lookup is to make an emergency call. 1280 * @param network {@link Network} The server lookups will be performed over this Network. 1281 * @param selectorCallback {@link EpdgSelectorCallback} The result will be returned through this 1282 * callback. If null, the caller is not interested in the result. Typically, this means the 1283 * caller is performing DNS prefetch of the ePDG server addresses to warm the native 1284 * dnsresolver module's caches. 1285 * @return {link IwlanError} denoting the status of this operation. 1286 */ getValidatedServerList( int transactionId, @ProtoFilter int filter, @EpdgAddressOrder int order, boolean isRoaming, boolean isEmergency, @NonNull Network network, EpdgSelectorCallback selectorCallback)1287 public IwlanError getValidatedServerList( 1288 int transactionId, 1289 @ProtoFilter int filter, 1290 @EpdgAddressOrder int order, 1291 boolean isRoaming, 1292 boolean isEmergency, 1293 @NonNull Network network, 1294 EpdgSelectorCallback selectorCallback) { 1295 1296 final Runnable epdgSelectionRunnable = 1297 () -> { 1298 List<InetAddress> validIpList = new ArrayList<>(); 1299 Log.d( 1300 TAG, 1301 "Processing request with transactionId: " 1302 + transactionId 1303 + ", for slotID: " 1304 + mSlotId 1305 + ", isEmergency: " 1306 + isEmergency); 1307 1308 int[] addrResolutionMethods = 1309 IwlanCarrierConfig.getConfigIntArray( 1310 mContext, 1311 mSlotId, 1312 CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY); 1313 1314 final boolean isVisitedCountryMethodRequired = 1315 Arrays.stream(addrResolutionMethods) 1316 .anyMatch( 1317 i -> 1318 i 1319 == CarrierConfigManager.Iwlan 1320 .EPDG_ADDRESS_VISITED_COUNTRY); 1321 1322 // In the visited country 1323 if (isRoaming && !inSameCountry() && isVisitedCountryMethodRequired) { 1324 resolutionMethodVisitedCountry(filter, validIpList, isEmergency, network); 1325 } 1326 1327 Map<String, List<InetAddress>> plmnDomainNamesToIpAddress = null; 1328 for (int addrResolutionMethod : addrResolutionMethods) { 1329 switch (addrResolutionMethod) { 1330 case CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC: 1331 resolutionMethodStatic(filter, validIpList, network); 1332 break; 1333 1334 case CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN: 1335 plmnDomainNamesToIpAddress = 1336 resolutionMethodPlmn( 1337 filter, validIpList, isEmergency, network); 1338 break; 1339 1340 case CarrierConfigManager.Iwlan.EPDG_ADDRESS_PCO: 1341 resolutionMethodPco(filter, validIpList); 1342 break; 1343 1344 case CarrierConfigManager.Iwlan.EPDG_ADDRESS_CELLULAR_LOC: 1345 resolutionMethodCellularLoc( 1346 filter, validIpList, isEmergency, network); 1347 break; 1348 1349 default: 1350 Log.d( 1351 TAG, 1352 "Incorrect address resolution method " 1353 + addrResolutionMethod); 1354 } 1355 } 1356 1357 if (selectorCallback != null) { 1358 if (mErrorPolicyManager.getMostRecentDataFailCause() 1359 == DataFailCause.IWLAN_CONGESTION) { 1360 Objects.requireNonNull(plmnDomainNamesToIpAddress) 1361 .values() 1362 .removeIf(List::isEmpty); 1363 1364 int numFqdns = plmnDomainNamesToIpAddress.size(); 1365 int index = mErrorPolicyManager.getCurrentFqdnIndex(numFqdns); 1366 if (index >= 0 && index < numFqdns) { 1367 Object[] keys = plmnDomainNamesToIpAddress.keySet().toArray(); 1368 validIpList = plmnDomainNamesToIpAddress.get((String) keys[index]); 1369 } else { 1370 Log.w( 1371 TAG, 1372 "CONGESTION error handling- invalid index: " 1373 + index 1374 + " number of PLMN FQDNs: " 1375 + numFqdns); 1376 } 1377 } 1378 1379 if (!validIpList.isEmpty()) { 1380 validIpList = removeDuplicateIp(validIpList); 1381 validIpList = filterExcludedAddresses(validIpList); 1382 validIpList = prioritizeIp(validIpList, order); 1383 selectorCallback.onServerListChanged(transactionId, validIpList); 1384 } else { 1385 selectorCallback.onError( 1386 transactionId, 1387 new IwlanError( 1388 IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED)); 1389 } 1390 } 1391 }; 1392 1393 boolean isPrefetch = (selectorCallback == null); 1394 trySubmitEpdgSelectionExecutor(epdgSelectionRunnable, isPrefetch, isEmergency); 1395 1396 return new IwlanError(IwlanError.NO_ERROR); 1397 } 1398 1399 /** 1400 * Validates a PLMN (Public Land Mobile Network) identifier string. 1401 * 1402 * @param plmn The PLMN identifier string to validate. 1403 * @return True if the PLMN identifier is valid, false otherwise. 1404 */ isValidPlmn(String plmn)1405 private static boolean isValidPlmn(String plmn) { 1406 return plmn != null && PLMN_PATTERN.matcher(plmn).matches(); 1407 } 1408 } 1409