• 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.CompletableFuture;
58 import java.util.concurrent.ConcurrentHashMap;
59 import java.util.concurrent.ExecutionException;
60 import java.util.concurrent.TimeoutException;
61 import java.util.concurrent.TimeUnit;
62 
63 public class EpdgSelector {
64     private static final String TAG = "EpdgSelector";
65     private Context mContext;
66     private int mSlotId;
67     private static ConcurrentHashMap<Integer, EpdgSelector> mSelectorInstances =
68             new ConcurrentHashMap<>();
69     private int mV4PcoId = -1;
70     private int mV6PcoId = -1;
71     private byte[] mV4PcoData = null;
72     private byte[] mV6PcoData = null;
73     @NonNull private ErrorPolicyManager mErrorPolicyManager;
74 
75     private static final long DNS_RESOLVER_TIMEOUT_DURATION_SEC = 5L;
76 
77     final Comparator<InetAddress> inetAddressComparator =
78             new Comparator<InetAddress>() {
79                 @Override
80                 public int compare(InetAddress ip1, InetAddress ip2) {
81                     if ((ip1 instanceof Inet4Address) && (ip2 instanceof Inet6Address)) {
82                         return -1;
83                     } else if ((ip1 instanceof Inet6Address) && (ip2 instanceof Inet4Address)) {
84                         return 1;
85                     } else {
86                         return 0;
87                     }
88                 }
89             };
90 
91     public static final int PROTO_FILTER_IPV4 = 0;
92     public static final int PROTO_FILTER_IPV6 = 1;
93     public static final int PROTO_FILTER_IPV4V6 = 2;
94 
95     @IntDef({PROTO_FILTER_IPV4, PROTO_FILTER_IPV6, PROTO_FILTER_IPV4V6})
96     @interface ProtoFilter {}
97 
98     public interface EpdgSelectorCallback {
99         /*gives priority ordered list of addresses*/
onServerListChanged(int transactionId, ArrayList<InetAddress> validIPList)100         void onServerListChanged(int transactionId, ArrayList<InetAddress> validIPList);
101 
onError(int transactionId, IwlanError error)102         void onError(int transactionId, IwlanError error);
103     }
104 
105     @VisibleForTesting
EpdgSelector(Context context, int slotId)106     EpdgSelector(Context context, int slotId) {
107         mContext = context;
108         mSlotId = slotId;
109 
110         mErrorPolicyManager = ErrorPolicyManager.getInstance(mContext, mSlotId);
111     }
112 
getSelectorInstance(Context context, int slotId)113     public static EpdgSelector getSelectorInstance(Context context, int slotId) {
114         mSelectorInstances.computeIfAbsent(slotId, k -> new EpdgSelector(context, slotId));
115         return mSelectorInstances.get(slotId);
116     }
117 
setPcoData(int pcoId, byte[] pcoData)118     public boolean setPcoData(int pcoId, byte[] pcoData) {
119         Log.d(TAG, "onReceive PcoId:" + String.format("0x%04x", pcoId) + " PcoData:" + pcoData);
120 
121         int PCO_ID_IPV6 =
122                 IwlanHelper.getConfig(
123                         CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV6_INT, mContext, mSlotId);
124         int PCO_ID_IPV4 =
125                 IwlanHelper.getConfig(
126                         CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV4_INT, mContext, mSlotId);
127 
128         Log.d(
129                 TAG,
130                 "PCO_ID_IPV6:"
131                         + String.format("0x%04x", PCO_ID_IPV6)
132                         + " PCO_ID_IPV4:"
133                         + String.format("0x%04x", PCO_ID_IPV4));
134 
135         if (pcoId == PCO_ID_IPV4) {
136             mV4PcoId = pcoId;
137             mV4PcoData = pcoData;
138             return true;
139         } else if (pcoId == PCO_ID_IPV6) {
140             mV6PcoId = pcoId;
141             mV6PcoData = pcoData;
142             return true;
143         }
144 
145         return false;
146     }
147 
clearPcoData()148     public void clearPcoData() {
149         Log.d(TAG, "Clear PCO data");
150         mV4PcoId = -1;
151         mV6PcoId = -1;
152         mV4PcoData = null;
153         mV6PcoData = null;
154     }
155 
getIP( String domainName, int filter, List<InetAddress> validIpList, Network network)156     private Map.Entry<String, List<InetAddress>> getIP(
157             String domainName, int filter, List<InetAddress> validIpList, Network network) {
158         List<InetAddress> ipList = new ArrayList<InetAddress>();
159 
160         // Get All IP for each domain name
161         Log.d(TAG, "Input domainName : " + domainName);
162 
163         if (InetAddresses.isNumericAddress(domainName)) {
164             try {
165                 Log.d(TAG, domainName + " is a numeric ip address");
166                 ipList.add(InetAddress.getByName(domainName));
167             } catch (UnknownHostException e) {
168                 Log.e(TAG, "Exception when resolving domainName : " + domainName + ".", e);
169             }
170         } else {
171             try {
172                 CompletableFuture<List<InetAddress>> result = new CompletableFuture();
173                 final DnsResolver.Callback<List<InetAddress>> cb =
174                         new DnsResolver.Callback<List<InetAddress>>() {
175                             @Override
176                             public void onAnswer(
177                                     @NonNull final List<InetAddress> answer, final int rcode) {
178                                 if (rcode != 0) {
179                                     Log.e(TAG, "DnsResolver Response Code = " + rcode);
180                                 }
181                                 result.complete(answer);
182                             }
183 
184                             @Override
185                             public void onError(@Nullable final DnsResolver.DnsException error) {
186                                 Log.e(TAG, "Resolve DNS with error : " + error);
187                                 result.completeExceptionally(error);
188                             }
189                         };
190                 DnsResolver.getInstance()
191                         .query(network, domainName, DnsResolver.FLAG_EMPTY, r -> r.run(), null, cb);
192                 ipList =
193                         new ArrayList<>(
194                                 result.get(DNS_RESOLVER_TIMEOUT_DURATION_SEC, TimeUnit.SECONDS));
195             } catch (ExecutionException e) {
196                 Log.e(TAG, "Cause of ExecutionException: ", e.getCause());
197             } catch (InterruptedException e) {
198                 if (Thread.currentThread().interrupted()) {
199                     Thread.currentThread().interrupt();
200                 }
201                 Log.e(TAG, "InterruptedException: ", e);
202             } catch (TimeoutException e) {
203                 Log.e(TAG, "TimeoutException: ", e);
204             }
205         }
206 
207         List<InetAddress> filteredIpList = new ArrayList<>();
208         // Filter the IP list by input ProtoFilter
209         for (InetAddress ipAddress : ipList) {
210             switch (filter) {
211                 case PROTO_FILTER_IPV4:
212                     if (ipAddress instanceof Inet4Address) {
213                         filteredIpList.add(ipAddress);
214                     }
215                     break;
216                 case PROTO_FILTER_IPV6:
217                     if (!IwlanHelper.isIpv4EmbeddedIpv6Address(ipAddress)) {
218                         filteredIpList.add(ipAddress);
219                     }
220                     break;
221                 case PROTO_FILTER_IPV4V6:
222                     filteredIpList.add(ipAddress);
223                     break;
224                 default:
225                     Log.d(TAG, "Invalid ProtoFilter : " + filter);
226             }
227         }
228         validIpList.addAll(filteredIpList);
229 
230         return Map.entry(domainName, filteredIpList);
231     }
232 
getPlmnList()233     private String[] getPlmnList() {
234         List<String> plmnsFromSubInfo = new ArrayList<>();
235 
236         List<String> plmnsFromCarrierConfig =
237                 new ArrayList<>(
238                         Arrays.asList(
239                                 IwlanHelper.getConfig(
240                                         CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY,
241                                         mContext,
242                                         mSlotId)));
243         Log.d(TAG, "plmnsFromCarrierConfig:" + plmnsFromCarrierConfig);
244 
245         // Get Ehplmns & mccmnc from SubscriptionManager
246         SubscriptionManager subscriptionManager =
247                 mContext.getSystemService(SubscriptionManager.class);
248         if (subscriptionManager == null) {
249             Log.e(TAG, "SubscriptionManager is NULL");
250             return plmnsFromCarrierConfig.toArray(new String[plmnsFromCarrierConfig.size()]);
251         }
252 
253         SubscriptionInfo subInfo =
254                 subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(mSlotId);
255         if (subInfo == null) {
256             Log.e(TAG, "SubscriptionInfo is NULL");
257             return plmnsFromCarrierConfig.toArray(new String[plmnsFromCarrierConfig.size()]);
258         }
259 
260         // There are three sources of plmns - sim plmn, plmn list from carrier config and
261         // Ehplmn list from subscription info.
262         // The plmns are prioritized as follows:
263         // 1. Sim plmn
264         // 2. Plmns common to both lists.
265         // 3. Remaining plmns in the lists.
266         List<String> combinedList = new ArrayList<>();
267         // Get MCCMNC from IMSI
268         String plmnFromImsi =
269                 new StringBuilder()
270                         .append(subInfo.getMccString())
271                         .append("-")
272                         .append(subInfo.getMncString())
273                         .toString();
274         combinedList.add(plmnFromImsi);
275 
276         // Get Ehplmns from TelephonyManager
277         for (String ehplmn : getEhplmns()) {
278             if (ehplmn.length() == 5 || ehplmn.length() == 6) {
279                 StringBuilder str = new StringBuilder(ehplmn);
280                 str.insert(3, "-");
281                 plmnsFromSubInfo.add(str.toString());
282             }
283         }
284 
285         Log.d(TAG, "plmnsFromSubInfo:" + plmnsFromSubInfo);
286 
287         // To avoid double adding plmn from imsi
288         plmnsFromCarrierConfig.removeIf(i -> i.equals(plmnFromImsi));
289         plmnsFromSubInfo.removeIf(i -> i.equals(plmnFromImsi));
290 
291         for (Iterator<String> iterator = plmnsFromCarrierConfig.iterator(); iterator.hasNext(); ) {
292             String plmn = iterator.next();
293             if (plmnsFromSubInfo.contains(plmn)) {
294                 combinedList.add(plmn);
295                 plmnsFromSubInfo.remove(plmn);
296                 iterator.remove();
297             }
298         }
299 
300         combinedList.addAll(plmnsFromSubInfo);
301         combinedList.addAll(plmnsFromCarrierConfig);
302 
303         Log.d(TAG, "Final plmn list:" + combinedList);
304         return combinedList.toArray(new String[combinedList.size()]);
305     }
306 
removeDuplicateIp(List<InetAddress> validIpList)307     private ArrayList<InetAddress> removeDuplicateIp(List<InetAddress> validIpList) {
308         ArrayList<InetAddress> resultIpList = new ArrayList<InetAddress>();
309 
310         for (Iterator<InetAddress> iterator = validIpList.iterator(); iterator.hasNext(); ) {
311             InetAddress validIp = iterator.next();
312 
313             if (!resultIpList.contains(validIp)) {
314                 resultIpList.add(validIp);
315             }
316         }
317 
318         return resultIpList;
319     }
320 
splitMccMnc(String plmn)321     private String[] splitMccMnc(String plmn) {
322         String[] mccmnc = plmn.split("-");
323         mccmnc[1] = String.format("%03d", Integer.parseInt(mccmnc[1]));
324         return mccmnc;
325     }
326 
getEhplmns()327     private List<String> getEhplmns() {
328         TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
329         mTelephonyManager =
330                 mTelephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
331 
332         if (mTelephonyManager == null) {
333             Log.e(TAG, "TelephonyManager is NULL");
334             return new ArrayList<String>();
335         } else {
336             return mTelephonyManager.getEquivalentHomePlmns();
337         }
338     }
339 
resolutionMethodStatic( int filter, List<InetAddress> validIpList, boolean isRoaming, Network network)340     private void resolutionMethodStatic(
341             int filter, List<InetAddress> validIpList, boolean isRoaming, Network network) {
342         String[] domainNames = null;
343 
344         Log.d(TAG, "STATIC Method");
345 
346         // Get the static domain names from carrier config
347         // Config obtained in form of a list of domain names separated by
348         // a delimeter is only used for testing purpose.
349         if (!inSameCountry()) {
350             domainNames =
351                     getDomainNames(
352                             CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_ROAMING_STRING);
353         }
354         if (domainNames == null
355                 && (domainNames =
356                                 getDomainNames(
357                                         CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING))
358                         == null) {
359             Log.d(TAG, "Static address string is null");
360             return;
361         }
362 
363         Log.d(TAG, "Static Domain Names: " + Arrays.toString(domainNames));
364         for (String domainName : domainNames) {
365             getIP(domainName, filter, validIpList, network);
366         }
367     }
368 
getDomainNames(String key)369     private String[] getDomainNames(String key) {
370         String configValue = (String) IwlanHelper.getConfig(key, mContext, mSlotId);
371         if (configValue == null || configValue.isEmpty()) {
372             Log.d(TAG, key + " string is null");
373             return null;
374         }
375         return configValue.split(",");
376     }
377 
inSameCountry()378     private boolean inSameCountry() {
379         boolean inSameCountry = true;
380 
381         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
382         tm = tm.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
383 
384         if (tm != null) {
385             String simCountry = tm.getSimCountryIso();
386             String currentCountry = IwlanHelper.getLastKnownCountryCode(mContext);
387             if (!TextUtils.isEmpty(simCountry) && !TextUtils.isEmpty(currentCountry)) {
388                 Log.d(TAG, "simCountry = " + simCountry + ", currentCountry = " + currentCountry);
389                 inSameCountry = simCountry.equalsIgnoreCase(currentCountry);
390             }
391         }
392 
393         return inSameCountry;
394     }
395 
resolutionMethodPlmn( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network)396     private Map<String, List<InetAddress>> resolutionMethodPlmn(
397             int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) {
398         String[] plmnList;
399         StringBuilder domainName = new StringBuilder();
400         Map<String, List<InetAddress>> domainNameToIpAddr = new LinkedHashMap<>();
401 
402         Log.d(TAG, "PLMN Method");
403 
404         plmnList = getPlmnList();
405         for (String plmn : plmnList) {
406             String[] mccmnc = splitMccMnc(plmn);
407             /*
408              * Operator Identifier based ePDG FQDN format:
409              * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
410              *
411              * Operator Identifier based Emergency ePDG FQDN format:
412              * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
413              */
414             if (isEmergency) {
415                 domainName = new StringBuilder();
416                 domainName
417                         .append("sos.")
418                         .append("epdg.epc.mnc")
419                         .append(mccmnc[1])
420                         .append(".mcc")
421                         .append(mccmnc[0])
422                         .append(".pub.3gppnetwork.org");
423                 getIP(domainName.toString(), filter, validIpList, network);
424                 domainName.setLength(0);
425             }
426             // For emergency PDN setup, still adding FQDN without "sos" header as second priority
427             // because some operator doesn't support hostname with "sos" prefix.
428             domainName
429                     .append("epdg.epc.mnc")
430                     .append(mccmnc[1])
431                     .append(".mcc")
432                     .append(mccmnc[0])
433                     .append(".pub.3gppnetwork.org");
434             Map.Entry<String, List<InetAddress>> entry =
435                     getIP(domainName.toString(), filter, validIpList, network);
436             domainNameToIpAddr.put(entry.getKey(), entry.getValue());
437             domainName.setLength(0);
438         }
439         return domainNameToIpAddr;
440     }
441 
resolutionMethodCellularLoc( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network)442     private void resolutionMethodCellularLoc(
443             int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) {
444         String[] plmnList;
445         StringBuilder domainName = new StringBuilder();
446 
447         Log.d(TAG, "CELLULAR_LOC Method");
448 
449         TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
450         mTelephonyManager =
451                 mTelephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
452 
453         if (mTelephonyManager == null) {
454             Log.e(TAG, "TelephonyManager is NULL");
455             return;
456         }
457 
458         List<CellInfo> cellInfoList = mTelephonyManager.getAllCellInfo();
459         if (cellInfoList == null) {
460             Log.e(TAG, "cellInfoList is NULL");
461             return;
462         }
463 
464         for (CellInfo cellInfo : cellInfoList) {
465             if (!cellInfo.isRegistered()) {
466                 continue;
467             }
468 
469             if (cellInfo instanceof CellInfoGsm) {
470                 CellIdentityGsm gsmCellId = ((CellInfoGsm) cellInfo).getCellIdentity();
471                 String lacString = String.format("%04x", gsmCellId.getLac());
472 
473                 lacDomainNameResolution(filter, validIpList, lacString, isEmergency, network);
474             } else if (cellInfo instanceof CellInfoWcdma) {
475                 CellIdentityWcdma wcdmaCellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
476                 String lacString = String.format("%04x", wcdmaCellId.getLac());
477 
478                 lacDomainNameResolution(filter, validIpList, lacString, isEmergency, network);
479             } else if (cellInfo instanceof CellInfoLte) {
480                 CellIdentityLte lteCellId = ((CellInfoLte) cellInfo).getCellIdentity();
481                 String tacString = String.format("%04x", lteCellId.getTac());
482                 String[] tacSubString = new String[2];
483                 tacSubString[0] = tacString.substring(0, 2);
484                 tacSubString[1] = tacString.substring(2);
485 
486                 plmnList = getPlmnList();
487                 for (String plmn : plmnList) {
488                     String[] mccmnc = splitMccMnc(plmn);
489                     /**
490                      * Tracking Area Identity based ePDG FQDN format:
491                      * tac-lb<TAC-low-byte>.tac-hb<TAC-high-byte>.tac.
492                      * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
493                      *
494                      * <p>Tracking Area Identity based Emergency ePDG FQDN format:
495                      * tac-lb<TAC-low-byte>.tac-hb<TAC-highbyte>.tac.
496                      * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org"
497                      */
498                     domainName
499                             .append("tac-lb")
500                             .append(tacSubString[1])
501                             .append(".tac-hb")
502                             .append(tacSubString[0]);
503                     if (isEmergency) {
504                         domainName.append(".tac.sos.epdg.epc.mnc");
505                     } else {
506                         domainName.append(".tac.epdg.epc.mnc");
507                     }
508                     domainName
509                             .append(mccmnc[1])
510                             .append(".mcc")
511                             .append(mccmnc[0])
512                             .append(".pub.3gppnetwork.org");
513                     getIP(domainName.toString(), filter, validIpList, network);
514                     domainName.setLength(0);
515                 }
516             } else if (cellInfo instanceof CellInfoNr) {
517                 CellIdentityNr nrCellId =
518                         (CellIdentityNr) ((CellInfoNr) cellInfo).getCellIdentity();
519                 String tacString = String.format("%06x", nrCellId.getTac());
520                 String[] tacSubString = new String[3];
521                 tacSubString[0] = tacString.substring(0, 2);
522                 tacSubString[1] = tacString.substring(2, 4);
523                 tacSubString[2] = tacString.substring(4);
524 
525                 plmnList = getPlmnList();
526                 for (String plmn : plmnList) {
527                     String[] mccmnc = splitMccMnc(plmn);
528                     /**
529                      * 5GS Tracking Area Identity based ePDG FQDN format:
530                      * tac-lb<TAC-low-byte>.tac-mb<TAC-middle-byte>.tac-hb<TAC-high-byte>.
531                      * 5gstac.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
532                      *
533                      * <p>5GS Tracking Area Identity based Emergency ePDG FQDN format:
534                      * tac-lb<TAC-low-byte>.tac-mb<TAC-middle-byte>.tac-hb<TAC-high-byte>.
535                      * 5gstac.sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
536                      */
537                     domainName
538                             .append("tac-lb")
539                             .append(tacSubString[2])
540                             .append(".tac-mb")
541                             .append(tacSubString[1])
542                             .append(".tac-hb")
543                             .append(tacSubString[0]);
544                     if (isEmergency) {
545                         domainName.append(".5gstac.sos.epdg.epc.mnc");
546                     } else {
547                         domainName.append(".5gstac.epdg.epc.mnc");
548                     }
549                     domainName
550                             .append(mccmnc[1])
551                             .append(".mcc")
552                             .append(mccmnc[0])
553                             .append(".pub.3gppnetwork.org");
554                     getIP(domainName.toString(), filter, validIpList, network);
555                     domainName.setLength(0);
556                 }
557             } else {
558                 Log.d(TAG, "This cell doesn't contain LAC/TAC info");
559             }
560         }
561     }
562 
lacDomainNameResolution( int filter, List<InetAddress> validIpList, String lacString, boolean isEmergency, Network network)563     private void lacDomainNameResolution(
564             int filter,
565             List<InetAddress> validIpList,
566             String lacString,
567             boolean isEmergency,
568             Network network) {
569         String[] plmnList;
570         StringBuilder domainName = new StringBuilder();
571 
572         plmnList = getPlmnList();
573         for (String plmn : plmnList) {
574             String[] mccmnc = splitMccMnc(plmn);
575             /**
576              * Location Area Identity based ePDG FQDN format:
577              * lac<LAC>.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
578              *
579              * <p>Location Area Identity based Emergency ePDG FQDN format:
580              * lac<LAC>.sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
581              */
582             domainName.append("lac").append(lacString);
583             if (isEmergency) {
584                 domainName.append(".sos.epdg.epc.mnc");
585             } else {
586                 domainName.append(".epdg.epc.mnc");
587             }
588             domainName
589                     .append(mccmnc[1])
590                     .append(".mcc")
591                     .append(mccmnc[0])
592                     .append(".pub.3gppnetwork.org");
593 
594             getIP(domainName.toString(), filter, validIpList, network);
595             domainName.setLength(0);
596         }
597     }
598 
resolutionMethodPco(int filter, List<InetAddress> validIpList)599     private void resolutionMethodPco(int filter, List<InetAddress> validIpList) {
600         Log.d(TAG, "PCO Method");
601 
602         int PCO_ID_IPV6 =
603                 IwlanHelper.getConfig(
604                         CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV6_INT, mContext, mSlotId);
605         int PCO_ID_IPV4 =
606                 IwlanHelper.getConfig(
607                         CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV4_INT, mContext, mSlotId);
608 
609         switch (filter) {
610             case PROTO_FILTER_IPV4:
611                 if (mV4PcoId != PCO_ID_IPV4) {
612                     clearPcoData();
613                 } else {
614                     getInetAddressWithPcoData(mV4PcoData, validIpList);
615                 }
616                 break;
617             case PROTO_FILTER_IPV6:
618                 if (mV6PcoId != PCO_ID_IPV6) {
619                     clearPcoData();
620                 } else {
621                     getInetAddressWithPcoData(mV6PcoData, validIpList);
622                 }
623                 break;
624             case PROTO_FILTER_IPV4V6:
625                 if ((mV4PcoId != PCO_ID_IPV4) || (mV6PcoId != PCO_ID_IPV6)) {
626                     clearPcoData();
627                 } else {
628                     getInetAddressWithPcoData(mV4PcoData, validIpList);
629                     getInetAddressWithPcoData(mV6PcoData, validIpList);
630                 }
631                 break;
632             default:
633                 Log.d(TAG, "Invalid ProtoFilter : " + filter);
634         }
635     }
636 
getInetAddressWithPcoData(byte[] pcoData, List<InetAddress> validIpList)637     private void getInetAddressWithPcoData(byte[] pcoData, List<InetAddress> validIpList) {
638         InetAddress ipAddress;
639         if (pcoData != null && pcoData.length > 0) {
640             try {
641                 ipAddress = InetAddress.getByAddress(pcoData);
642                 validIpList.add(ipAddress);
643             } catch (Exception e) {
644                 Log.e(TAG, "Exception when querying IP address : " + e);
645             }
646         } else {
647             Log.d(TAG, "Empty PCO data");
648         }
649     }
650 
composeFqdnWithMccMnc(String mcc, String mnc, boolean isEmergency)651     private String composeFqdnWithMccMnc(String mcc, String mnc, boolean isEmergency) {
652         StringBuilder domainName = new StringBuilder();
653 
654         /*
655          * Operator Identifier based ePDG FQDN format:
656          * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
657          *
658          * Operator Identifier based Emergency ePDG FQDN format:
659          * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
660          */
661         domainName.setLength(0);
662         if (isEmergency) {
663             domainName.append("sos.");
664         }
665         domainName
666                 .append("epdg.epc.mnc")
667                 .append(mnc)
668                 .append(".mcc")
669                 .append(mcc)
670                 .append(".pub.3gppnetwork.org");
671 
672         return domainName.toString();
673     }
674 
isRegisteredWith3GPP(TelephonyManager telephonyManager)675     private boolean isRegisteredWith3GPP(TelephonyManager telephonyManager) {
676         List<CellInfo> cellInfoList = telephonyManager.getAllCellInfo();
677         if (cellInfoList == null) {
678             Log.e(TAG, "cellInfoList is NULL");
679         } else {
680             for (CellInfo cellInfo : cellInfoList) {
681                 if (!cellInfo.isRegistered()) {
682                     continue;
683                 }
684                 if (cellInfo instanceof CellInfoGsm
685                         || cellInfo instanceof CellInfoTdscdma
686                         || cellInfo instanceof CellInfoWcdma
687                         || cellInfo instanceof CellInfoLte
688                         || cellInfo instanceof CellInfoNr) {
689                     return true;
690                 }
691             }
692         }
693         return false;
694     }
695 
processNaptrResponse( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network, boolean isRegisteredWith3GPP, List<NaptrTarget> naptrResponse, Set<String> plmnsFromCarrierConfig, String registeredhostName)696     private void processNaptrResponse(
697             int filter,
698             List<InetAddress> validIpList,
699             boolean isEmergency,
700             Network network,
701             boolean isRegisteredWith3GPP,
702             List<NaptrTarget> naptrResponse,
703             Set<String> plmnsFromCarrierConfig,
704             String registeredhostName) {
705         Set<String> resultSet = new LinkedHashSet<>();
706 
707         for (NaptrTarget target : naptrResponse) {
708             Log.d(TAG, "NaptrTarget - name: " + target.mName);
709             Log.d(TAG, "NaptrTarget - type: " + target.mType);
710             if (target.mType == NaptrDnsResolver.TYPE_A) {
711                 resultSet.add(target.mName);
712             }
713         }
714 
715         /*
716          * As 3GPP TS 23.402 4.5.4.5 bullet 2a,
717          * if the device registers via 3GPP and its PLMN info is in the NAPTR response,
718          * try to connect ePDG with this PLMN info.
719          */
720         if (isRegisteredWith3GPP) {
721             if (resultSet.contains(registeredhostName)) {
722                 getIP(registeredhostName, filter, validIpList, network);
723                 resultSet.remove(registeredhostName);
724             }
725         }
726 
727         /*
728          * As 3GPP TS 23.402 4.5.4.5 bullet 2b
729          * Check if there is any PLMN in both ePDG selection information and the DNS response
730          */
731         for (String plmn : plmnsFromCarrierConfig) {
732             String[] mccmnc = splitMccMnc(plmn);
733             String carrierConfighostName = composeFqdnWithMccMnc(mccmnc[0], mccmnc[1], isEmergency);
734 
735             if (resultSet.contains(carrierConfighostName)) {
736                 getIP(carrierConfighostName, filter, validIpList, network);
737                 resultSet.remove(carrierConfighostName);
738             }
739         }
740 
741         /*
742          * Do FQDN with the remaining PLMNs in the ResultSet
743          */
744         for (String result : resultSet) {
745             getIP(result, filter, validIpList, network);
746         }
747     }
748 
resolutionMethodVisitedCountry( int filter, List<InetAddress> validIpList, boolean isEmergency, Network network)749     private void resolutionMethodVisitedCountry(
750             int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) {
751         StringBuilder domainName = new StringBuilder();
752 
753         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
754         telephonyManager =
755                 telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
756 
757         if (telephonyManager == null) {
758             Log.e(TAG, "TelephonyManager is NULL");
759             return;
760         }
761 
762         final boolean isRegisteredWith3GPP = isRegisteredWith3GPP(telephonyManager);
763 
764         // Get ePDG selection information from CarrierConfig
765         final Set<String> plmnsFromCarrierConfig =
766                 new LinkedHashSet<>(
767                         Arrays.asList(
768                                 IwlanHelper.getConfig(
769                                         CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY,
770                                         mContext,
771                                         mSlotId)));
772 
773         final String cellMcc = telephonyManager.getNetworkOperator().substring(0, 3);
774         final String cellMnc = telephonyManager.getNetworkOperator().substring(3);
775         final String plmnFromNetwork =
776                 new StringBuilder().append(cellMcc).append("-").append(cellMnc).toString();
777         final String registeredhostName = composeFqdnWithMccMnc(cellMcc, cellMnc, isEmergency);
778 
779         /*
780         * As TS 23 402 4.5.4.4 bullet 3a
781         * If the UE determines to be located in a country other than its home country
782         * If the UE is registered via 3GPP access to a PLMN and this PLMN matches an entry
783           in the ePDG selection information, then the UE shall select an ePDG in this PLMN.
784         */
785         if (isRegisteredWith3GPP) {
786             if (plmnsFromCarrierConfig.contains(plmnFromNetwork)) {
787                 getIP(registeredhostName, filter, validIpList, network);
788             }
789         }
790 
791         /*
792          * Visited Country FQDN format:
793          * epdg.epc.mcc<MCC>.visited-country.pub.3gppnetwork.org
794          *
795          * Visited Country Emergency ePDG FQDN format:
796          * sos.epdg.epc.mcc<MCC>.visited-country.pub.3gppnetwork.org
797          */
798         if (isEmergency) {
799             domainName.append("sos.");
800         }
801         domainName
802                 .append("epdg.epc.mcc")
803                 .append(cellMcc)
804                 .append(".visited-country.pub.3gppnetwork.org");
805 
806         Log.d(TAG, "Visited Country FQDN with " + domainName.toString());
807 
808         CompletableFuture<List<NaptrTarget>> naptrDnsResult = new CompletableFuture<>();
809         DnsResolver.Callback<List<NaptrTarget>> naptrDnsCb =
810                 new DnsResolver.Callback<List<NaptrTarget>>() {
811                     @Override
812                     public void onAnswer(@NonNull final List<NaptrTarget> answer, final int rcode) {
813                         if (rcode == 0 && answer.size() != 0) {
814                             naptrDnsResult.complete(answer);
815                         } else {
816                             naptrDnsResult.completeExceptionally(new UnknownHostException());
817                         }
818                     }
819 
820                     @Override
821                     public void onError(@Nullable final DnsException error) {
822                         naptrDnsResult.completeExceptionally(error);
823                     }
824                 };
825         NaptrDnsResolver.query(network, domainName.toString(), r -> r.run(), null, naptrDnsCb);
826 
827         try {
828             final List<NaptrTarget> naptrResponse =
829                     naptrDnsResult.get(DNS_RESOLVER_TIMEOUT_DURATION_SEC, TimeUnit.SECONDS);
830             // Check if there is any record in the NAPTR response
831             if (naptrResponse != null && naptrResponse.size() > 0) {
832                 processNaptrResponse(
833                         filter,
834                         validIpList,
835                         isEmergency,
836                         network,
837                         isRegisteredWith3GPP,
838                         naptrResponse,
839                         plmnsFromCarrierConfig,
840                         registeredhostName);
841             }
842         } catch (ExecutionException e) {
843             Log.e(TAG, "Cause of ExecutionException: ", e.getCause());
844         } catch (InterruptedException e) {
845             if (Thread.currentThread().interrupted()) {
846                 Thread.currentThread().interrupt();
847             }
848             Log.e(TAG, "InterruptedException: ", e);
849         } catch (TimeoutException e) {
850             Log.e(TAG, "TimeoutException: ", e);
851         }
852     }
853 
getValidatedServerList( int transactionId, @ProtoFilter int filter, boolean isRoaming, boolean isEmergency, @NonNull Network network, EpdgSelectorCallback selectorCallback)854     public IwlanError getValidatedServerList(
855             int transactionId,
856             @ProtoFilter int filter,
857             boolean isRoaming,
858             boolean isEmergency,
859             @NonNull Network network,
860             EpdgSelectorCallback selectorCallback) {
861 
862         Runnable doValidation =
863                 () -> {
864                     List<InetAddress> validIpList = new ArrayList<>();
865                     Log.d(TAG, "Processing request with transactionId: " + transactionId);
866 
867                     int[] addrResolutionMethods =
868                             IwlanHelper.getConfig(
869                                     CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
870                                     mContext,
871                                     mSlotId);
872 
873                     final boolean isVisitedCountryMethodRequired =
874                             Arrays.stream(addrResolutionMethods)
875                                     .anyMatch(
876                                             i ->
877                                                     i
878                                                             == CarrierConfigManager.Iwlan
879                                                                     .EPDG_ADDRESS_VISITED_COUNTRY);
880 
881                     // In the visited country
882                     if (isRoaming && !inSameCountry() && isVisitedCountryMethodRequired) {
883                         resolutionMethodVisitedCountry(filter, validIpList, isEmergency, network);
884                     }
885 
886                     Map<String, List<InetAddress>> plmnDomainNamesToIpAddress = null;
887                     for (int addrResolutionMethod : addrResolutionMethods) {
888                         switch (addrResolutionMethod) {
889                             case CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC:
890                                 resolutionMethodStatic(filter, validIpList, isRoaming, network);
891                                 break;
892 
893                             case CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN:
894                                 plmnDomainNamesToIpAddress =
895                                         resolutionMethodPlmn(
896                                                 filter, validIpList, isEmergency, network);
897                                 break;
898 
899                             case CarrierConfigManager.Iwlan.EPDG_ADDRESS_PCO:
900                                 resolutionMethodPco(filter, validIpList);
901                                 break;
902 
903                             case CarrierConfigManager.Iwlan.EPDG_ADDRESS_CELLULAR_LOC:
904                                 resolutionMethodCellularLoc(
905                                         filter, validIpList, isEmergency, network);
906                                 break;
907 
908                             default:
909                                 Log.d(
910                                         TAG,
911                                         "Incorrect address resolution method "
912                                                 + addrResolutionMethod);
913                         }
914                     }
915 
916                     if (selectorCallback != null) {
917                         if (mErrorPolicyManager.getMostRecentDataFailCause()
918                                 == DataFailCause.IWLAN_CONGESTION) {
919                             int numFqdns = plmnDomainNamesToIpAddress.size();
920                             int index = mErrorPolicyManager.getCurrentFqdnIndex(numFqdns);
921                             if (index >= 0 && index < numFqdns) {
922                                 Object[] keys = plmnDomainNamesToIpAddress.keySet().toArray();
923                                 validIpList = plmnDomainNamesToIpAddress.get(keys[index]);
924                             } else {
925                                 Log.w(
926                                         TAG,
927                                         "CONGESTION error handling- invalid index: "
928                                                 + index
929                                                 + " number of PLMN FQDNs: "
930                                                 + numFqdns);
931                             }
932                         }
933 
934                         if (!validIpList.isEmpty()) {
935                             Collections.sort(validIpList, inetAddressComparator);
936                             selectorCallback.onServerListChanged(
937                                     transactionId, removeDuplicateIp(validIpList));
938                         } else {
939                             selectorCallback.onError(
940                                     transactionId,
941                                     new IwlanError(
942                                             IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED));
943                         }
944                     }
945                 };
946         Thread subThread = new Thread(doValidation);
947         subThread.start();
948         return new IwlanError(IwlanError.NO_ERROR);
949     }
950 }
951