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