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