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