/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.iwlan.epdg; import static android.net.ipsec.ike.ike3gpp.Ike3gppData.DATA_TYPE_NOTIFY_BACKOFF_TIMER; import static android.net.ipsec.ike.ike3gpp.Ike3gppData.DATA_TYPE_NOTIFY_N1_MODE_INFORMATION; import static android.net.ipsec.ike.ike3gpp.Ike3gppParams.PDU_SESSION_ID_UNSET; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static android.telephony.PreciseDataConnectionState.NetworkValidationStatus; import static com.google.android.iwlan.proto.MetricsAtom.*; import android.content.Context; import android.net.ConnectivityDiagnosticsManager; import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; import android.net.IpSecTransform; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.eap.EapAkaInfo; import android.net.eap.EapInfo; import android.net.eap.EapSessionConfig; import android.net.ipsec.ike.ChildSaProposal; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; import android.net.ipsec.ike.ChildSessionParams; import android.net.ipsec.ike.IkeFqdnIdentification; import android.net.ipsec.ike.IkeIdentification; import android.net.ipsec.ike.IkeKeyIdIdentification; import android.net.ipsec.ike.IkeRfc822AddrIdentification; import android.net.ipsec.ike.IkeSaProposal; import android.net.ipsec.ike.IkeSession; import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.IkeSessionConfiguration; import android.net.ipsec.ike.IkeSessionConnectionInfo; import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.IkeTrafficSelector; import android.net.ipsec.ike.SaProposal; import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeIOException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.ipsec.ike.ike3gpp.Ike3gppBackoffTimer; import android.net.ipsec.ike.ike3gpp.Ike3gppData; import android.net.ipsec.ike.ike3gpp.Ike3gppExtension; import android.net.ipsec.ike.ike3gpp.Ike3gppN1ModeInformation; import android.net.ipsec.ike.ike3gpp.Ike3gppParams; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telephony.CarrierConfigManager; import android.telephony.PreciseDataConnectionState; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.telephony.data.NetworkSliceInfo; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.google.android.iwlan.ErrorPolicyManager; import com.google.android.iwlan.IwlanCarrierConfig; import com.google.android.iwlan.IwlanError; import com.google.android.iwlan.IwlanHelper; import com.google.android.iwlan.IwlanStatsLog; import com.google.android.iwlan.TunnelMetricsInterface.OnClosedMetrics; import com.google.android.iwlan.TunnelMetricsInterface.OnOpenedMetrics; import com.google.android.iwlan.exceptions.IwlanSimNotReadyException; import com.google.android.iwlan.flags.FeatureFlags; import com.google.android.iwlan.flags.FeatureFlagsImpl; import com.google.android.iwlan.proto.MetricsAtom; import java.io.IOException; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class EpdgTunnelManager { private final FeatureFlags mFeatureFlags; private final Context mContext; private final int mSlotId; private Handler mHandler; private ConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; private static final int EVENT_TUNNEL_BRINGUP_REQUEST = 0; private static final int EVENT_TUNNEL_BRINGDOWN_REQUEST = 1; private static final int EVENT_CHILD_SESSION_OPENED = 2; private static final int EVENT_CHILD_SESSION_CLOSED = 3; private static final int EVENT_IKE_SESSION_CLOSED = 5; private static final int EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE = 6; private static final int EVENT_IPSEC_TRANSFORM_CREATED = 7; private static final int EVENT_IPSEC_TRANSFORM_DELETED = 8; private static final int EVENT_UPDATE_NETWORK = 9; private static final int EVENT_IKE_SESSION_OPENED = 10; private static final int EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED = 11; private static final int EVENT_IKE_3GPP_DATA_RECEIVED = 12; private static final int EVENT_IKE_LIVENESS_STATUS_CHANGED = 13; private static final int EVENT_REQUEST_NETWORK_VALIDATION_CHECK = 14; private static final int IKE_HARD_LIFETIME_SEC_MINIMUM = 300; private static final int IKE_HARD_LIFETIME_SEC_MAXIMUM = 86400; private static final int IKE_SOFT_LIFETIME_SEC_MINIMUM = 120; private static final int CHILD_HARD_LIFETIME_SEC_MINIMUM = 300; private static final int CHILD_HARD_LIFETIME_SEC_MAXIMUM = 14400; private static final int CHILD_SOFT_LIFETIME_SEC_MINIMUM = 120; private static final int LIFETIME_MARGIN_SEC_MINIMUM = (int) TimeUnit.MINUTES.toSeconds(1L); private static final int IKE_RETRANS_TIMEOUT_MS_MIN = 500; private static final int IKE_RETRANS_TIMEOUT_MS_MAX = (int) TimeUnit.MINUTES.toMillis(30L); private static final int IKE_RETRANS_MAX_ATTEMPTS_MAX = 10; private static final int IKE_DPD_DELAY_SEC_MIN = 20; private static final int IKE_DPD_DELAY_SEC_MAX = 1800; // 30 minutes private static final int NATT_KEEPALIVE_DELAY_SEC_MIN = 10; private static final int NATT_KEEPALIVE_DELAY_SEC_MAX = 120; private static final int DEVICE_IMEI_LEN = 15; private static final int DEVICE_IMEISV_SUFFIX_LEN = 2; private static final int TRAFFIC_SELECTOR_START_PORT = 0; private static final int TRAFFIC_SELECTOR_END_PORT = 65535; private static final String TRAFFIC_SELECTOR_IPV4_START_ADDR = "0.0.0.0"; private static final String TRAFFIC_SELECTOR_IPV4_END_ADDR = "255.255.255.255"; private static final String TRAFFIC_SELECTOR_IPV6_START_ADDR = "::"; private static final String TRAFFIC_SELECTOR_IPV6_END_ADDR = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"; private static final int NETWORK_VALIDATION_MIN_INTERVAL_MS = 10000; private static long sLastUnderlyingNetworkValidationMs = 0; private static final Object sLastUnderlyingNetworkValidationLock = new Object(); // "192.0.2.0" is selected from RFC5737, "IPv4 Address Blocks Reserved for Documentation" private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); private static final Map mTunnelManagerInstances = new ConcurrentHashMap<>(); private final Map mMetricsAtomForNetwork = new ConcurrentHashMap<>(); private final Queue mPendingBringUpRequests = new ArrayDeque<>(); private final EpdgInfo mValidEpdgInfo = new EpdgInfo(); // The most recently updated system default network as seen by IwlanDataService. @Nullable private Network mDefaultNetwork; // The latest Network provided to the IKE session. Only for debugging purposes. @Nullable private Network mIkeSessionNetwork; private int mTransactionId = 0; private boolean mHasConnectedToEpdg; private final IkeSessionCreator mIkeSessionCreator; private final IpSecManager mIpSecManager; private final EpdgSelector mEpdgSelector; private final Map mApnNameToTunnelConfig = new ConcurrentHashMap<>(); private final Map mApnNameToIpsecTransform = new ConcurrentHashMap<>(); private final Map mApnNameToCurrentToken = new ConcurrentHashMap<>(); private final String TAG; @Nullable private byte[] mNextReauthId = null; private long mEpdgServerSelectionDuration = 0; private long mEpdgServerSelectionStartTime = 0; private long mIkeTunnelEstablishmentStartTime = 0; private static final Set VALID_DH_GROUPS; private static final Set VALID_KEY_LENGTHS; private static final Set VALID_PRF_ALGOS; private static final Set VALID_INTEGRITY_ALGOS; private static final Set VALID_ENCRYPTION_ALGOS; private static final Set VALID_AEAD_ALGOS; private static final String CONFIG_TYPE_DH_GROUP = "dh group"; private static final String CONFIG_TYPE_KEY_LEN = "algorithm key length"; private static final String CONFIG_TYPE_PRF_ALGO = "prf algorithm"; private static final String CONFIG_TYPE_INTEGRITY_ALGO = "integrity algorithm"; private static final String CONFIG_TYPE_ENCRYPT_ALGO = "encryption algorithm"; static { VALID_DH_GROUPS = Set.of( SaProposal.DH_GROUP_1024_BIT_MODP, SaProposal.DH_GROUP_1536_BIT_MODP, SaProposal.DH_GROUP_2048_BIT_MODP, SaProposal.DH_GROUP_3072_BIT_MODP, SaProposal.DH_GROUP_4096_BIT_MODP); VALID_KEY_LENGTHS = Set.of( SaProposal.KEY_LEN_AES_128, SaProposal.KEY_LEN_AES_192, SaProposal.KEY_LEN_AES_256); VALID_ENCRYPTION_ALGOS = Set.of( SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.ENCRYPTION_ALGORITHM_AES_CTR); VALID_INTEGRITY_ALGOS = Set.of( SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256); VALID_AEAD_ALGOS = Set.of( SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16); VALID_PRF_ALGOS = Set.of( SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1, SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC, SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256, SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384, SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512); } @VisibleForTesting protected EpdgMonitor mEpdgMonitor = new EpdgMonitor(); public static final int BRINGDOWN_REASON_UNKNOWN = 0; public static final int BRINGDOWN_REASON_DISABLE_N1_MODE = 1; public static final int BRINGDOWN_REASON_ENABLE_N1_MODE = 2; public static final int BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC = 3; public static final int BRINGDOWN_REASON_IN_DEACTIVATING_STATE = 4; public static final int BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP = 5; public static final int BRINGDOWN_REASON_DEACTIVATE_DATA_CALL = 6; @IntDef({ BRINGDOWN_REASON_UNKNOWN, BRINGDOWN_REASON_DISABLE_N1_MODE, BRINGDOWN_REASON_ENABLE_N1_MODE, BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC, BRINGDOWN_REASON_IN_DEACTIVATING_STATE, BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP, BRINGDOWN_REASON_DEACTIVATE_DATA_CALL, }) public @interface TunnelBringDownReason {} private static String bringdownReasonToString(@TunnelBringDownReason int reason) { return switch (reason) { case BRINGDOWN_REASON_UNKNOWN -> "BRINGDOWN_REASON_UNKNOWN"; case BRINGDOWN_REASON_DISABLE_N1_MODE -> "BRINGDOWN_REASON_DISABLE_N1_MODE"; case BRINGDOWN_REASON_ENABLE_N1_MODE -> "BRINGDOWN_REASON_ENABLE_N1_MODE"; case BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC -> "BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC"; case BRINGDOWN_REASON_IN_DEACTIVATING_STATE -> "BRINGDOWN_REASON_IN_DEACTIVATING_STATE"; case BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP -> "BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP"; case BRINGDOWN_REASON_DEACTIVATE_DATA_CALL -> "BRINGDOWN_REASON_DEACTIVATE_DATA_CALL"; default -> "Unknown(" + reason + ")"; }; } private final EpdgSelector.EpdgSelectorCallback mSelectorCallback = new EpdgSelector.EpdgSelectorCallback() { @Override public void onServerListChanged(int transactionId, List validIPList) { sendSelectionRequestComplete( validIPList, new IwlanError(IwlanError.NO_ERROR), transactionId); } @Override public void onError(int transactionId, IwlanError epdgSelectorError) { sendSelectionRequestComplete(null, epdgSelectorError, transactionId); } }; @VisibleForTesting class TunnelConfig { @NonNull final TunnelCallback mTunnelCallback; // TODO: Change this to TunnelLinkProperties after removing autovalue private List mPcscfAddrList; private List mDnsAddrList; private List mInternalAddrList; private final InetAddress mSrcIpv6Address; private final int mSrcIpv6AddressPrefixLen; private NetworkSliceInfo mSliceInfo; private boolean mIsBackoffTimeValid = false; private long mBackoffTime; @NonNull final IkeSession mIkeSession; IwlanError mError; private final IpSecManager.IpSecTunnelInterface mIface; private IkeSessionState mIkeSessionState; private final boolean mIsEmergency; private final InetAddress mEpdgAddress; public TunnelConfig( IkeSession ikeSession, TunnelCallback tunnelCallback, IpSecManager.IpSecTunnelInterface iface, InetAddress srcIpv6Addr, int srcIpv6PrefixLength, boolean isEmergency, InetAddress epdgAddress) { mTunnelCallback = tunnelCallback; mIkeSession = ikeSession; mError = new IwlanError(IwlanError.NO_ERROR); mSrcIpv6Address = srcIpv6Addr; mSrcIpv6AddressPrefixLen = srcIpv6PrefixLength; mIface = iface; setIkeSessionState(IkeSessionState.IKE_SESSION_INIT_IN_PROGRESS); mIsEmergency = isEmergency; mEpdgAddress = epdgAddress; } public IkeSessionState getIkeSessionState() { return mIkeSessionState; } public void setIkeSessionState(IkeSessionState ikeSessionState) { mIkeSessionState = ikeSessionState; } public NetworkSliceInfo getSliceInfo() { return mSliceInfo; } public void setSliceInfo(NetworkSliceInfo si) { mSliceInfo = si; } public boolean isBackoffTimeValid() { return mIsBackoffTimeValid; } public long getBackoffTime() { return mBackoffTime; } public void setBackoffTime(long backoffTime) { mIsBackoffTimeValid = true; mBackoffTime = backoffTime; } @NonNull TunnelCallback getTunnelCallback() { return mTunnelCallback; } List getPcscfAddrList() { return mPcscfAddrList; } void setPcscfAddrList(List pcscfAddrList) { mPcscfAddrList = pcscfAddrList; } public List getDnsAddrList() { return mDnsAddrList; } public void setDnsAddrList(List dnsAddrList) { this.mDnsAddrList = dnsAddrList; } public List getInternalAddrList() { return mInternalAddrList; } boolean isPrefixSameAsSrcIP(LinkAddress laddr) { if (laddr.isIpv6() && (laddr.getPrefixLength() == mSrcIpv6AddressPrefixLen)) { IpPrefix assignedPrefix = new IpPrefix(laddr.getAddress(), laddr.getPrefixLength()); IpPrefix srcPrefix = new IpPrefix(mSrcIpv6Address, mSrcIpv6AddressPrefixLen); return assignedPrefix.equals(srcPrefix); } return false; } public void setInternalAddrList(List internalAddrList) { mInternalAddrList = new ArrayList(internalAddrList); if (getSrcIpv6Address() != null) { // check if we can reuse src ipv6 address (i.e. if prefix is same) for (LinkAddress assignedAddr : internalAddrList) { if (isPrefixSameAsSrcIP(assignedAddr)) { // the assigned IPv6 address is same as pre-Handover IPv6 // addr. Just reuse the pre-Handover Address so the IID is // preserved mInternalAddrList.remove(assignedAddr); // add original address mInternalAddrList.add( new LinkAddress(mSrcIpv6Address, mSrcIpv6AddressPrefixLen)); Log.d( TAG, "Network assigned IP replaced OLD:" + internalAddrList + " NEW:" + mInternalAddrList); break; } } } } @NonNull public IkeSession getIkeSession() { return mIkeSession; } public IwlanError getError() { return mError; } public void setError(IwlanError error) { this.mError = error; } public IpSecManager.IpSecTunnelInterface getIface() { return mIface; } public InetAddress getSrcIpv6Address() { return mSrcIpv6Address; } public boolean isEmergency() { return mIsEmergency; } public InetAddress getEpdgAddress() { return mEpdgAddress; } public boolean hasTunnelOpened() { return mInternalAddrList != null && !mInternalAddrList.isEmpty() /* The child session is opened */ && mIface != null; /* The tunnel interface is bring up */ } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("TunnelConfig { "); if (mSliceInfo != null) { sb.append("mSliceInfo: ").append(mSliceInfo).append(", "); } if (mIsBackoffTimeValid) { sb.append("mBackoffTime: ").append(mBackoffTime).append(", "); } sb.append(" }"); return sb.toString(); } } @VisibleForTesting class TmIkeSessionCallback implements IkeSessionCallback { private final String mApnName; private final int mToken; TmIkeSessionCallback(String apnName, int token) { this.mApnName = apnName; this.mToken = token; } @Override public void onOpened(IkeSessionConfiguration sessionConfiguration) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d(TAG, "Ike session opened for apn: " + mApnName + " with token: " + mToken); mHandler.obtainMessage( EVENT_IKE_SESSION_OPENED, new IkeSessionOpenedData(mApnName, mToken, sessionConfiguration)) .sendToTarget(); } @Override public void onClosed() { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d(TAG, "Ike session closed for apn: " + mApnName + " with token: " + mToken); mHandler.obtainMessage( EVENT_IKE_SESSION_CLOSED, new SessionClosedData(mApnName, mToken, null /* ikeException */)) .sendToTarget(); } @Override public void onClosedWithException(IkeException exception) { mNextReauthId = null; onSessionClosedWithException(exception, mApnName, mToken, EVENT_IKE_SESSION_CLOSED); } @Override public void onError(IkeProtocolException exception) { Log.d(TAG, "Ike session onError for apn: " + mApnName + " with token: " + mToken); mNextReauthId = null; Log.d( TAG, "ErrorType:" + exception.getErrorType() + " ErrorData:" + exception.getMessage()); } @Override public void onIkeSessionConnectionInfoChanged( IkeSessionConnectionInfo ikeSessionConnectionInfo) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Network network = ikeSessionConnectionInfo.getNetwork(); Log.d( TAG, "Ike session connection info changed for apn: " + mApnName + " with token: " + mToken + " Network: " + network); mHandler.obtainMessage( EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED, new IkeSessionConnectionInfoData( mApnName, mToken, ikeSessionConnectionInfo)) .sendToTarget(); } @Override public void onLivenessStatusChanged(int status) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d( TAG, "Ike liveness status changed for apn: " + mApnName + " with status: " + status); @NetworkValidationStatus int validationStatus; switch (status) { case IkeSessionCallback.LIVENESS_STATUS_ON_DEMAND_STARTED: case IkeSessionCallback.LIVENESS_STATUS_BACKGROUND_STARTED: case IkeSessionCallback.LIVENESS_STATUS_ON_DEMAND_ONGOING: case IkeSessionCallback.LIVENESS_STATUS_BACKGROUND_ONGOING: validationStatus = PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS; break; case IkeSessionCallback.LIVENESS_STATUS_SUCCESS: validationStatus = PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS; break; case IkeSessionCallback.LIVENESS_STATUS_FAILURE: validationStatus = PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE; break; default: validationStatus = PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS; } mHandler.obtainMessage( EVENT_IKE_LIVENESS_STATUS_CHANGED, new IkeSessionValidationStatusData(mApnName, mToken, validationStatus)) .sendToTarget(); } } @VisibleForTesting class TmIke3gppCallback implements Ike3gppExtension.Ike3gppDataListener { private final String mApnName; private final int mToken; private TmIke3gppCallback(String apnName, int token) { mApnName = apnName; mToken = token; } @Override public void onIke3gppDataReceived(List payloads) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } mHandler.obtainMessage( EVENT_IKE_3GPP_DATA_RECEIVED, new Ike3gppDataReceived(mApnName, mToken, payloads)) .sendToTarget(); } } @VisibleForTesting class TmChildSessionCallback implements ChildSessionCallback { private final String mApnName; private final int mToken; TmChildSessionCallback(String apnName, int token) { this.mApnName = apnName; this.mToken = token; } @Override public void onOpened(ChildSessionConfiguration sessionConfiguration) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d(TAG, "onOpened child session for apn: " + mApnName + " with token: " + mToken); mHandler.obtainMessage( EVENT_CHILD_SESSION_OPENED, new TunnelOpenedData( mApnName, mToken, sessionConfiguration.getInternalDnsServers(), sessionConfiguration.getInternalAddresses())) .sendToTarget(); } @Override public void onClosed() { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d(TAG, "onClosed child session for apn: " + mApnName + " with token: " + mToken); mHandler.obtainMessage( EVENT_CHILD_SESSION_CLOSED, new SessionClosedData(mApnName, mToken, null /* ikeException */)) .sendToTarget(); } @Override public void onClosedWithException(IkeException exception) { onSessionClosedWithException(exception, mApnName, mToken, EVENT_CHILD_SESSION_CLOSED); } @Override public void onIpSecTransformsMigrated( IpSecTransform inIpSecTransform, IpSecTransform outIpSecTransform) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } // migration is similar to addition Log.d(TAG, "Transforms migrated for apn: " + mApnName + " with token: " + mToken); mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_CREATED, new IpsecTransformData( inIpSecTransform, IpSecManager.DIRECTION_IN, mApnName, mToken)) .sendToTarget(); mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_CREATED, new IpsecTransformData( outIpSecTransform, IpSecManager.DIRECTION_OUT, mApnName, mToken)) .sendToTarget(); } @Override public void onIpSecTransformCreated(IpSecTransform ipSecTransform, int direction) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d( TAG, "Transform created, direction: " + direction + ", apn: " + mApnName + ", token: " + mToken); mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_CREATED, new IpsecTransformData(ipSecTransform, direction, mApnName, mToken)) .sendToTarget(); } @Override public void onIpSecTransformDeleted(IpSecTransform ipSecTransform, int direction) { if (mHandler == null) { Log.d(TAG, "Handler unavailable"); return; } Log.d( TAG, "Transform deleted, direction: " + direction + ", apn: " + mApnName + ", token: " + mToken); mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_DELETED, new IpsecTransformData(ipSecTransform, direction, mApnName, mToken)) .sendToTarget(); } } private EpdgTunnelManager(Context context, int slotId, FeatureFlags featureFlags) { this( context, slotId, featureFlags, new IkeSessionCreator(), new EpdgSelector(context, slotId, featureFlags)); } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) EpdgTunnelManager( Context context, int slotIndex, FeatureFlags featureFlags, IkeSessionCreator ikeSessionCreator, EpdgSelector epdgSelector) { mContext = context; mSlotId = slotIndex; mFeatureFlags = featureFlags; mIkeSessionCreator = ikeSessionCreator; mIpSecManager = mContext.getSystemService(IpSecManager.class); // Adding this here is necessary because we need to initialize EpdgSelector at the beginning // to ensure no broadcasts are missed. mEpdgSelector = epdgSelector; TAG = EpdgTunnelManager.class.getSimpleName() + "[" + mSlotId + "]"; initHandler(); registerConnectivityDiagnosticsCallback(); } private void registerConnectivityDiagnosticsCallback() { ConnectivityDiagnosticsManager connectivityDiagnosticsManager = Objects.requireNonNull(mContext) .getSystemService(ConnectivityDiagnosticsManager.class); NetworkRequest networkRequest = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .build(); mConnectivityDiagnosticsCallback = new ConnectivityDiagnosticsCallback() { @Override public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) { Network network = report.getNetwork(); int mNetworkValidationResult = report.getAdditionalInfo() .getInt(ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT); if (!mMetricsAtomForNetwork.containsKey(network)) { return; } reportValidationMetricsAtom( network, getMetricsValidationResult(mNetworkValidationResult), /* validationTriggered */ true); } }; connectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback( networkRequest, new HandlerExecutor(mHandler), mConnectivityDiagnosticsCallback); } private void reportValidationMetricsAtom( @NonNull Network network, int validationResult, boolean validationTriggered) { if (!mMetricsAtomForNetwork.containsKey(network)) { return; } MetricsAtom metricsAtom = mMetricsAtomForNetwork.get(network); metricsAtom.setValidationResult(validationResult); metricsAtom.setValidationTriggered(validationTriggered); metricsAtom.setValidationDurationMills( validationTriggered ? (int) (IwlanHelper.elapsedRealtime() - metricsAtom.getValidationStartTimeMills()) : 0); Log.d( TAG, "reportValidationMetricsAtom: reason=" + metricsAtom.getTriggerReason() + " validationResult=" + metricsAtom.getValidationResult() + " transportType=" + metricsAtom.getValidationTransportType() + " duration=" + metricsAtom.getValidationDurationMills() + " validationTriggered=" + metricsAtom.getValidationTriggered()); metricsAtom.sendMetricsData(); mMetricsAtomForNetwork.remove(network); } @VisibleForTesting MetricsAtom getValidationMetricsAtom(Network network) { return mMetricsAtomForNetwork.get(network); } private void unregisterConnectivityDiagnosticsCallback() { ConnectivityDiagnosticsManager connectivityDiagnosticsManager = Objects.requireNonNull(mContext) .getSystemService(ConnectivityDiagnosticsManager.class); if (connectivityDiagnosticsManager != null) { connectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( mConnectivityDiagnosticsCallback); } } @VisibleForTesting void initHandler() { mHandler = new TmHandler(getLooper()); } @VisibleForTesting Looper getLooper() { HandlerThread handlerThread = new HandlerThread("EpdgTunnelManagerThread"); handlerThread.start(); return handlerThread.getLooper(); } /** * Gets a EpdgTunnelManager instance. * * @param context the context at which EpdgTunnelManager instance to be created * @param slotId the slot index at which EpdgTunnelManager instance to be created * @return EpdgTunnelManager instance for the specified slot id */ public static EpdgTunnelManager getInstance(@NonNull Context context, int slotId) { return mTunnelManagerInstances.computeIfAbsent( slotId, k -> new EpdgTunnelManager(context, slotId, new FeatureFlagsImpl())); } @VisibleForTesting public static void resetAllInstances() { mTunnelManagerInstances.clear(); sLastUnderlyingNetworkValidationMs = 0; } private void reset() { if (mHandler != null) { mHandler.getLooper().quit(); mHandler = null; } mApnNameToTunnelConfig.forEach( (apn, config) -> { config.getIkeSession().kill(); IpSecManager.IpSecTunnelInterface iface = config.getIface(); if (iface != null) { iface.close(); } IpsecTransformData transformData = mApnNameToIpsecTransform.get(apn); if (transformData != null) { transformData.getTransform().close(); mApnNameToIpsecTransform.remove(apn); } }); mApnNameToTunnelConfig.clear(); } public static void deinit() { mTunnelManagerInstances.values().forEach(EpdgTunnelManager::reset); } public interface TunnelCallback { /** * Called when the tunnel is opened. * * @param apnName apn for which the tunnel was opened * @param linkProperties link properties of the tunnel * @param onOpenedMetrics metrics for the tunnel */ void onOpened( @NonNull String apnName, @NonNull TunnelLinkProperties linkProperties, OnOpenedMetrics onOpenedMetrics); /** * Called when the tunnel is closed OR if bring up fails * * @param apnName apn for which the tunnel was closed * @param error IwlanError carrying details of the error * @param onClosedMetrics metrics for the tunnel */ void onClosed( @NonNull String apnName, @NonNull IwlanError error, OnClosedMetrics onClosedMetrics); /** * Called when updates upon network validation status change. * * @param apnName APN affected. * @param status The updated validation status of the network. */ void onNetworkValidationStatusChanged( @NonNull String apnName, @NetworkValidationStatus int status); } /** * Close tunnel for an apn. Confirmation of closing will be delivered in TunnelCallback that was * provided in {@link #bringUpTunnel}. If no tunnel was available, callback will be delivered * using client-provided provided tunnelCallback and iwlanTunnelMetrics * * @param apnName APN name * @param forceClose if {@code true}, triggers a local cleanup of the tunnel; if {@code false}, * performs a normal closure procedure * @param tunnelCallback The tunnelCallback for tunnel to be closed * @param reason The reason for tunnel to be closed */ public void closeTunnel( @NonNull String apnName, boolean forceClose, @NonNull TunnelCallback tunnelCallback, @TunnelBringDownReason int reason) { mHandler.obtainMessage( EVENT_TUNNEL_BRINGDOWN_REQUEST, new TunnelBringdownRequest(apnName, forceClose, tunnelCallback, reason)) .sendToTarget(); } /** * Update the local Network. This will trigger a revaluation for every tunnel for which tunnel * manager has state. * * @param network the network to be updated * @param linkProperties the linkProperties to be updated */ public void updateNetwork(Network network, LinkProperties linkProperties) { UpdateNetworkWrapper updateNetworkWrapper = new UpdateNetworkWrapper(network, linkProperties); mHandler.obtainMessage(EVENT_UPDATE_NETWORK, updateNetworkWrapper).sendToTarget(); } /** * Bring up epdg tunnel. Only one bring up request per apn is expected. All active tunnel * requests and tunnels are expected to be on the same network. * * @param setupRequest {@link TunnelSetupRequest} tunnel configurations * @param tunnelCallback {@link TunnelCallback} interface to notify clients about the tunnel * state * @return true if params are valid and no existing tunnel. False otherwise. */ public boolean bringUpTunnel( @NonNull TunnelSetupRequest setupRequest, @NonNull TunnelCallback tunnelCallback) { String apnName = setupRequest.apnName(); if (getTunnelSetupRequestApnName(setupRequest) == null) { Log.e(TAG, "APN is null."); return false; } if (isTunnelConfigContainExistApn(apnName)) { Log.e(TAG, "Tunnel exists for apn:" + apnName); return false; } if (!isValidApnProtocol(setupRequest.apnIpProtocol())) { Log.e(TAG, "Invalid protocol for APN"); return false; } int pduSessionId = setupRequest.pduSessionId(); if (pduSessionId < 0 || pduSessionId > 15) { Log.e(TAG, "Invalid pduSessionId: " + pduSessionId); return false; } TunnelRequestWrapper tunnelRequestWrapper = new TunnelRequestWrapper(setupRequest, tunnelCallback); mHandler.obtainMessage(EVENT_TUNNEL_BRINGUP_REQUEST, tunnelRequestWrapper).sendToTarget(); return true; } private IkeSessionParams tryBuildIkeSessionParams( TunnelSetupRequest setupRequest, String apnName, int token, InetAddress epdgAddress) { try { return buildIkeSessionParams(setupRequest, apnName, token, epdgAddress); } catch (IwlanSimNotReadyException e) { return null; } } private IpSecManager.IpSecTunnelInterface tryCreateIpSecTunnelInterface() { try { return mIpSecManager.createIpSecTunnelInterface( DUMMY_ADDR /* unused */, DUMMY_ADDR /* unused */, mDefaultNetwork); } catch (IpSecManager.ResourceUnavailableException | IOException e) { Log.e(TAG, "Failed to create tunnel interface. " + e); return null; } } private void onBringUpTunnel( TunnelRequestWrapper tunnelRequestWrapper, InetAddress epdgAddress) { TunnelSetupRequest setupRequest = tunnelRequestWrapper.getSetupRequest(); TunnelCallback tunnelCallback = tunnelRequestWrapper.getTunnelCallback(); String apnName = setupRequest.apnName(); IkeSessionParams ikeSessionParams; IpSecManager.IpSecTunnelInterface iface; Log.d( TAG, "Bringing up tunnel for apn: " + apnName + " ePDG: " + epdgAddress.getHostAddress()); final int token = incrementAndGetCurrentTokenForApn(apnName); ikeSessionParams = tryBuildIkeSessionParams(setupRequest, apnName, token, epdgAddress); if (Objects.isNull(ikeSessionParams)) { IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION); reportIwlanError(apnName, iwlanError); tunnelCallback.onClosed( apnName, iwlanError, new OnClosedMetrics.Builder().setApnName(apnName).build()); return; } iface = tryCreateIpSecTunnelInterface(); if (Objects.isNull(iface)) { IwlanError iwlanError = new IwlanError(IwlanError.TUNNEL_TRANSFORM_FAILED); reportIwlanError(apnName, iwlanError); tunnelCallback.onClosed( apnName, iwlanError, new OnClosedMetrics.Builder().setApnName(apnName).build()); return; } mIkeTunnelEstablishmentStartTime = System.currentTimeMillis(); IkeSession ikeSession = getIkeSessionCreator() .createIkeSession( mContext, ikeSessionParams, buildChildSessionParams(setupRequest), Executors.newSingleThreadExecutor(), getTmIkeSessionCallback(apnName, token), new TmChildSessionCallback(apnName, token)); boolean isSrcIpv6Present = setupRequest.srcIpv6Address().isPresent(); putApnNameToTunnelConfig( apnName, ikeSession, tunnelCallback, iface, isSrcIpv6Present ? setupRequest.srcIpv6Address().get() : null, setupRequest.srcIpv6AddressPrefixLength(), setupRequest.isEmergency(), epdgAddress); } /** * Proxy to allow testing * * @hide */ @VisibleForTesting static class IkeSessionCreator { /** Creates a IKE session */ public IkeSession createIkeSession( @NonNull Context context, @NonNull IkeSessionParams ikeSessionParams, @NonNull ChildSessionParams firstChildSessionParams, @NonNull Executor userCbExecutor, @NonNull IkeSessionCallback ikeSessionCallback, @NonNull ChildSessionCallback firstChildSessionCallback) { return new IkeSession( context, ikeSessionParams, firstChildSessionParams, userCbExecutor, ikeSessionCallback, firstChildSessionCallback); } } private ChildSessionParams buildChildSessionParams(TunnelSetupRequest setupRequest) { int proto = setupRequest.apnIpProtocol(); int hardTimeSeconds = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT); int softTimeSeconds = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT); if (!isValidChildSessionLifetime(hardTimeSeconds, softTimeSeconds)) { if (hardTimeSeconds > CHILD_HARD_LIFETIME_SEC_MAXIMUM && softTimeSeconds > CHILD_SOFT_LIFETIME_SEC_MINIMUM) { hardTimeSeconds = CHILD_HARD_LIFETIME_SEC_MAXIMUM; softTimeSeconds = CHILD_HARD_LIFETIME_SEC_MAXIMUM - LIFETIME_MARGIN_SEC_MINIMUM; } else { hardTimeSeconds = IwlanCarrierConfig.getDefaultConfigInt( CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT); softTimeSeconds = IwlanCarrierConfig.getDefaultConfigInt( CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT); } Log.d( TAG, "Invalid child session lifetime values, set hard: " + hardTimeSeconds + ", soft: " + softTimeSeconds); } TunnelModeChildSessionParams.Builder childSessionParamsBuilder = new TunnelModeChildSessionParams.Builder() .setLifetimeSeconds(hardTimeSeconds, softTimeSeconds); // Else block and it's related functionality can be removed once // multipleSaProposals, highSecureTransformsPrioritized and aeadAlgosEnabled feature flags // related functionality becomes stable and gets instruction to remove feature flags. if (mFeatureFlags.multipleSaProposals() || mFeatureFlags.highSecureTransformsPrioritized()) { EpdgChildSaProposal epdgChildSaProposal = createEpdgChildSaProposal(); if (IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL)) { epdgChildSaProposal.enableAddChildSessionRekeyKePayload(); } // Adds single SA proposal, priority is for AEAD if configured else non-AEAD proposal. if (isChildSessionAeadAlgosAvailable()) { childSessionParamsBuilder.addChildSaProposal( epdgChildSaProposal.buildProposedChildSaAeadProposal()); } else { childSessionParamsBuilder.addChildSaProposal( epdgChildSaProposal.buildProposedChildSaProposal()); } // Adds multiple proposals. If AEAD proposal already added then adds // configured non-AEAD proposal followed by supported AEAD and non-AEAD proposals. if (IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL)) { if (isChildSessionAeadAlgosAvailable() && isChildSessionNonAeadAlgosAvailable()) { childSessionParamsBuilder.addChildSaProposal( epdgChildSaProposal.buildProposedChildSaProposal()); } childSessionParamsBuilder.addChildSaProposal( epdgChildSaProposal.buildSupportedChildSaAeadProposal()); childSessionParamsBuilder.addChildSaProposal( epdgChildSaProposal.buildSupportedChildSaProposal()); } } else { if (isChildSessionAeadAlgosAvailable()) { childSessionParamsBuilder.addChildSaProposal(buildAeadChildSaProposal()); } else { childSessionParamsBuilder.addChildSaProposal(buildChildSaProposal()); } } boolean handoverIPv4Present = setupRequest.srcIpv4Address().isPresent(); boolean handoverIPv6Present = setupRequest.srcIpv6Address().isPresent(); if (handoverIPv4Present || handoverIPv6Present) { if (handoverIPv4Present) { childSessionParamsBuilder.addInternalAddressRequest( (Inet4Address) setupRequest.srcIpv4Address().get()); childSessionParamsBuilder.addInternalDnsServerRequest(AF_INET); childSessionParamsBuilder.addInboundTrafficSelectors( getDefaultTrafficSelectorIpv4()); childSessionParamsBuilder.addOutboundTrafficSelectors( getDefaultTrafficSelectorIpv4()); } if (handoverIPv6Present) { childSessionParamsBuilder.addInternalAddressRequest( (Inet6Address) setupRequest.srcIpv6Address().get(), setupRequest.srcIpv6AddressPrefixLength()); childSessionParamsBuilder.addInternalDnsServerRequest(AF_INET6); childSessionParamsBuilder.addInboundTrafficSelectors( getDefaultTrafficSelectorIpv6()); childSessionParamsBuilder.addOutboundTrafficSelectors( getDefaultTrafficSelectorIpv6()); } } else { // non-handover case if (proto == ApnSetting.PROTOCOL_IP || proto == ApnSetting.PROTOCOL_IPV4V6) { childSessionParamsBuilder.addInternalAddressRequest(AF_INET); childSessionParamsBuilder.addInternalDnsServerRequest(AF_INET); childSessionParamsBuilder.addInboundTrafficSelectors( getDefaultTrafficSelectorIpv4()); childSessionParamsBuilder.addOutboundTrafficSelectors( getDefaultTrafficSelectorIpv4()); } if (proto == ApnSetting.PROTOCOL_IPV6 || proto == ApnSetting.PROTOCOL_IPV4V6) { childSessionParamsBuilder.addInternalAddressRequest(AF_INET6); childSessionParamsBuilder.addInternalDnsServerRequest(AF_INET6); childSessionParamsBuilder.addInboundTrafficSelectors( getDefaultTrafficSelectorIpv6()); childSessionParamsBuilder.addOutboundTrafficSelectors( getDefaultTrafficSelectorIpv6()); } } return childSessionParamsBuilder.build(); } private static IkeTrafficSelector getDefaultTrafficSelectorIpv4() { return new IkeTrafficSelector( TRAFFIC_SELECTOR_START_PORT, TRAFFIC_SELECTOR_END_PORT, InetAddresses.parseNumericAddress(TRAFFIC_SELECTOR_IPV4_START_ADDR), InetAddresses.parseNumericAddress(TRAFFIC_SELECTOR_IPV4_END_ADDR)); } private static IkeTrafficSelector getDefaultTrafficSelectorIpv6() { return new IkeTrafficSelector( TRAFFIC_SELECTOR_START_PORT, TRAFFIC_SELECTOR_END_PORT, InetAddresses.parseNumericAddress(TRAFFIC_SELECTOR_IPV6_START_ADDR), InetAddresses.parseNumericAddress(TRAFFIC_SELECTOR_IPV6_END_ADDR)); } private boolean needIncludeInitialContact(InetAddress epdgAddress) { return !mEpdgMonitor.isConnectedEpdg(epdgAddress); } // Returns the IMEISV or device IMEI, in that order of priority. private @Nullable String getMobileDeviceIdentity() { TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); telephonyManager = Objects.requireNonNull(telephonyManager) .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); if (telephonyManager == null) { return null; } // Queries the 15-digit device IMEI. String imei = telephonyManager.getImei(); if (imei == null || imei.length() != DEVICE_IMEI_LEN) { Log.i(TAG, "Unable to query valid Mobile Device Identity (IMEI)!"); return null; } String imeisv_suffix = telephonyManager.getDeviceSoftwareVersion(); if (imeisv_suffix == null || imeisv_suffix.length() != DEVICE_IMEISV_SUFFIX_LEN) { // Unable to retrieve 2-digit suffix for IMEISV, so returns device IMEI. return imei; } // Splices the first 14 digit of device IMEI with 2-digit SV suffix to form IMEISV. return imei.substring(0, imei.length() - 1) + imeisv_suffix; } private IkeSessionParams buildIkeSessionParams( TunnelSetupRequest setupRequest, String apnName, int token, InetAddress epdgAddress) throws IwlanSimNotReadyException { int hardTimeSeconds = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT); int softTimeSeconds = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_IKE_REKEY_SOFT_TIMER_SEC_INT); if (!isValidIkeSessionLifetime(hardTimeSeconds, softTimeSeconds)) { if (hardTimeSeconds > IKE_HARD_LIFETIME_SEC_MAXIMUM && softTimeSeconds > IKE_SOFT_LIFETIME_SEC_MINIMUM) { hardTimeSeconds = IKE_HARD_LIFETIME_SEC_MAXIMUM; softTimeSeconds = IKE_HARD_LIFETIME_SEC_MAXIMUM - LIFETIME_MARGIN_SEC_MINIMUM; } else { hardTimeSeconds = IwlanCarrierConfig.getDefaultConfigInt( CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT); softTimeSeconds = IwlanCarrierConfig.getDefaultConfigInt( CarrierConfigManager.Iwlan.KEY_IKE_REKEY_SOFT_TIMER_SEC_INT); } Log.d( TAG, "Invalid ike session lifetime values, set hard: " + hardTimeSeconds + ", soft: " + softTimeSeconds); } IkeSessionParams.Builder builder = new IkeSessionParams.Builder() // permanently hardcode DSCP to 46 (Expedited Forwarding class) // See https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml // This will make WiFi prioritize IKE signallig under WMM AC_VO .setDscp(46) .setServerHostname(epdgAddress.getHostAddress()) .setLocalIdentification(getLocalIdentification()) .setRemoteIdentification(getId(setupRequest.apnName(), false)) .setAuthEap(null, getEapConfig()) .setNetwork(mDefaultNetwork) .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID) .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY) .setLifetimeSeconds(hardTimeSeconds, softTimeSeconds) .setRetransmissionTimeoutsMillis(getRetransmissionTimeoutsFromConfig()) .setDpdDelaySeconds(getDpdDelayFromConfig()); // Else block and it's related functionality can be removed once // multipleSaProposals, highSecureTransformsPrioritized and aeadAlgosEnabled feature flags // related functionality becomes stable and gets instruction to remove feature flags. if (mFeatureFlags.multipleSaProposals() || mFeatureFlags.highSecureTransformsPrioritized()) { EpdgIkeSaProposal epdgIkeSaProposal = createEpdgIkeSaProposal(); // Adds single SA proposal, priority is for AEAD if configured else non-AEAD proposal. if (isIkeSessionAeadAlgosAvailable()) { builder.addIkeSaProposal(epdgIkeSaProposal.buildProposedIkeSaAeadProposal()); } else { builder.addIkeSaProposal(epdgIkeSaProposal.buildProposedIkeSaProposal()); } // Adds multiple proposals. If AEAD proposal already added then adds // configured non-AEAD proposal followed by supported AEAD and non-AEAD proposals. if (IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL)) { if (isIkeSessionAeadAlgosAvailable() && isIkeSessionNonAeadAlgosAvailable()) { builder.addIkeSaProposal(epdgIkeSaProposal.buildProposedIkeSaProposal()); } builder.addIkeSaProposal(epdgIkeSaProposal.buildSupportedIkeSaAeadProposal()); builder.addIkeSaProposal(epdgIkeSaProposal.buildSupportedIkeSaProposal()); } } else { if (isIkeSessionAeadAlgosAvailable()) { builder.addIkeSaProposal(buildIkeSaAeadProposal()); } else { builder.addIkeSaProposal(buildIkeSaProposal()); } } if (needIncludeInitialContact(epdgAddress)) { builder.addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT); Log.d(TAG, "IKE_OPTION_INITIAL_CONTACT"); } if (IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_EPDG_AUTHENTICATION_METHOD_INT) == CarrierConfigManager.Iwlan.AUTHENTICATION_METHOD_EAP_ONLY) { builder.addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH); } if (setupRequest.requestPcscf()) { int proto = setupRequest.apnIpProtocol(); if (proto == ApnSetting.PROTOCOL_IP || proto == ApnSetting.PROTOCOL_IPV4V6) { builder.addPcscfServerRequest(AF_INET); } if (proto == ApnSetting.PROTOCOL_IPV6 || proto == ApnSetting.PROTOCOL_IPV4V6) { builder.addPcscfServerRequest(AF_INET6); } } // If MOBIKE is configured, ePDGs may force IPv6 UDP encapsulation- as specified by // RFC 4555- which Android connectivity stack presently does not support. if (epdgAddress instanceof Inet6Address) { builder.removeIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE); } builder.setIke3gppExtension(buildIke3gppExtension(setupRequest, apnName, token)); int nattKeepAliveTimer = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT); if (nattKeepAliveTimer < NATT_KEEPALIVE_DELAY_SEC_MIN || nattKeepAliveTimer > NATT_KEEPALIVE_DELAY_SEC_MAX) { Log.d(TAG, "Falling back to default natt keep alive timer" + nattKeepAliveTimer); nattKeepAliveTimer = IwlanCarrierConfig.getDefaultConfigInt( CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT); } builder.setNattKeepAliveDelaySeconds(nattKeepAliveTimer); return builder.build(); } private Ike3gppExtension buildIke3gppExtension( TunnelSetupRequest setupRequest, String apnName, int token) { Ike3gppParams.Builder builder3gppParams = new Ike3gppParams.Builder(); if (IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, IwlanCarrierConfig.KEY_IKE_DEVICE_IDENTITY_SUPPORTED_BOOL)) { String imei = getMobileDeviceIdentity(); if (imei != null) { Log.d(TAG, "DEVICE_IDENTITY set in Ike3gppParams"); builder3gppParams.setMobileDeviceIdentity(imei); } } if (setupRequest.pduSessionId() != PDU_SESSION_ID_UNSET) { // Includes N1_MODE_CAPABILITY NOTIFY payload in IKE_AUTH exchange when PDU session ID // is set; otherwise, do not include. builder3gppParams.setPduSessionId((byte) setupRequest.pduSessionId()); } return new Ike3gppExtension( builder3gppParams.build(), new TmIke3gppCallback(apnName, token)); } private boolean isChildSessionAeadAlgosAvailable() { if (!mFeatureFlags.aeadAlgosEnabled()) { return false; } int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (validateConfig(encryptionAlgo, VALID_AEAD_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { return true; } } return false; } private boolean isChildSessionNonAeadAlgosAvailable() { int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (validateConfig(encryptionAlgo, VALID_ENCRYPTION_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { return true; } } return false; } private boolean isIkeSessionAeadAlgosAvailable() { if (!mFeatureFlags.aeadAlgosEnabled()) { return false; } int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (validateConfig(encryptionAlgo, VALID_AEAD_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { return true; } } return false; } private boolean isIkeSessionNonAeadAlgosAvailable() { int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (validateConfig(encryptionAlgo, VALID_ENCRYPTION_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { return true; } } return false; } private boolean isValidChildSessionLifetime(int hardLifetimeSeconds, int softLifetimeSeconds) { return hardLifetimeSeconds >= CHILD_HARD_LIFETIME_SEC_MINIMUM && hardLifetimeSeconds <= CHILD_HARD_LIFETIME_SEC_MAXIMUM && softLifetimeSeconds >= CHILD_SOFT_LIFETIME_SEC_MINIMUM && hardLifetimeSeconds - softLifetimeSeconds >= LIFETIME_MARGIN_SEC_MINIMUM; } private boolean isValidIkeSessionLifetime(int hardLifetimeSeconds, int softLifetimeSeconds) { return hardLifetimeSeconds >= IKE_HARD_LIFETIME_SEC_MINIMUM && hardLifetimeSeconds <= IKE_HARD_LIFETIME_SEC_MAXIMUM && softLifetimeSeconds >= IKE_SOFT_LIFETIME_SEC_MINIMUM && hardLifetimeSeconds - softLifetimeSeconds >= LIFETIME_MARGIN_SEC_MINIMUM; } private void createEpdgSaProposal(EpdgSaProposal epdgSaProposal, boolean isChildProposal) { epdgSaProposal.addProposedDhGroups( IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY)); String encryptionAlgosConfigKey = isChildProposal ? CarrierConfigManager.Iwlan .KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY : CarrierConfigManager.Iwlan .KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY; int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray(mContext, mSlotId, encryptionAlgosConfigKey); for (int encryptionAlgo : encryptionAlgos) { if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CBC) { String aesCbcKeyLensConfigKey = isChildProposal ? CarrierConfigManager.Iwlan .KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY : CarrierConfigManager.Iwlan .KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY; int[] aesCbcKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, aesCbcKeyLensConfigKey); epdgSaProposal.addProposedEncryptionAlgorithm(encryptionAlgo, aesCbcKeyLens); } if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CTR) { String aesCtrKeyLensConfigKey = isChildProposal ? CarrierConfigManager.Iwlan .KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY : CarrierConfigManager.Iwlan .KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY; int[] aesCtrKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, aesCtrKeyLensConfigKey); epdgSaProposal.addProposedEncryptionAlgorithm(encryptionAlgo, aesCtrKeyLens); } } if (encryptionAlgos.length > 0) { epdgSaProposal.addProposedIntegrityAlgorithm( IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY)); } String aeadAlgosConfigKey = isChildProposal ? CarrierConfigManager.Iwlan .KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY : CarrierConfigManager.Iwlan .KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY; int[] aeadAlgos = IwlanCarrierConfig.getConfigIntArray(mContext, mSlotId, aeadAlgosConfigKey); for (int aeadAlgo : aeadAlgos) { if (!validateConfig(aeadAlgo, VALID_AEAD_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { continue; } if ((aeadAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8) || (aeadAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12) || (aeadAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16)) { String aesGcmKeyLensConfigKey = isChildProposal ? CarrierConfigManager.Iwlan .KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY : CarrierConfigManager.Iwlan .KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY; int[] aesGcmKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, aesGcmKeyLensConfigKey); epdgSaProposal.addProposedAeadAlgorithm(aeadAlgo, aesGcmKeyLens); } } if (IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, IwlanCarrierConfig.KEY_IKE_SA_TRANSFORMS_REORDER_BOOL)) { epdgSaProposal.enableReorderingSaferProposals(); } } private EpdgChildSaProposal createEpdgChildSaProposal() { EpdgChildSaProposal epdgChildSaProposal = new EpdgChildSaProposal(); createEpdgSaProposal(epdgChildSaProposal, true); return epdgChildSaProposal; } private EpdgIkeSaProposal createEpdgIkeSaProposal() { EpdgIkeSaProposal epdgIkeSaProposal = new EpdgIkeSaProposal(); createEpdgSaProposal(epdgIkeSaProposal, false); epdgIkeSaProposal.addProposedPrfAlgorithm( IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY)); return epdgIkeSaProposal; } private IkeSaProposal buildIkeSaProposal() { IkeSaProposal.Builder saProposalBuilder = new IkeSaProposal.Builder(); int[] dhGroups = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY); for (int dhGroup : dhGroups) { if (validateConfig(dhGroup, VALID_DH_GROUPS, CONFIG_TYPE_DH_GROUP)) { saProposalBuilder.addDhGroup(dhGroup); } } int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { validateConfig(encryptionAlgo, VALID_ENCRYPTION_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO); if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CBC) { int[] aesCbcKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY); for (int aesCbcKeyLen : aesCbcKeyLens) { if (validateConfig(aesCbcKeyLen, VALID_KEY_LENGTHS, CONFIG_TYPE_KEY_LEN)) { saProposalBuilder.addEncryptionAlgorithm(encryptionAlgo, aesCbcKeyLen); } } } if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CTR) { int[] aesCtrKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY); for (int aesCtrKeyLen : aesCtrKeyLens) { if (validateConfig(aesCtrKeyLen, VALID_KEY_LENGTHS, CONFIG_TYPE_KEY_LEN)) { saProposalBuilder.addEncryptionAlgorithm(encryptionAlgo, aesCtrKeyLen); } } } } int[] integrityAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY); for (int integrityAlgo : integrityAlgos) { if (validateConfig(integrityAlgo, VALID_INTEGRITY_ALGOS, CONFIG_TYPE_INTEGRITY_ALGO)) { saProposalBuilder.addIntegrityAlgorithm(integrityAlgo); } } int[] prfAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY); for (int prfAlgo : prfAlgos) { if (validateConfig(prfAlgo, VALID_PRF_ALGOS, CONFIG_TYPE_PRF_ALGO)) { saProposalBuilder.addPseudorandomFunction(prfAlgo); } } return saProposalBuilder.build(); } private IkeSaProposal buildIkeSaAeadProposal() { IkeSaProposal.Builder saProposalBuilder = new IkeSaProposal.Builder(); int[] dhGroups = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY); for (int dhGroup : dhGroups) { if (validateConfig(dhGroup, VALID_DH_GROUPS, CONFIG_TYPE_DH_GROUP)) { saProposalBuilder.addDhGroup(dhGroup); } } int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (!validateConfig(encryptionAlgo, VALID_AEAD_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { continue; } if ((encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8) || (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12) || (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16)) { int[] aesGcmKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY); for (int aesGcmKeyLen : aesGcmKeyLens) { if (validateConfig(aesGcmKeyLen, VALID_KEY_LENGTHS, CONFIG_TYPE_KEY_LEN)) { saProposalBuilder.addEncryptionAlgorithm(encryptionAlgo, aesGcmKeyLen); } } } } int[] prfAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY); for (int prfAlgo : prfAlgos) { if (validateConfig(prfAlgo, VALID_PRF_ALGOS, CONFIG_TYPE_PRF_ALGO)) { saProposalBuilder.addPseudorandomFunction(prfAlgo); } } return saProposalBuilder.build(); } private boolean validateConfig(int config, Set validConfigValues, String configType) { if (validConfigValues.contains(config)) { return true; } Log.e(TAG, "Invalid config value for " + configType + ":" + config); return false; } private ChildSaProposal buildChildSaProposal() { ChildSaProposal.Builder saProposalBuilder = new ChildSaProposal.Builder(); // IKE library doesn't add KE payload if dh groups are not set in child session params. // Use the same groups as that of IKE session. if (IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL)) { int[] dhGroups = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY); for (int dhGroup : dhGroups) { if (validateConfig(dhGroup, VALID_DH_GROUPS, CONFIG_TYPE_DH_GROUP)) { saProposalBuilder.addDhGroup(dhGroup); } } } int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (validateConfig(encryptionAlgo, VALID_ENCRYPTION_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { if (ChildSaProposal.getSupportedEncryptionAlgorithms().contains(encryptionAlgo)) { if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CBC) { int[] aesCbcKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY); for (int aesCbcKeyLen : aesCbcKeyLens) { if (validateConfig( aesCbcKeyLen, VALID_KEY_LENGTHS, CONFIG_TYPE_KEY_LEN)) { saProposalBuilder.addEncryptionAlgorithm( encryptionAlgo, aesCbcKeyLen); } } } if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CTR) { int[] aesCtrKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY); for (int aesCtrKeyLen : aesCtrKeyLens) { if (validateConfig( aesCtrKeyLen, VALID_KEY_LENGTHS, CONFIG_TYPE_KEY_LEN)) { saProposalBuilder.addEncryptionAlgorithm( encryptionAlgo, aesCtrKeyLen); } } } } else { Log.w(TAG, "Device does not support encryption algo: " + encryptionAlgo); } } } int[] integrityAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY); for (int integrityAlgo : integrityAlgos) { if (validateConfig(integrityAlgo, VALID_INTEGRITY_ALGOS, CONFIG_TYPE_INTEGRITY_ALGO)) { if (ChildSaProposal.getSupportedIntegrityAlgorithms().contains(integrityAlgo)) { saProposalBuilder.addIntegrityAlgorithm(integrityAlgo); } else { Log.w(TAG, "Device does not support integrity algo: " + integrityAlgo); } } } return saProposalBuilder.build(); } private ChildSaProposal buildAeadChildSaProposal() { ChildSaProposal.Builder saProposalBuilder = new ChildSaProposal.Builder(); int[] dhGroups = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY); for (int dhGroup : dhGroups) { if (validateConfig(dhGroup, VALID_DH_GROUPS, CONFIG_TYPE_DH_GROUP)) { saProposalBuilder.addDhGroup(dhGroup); } } int[] encryptionAlgos = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY); for (int encryptionAlgo : encryptionAlgos) { if (!validateConfig(encryptionAlgo, VALID_AEAD_ALGOS, CONFIG_TYPE_ENCRYPT_ALGO)) { continue; } if ((encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8) || (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12) || (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16)) { int[] aesGcmKeyLens = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY); for (int aesGcmKeyLen : aesGcmKeyLens) { if (validateConfig(aesGcmKeyLen, VALID_KEY_LENGTHS, CONFIG_TYPE_KEY_LEN)) { saProposalBuilder.addEncryptionAlgorithm(encryptionAlgo, aesGcmKeyLen); } } } } return saProposalBuilder.build(); } private IkeIdentification getLocalIdentification() throws IwlanSimNotReadyException { String nai; nai = IwlanHelper.getNai(mContext, mSlotId, mNextReauthId); if (nai == null) { throw new IwlanSimNotReadyException("Nai is null."); } Log.d(TAG, "getLocalIdentification: Nai: " + nai); return getId(nai, true); } private IkeIdentification getId(String id, boolean isLocal) { String idTypeConfig = isLocal ? CarrierConfigManager.Iwlan.KEY_IKE_LOCAL_ID_TYPE_INT : CarrierConfigManager.Iwlan.KEY_IKE_REMOTE_ID_TYPE_INT; int idType = IwlanCarrierConfig.getConfigInt(mContext, mSlotId, idTypeConfig); return switch (idType) { case CarrierConfigManager.Iwlan.ID_TYPE_FQDN -> new IkeFqdnIdentification(id); case CarrierConfigManager.Iwlan.ID_TYPE_KEY_ID -> new IkeKeyIdIdentification(id.getBytes(StandardCharsets.US_ASCII)); case CarrierConfigManager.Iwlan.ID_TYPE_RFC822_ADDR -> new IkeRfc822AddrIdentification(id); default -> throw new IllegalArgumentException("Invalid local Identity type: " + idType); }; } private EapSessionConfig getEapConfig() throws IwlanSimNotReadyException { int subId = IwlanHelper.getSubId(mContext, mSlotId); String nai = IwlanHelper.getNai(mContext, mSlotId, null); if (nai == null) { throw new IwlanSimNotReadyException("Nai is null."); } EapSessionConfig.EapAkaOption option = null; if (mNextReauthId != null) { option = new EapSessionConfig.EapAkaOption.Builder().setReauthId(mNextReauthId).build(); } Log.d(TAG, "getEapConfig: Nai: " + nai); return new EapSessionConfig.Builder() .setEapAkaConfig(subId, TelephonyManager.APPTYPE_USIM, option) .setEapIdentity(nai.getBytes(StandardCharsets.US_ASCII)) .build(); } private void onSessionClosedWithException( IkeException exception, String apnName, int token, int sessionType) { Log.e( TAG, "Closing tunnel with exception for apn: " + apnName + " with token: " + token + " sessionType:" + sessionType); exception.printStackTrace(); mHandler.obtainMessage(sessionType, new SessionClosedData(apnName, token, exception)) .sendToTarget(); } private boolean isEpdgSelectionOrFirstTunnelBringUpInProgress() { // Tunnel config is created but not connected to an ePDG. i.e., The first bring-up request // in progress. // No bring-up request in progress but pending queue is not empty. i.e. ePDG selection in // progress return (!mHasConnectedToEpdg && !mApnNameToTunnelConfig.isEmpty()) || !mPendingBringUpRequests.isEmpty(); } private IwlanError getErrorFromIkeException( IkeException ikeException, IkeSessionState ikeSessionState) { IwlanError error; if (ikeException instanceof IkeIOException) { error = new IwlanError(ikeSessionState.getErrorType(), ikeException); } else { error = new IwlanError(ikeException); } Log.e(TAG, "Closing tunnel: error: " + error + " state: " + ikeSessionState); return error; } private final class TmHandler extends Handler { @Override public void handleMessage(Message msg) { Log.d(TAG, "msg.what = " + eventToString(msg.what)); String apnName; TunnelConfig tunnelConfig; OnClosedMetrics.Builder onClosedMetricsBuilder; TunnelRequestWrapper tunnelRequestWrapper; ConnectivityManager connectivityManager; boolean isNetworkValidated; switch (msg.what) { case EVENT_CHILD_SESSION_OPENED: case EVENT_IKE_SESSION_CLOSED: case EVENT_IPSEC_TRANSFORM_CREATED: case EVENT_IPSEC_TRANSFORM_DELETED: case EVENT_CHILD_SESSION_CLOSED: case EVENT_IKE_SESSION_OPENED: case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED: case EVENT_IKE_3GPP_DATA_RECEIVED: case EVENT_IKE_LIVENESS_STATUS_CHANGED: IkeEventData ikeEventData = (IkeEventData) msg.obj; if (isObsoleteToken(ikeEventData.mApnName, ikeEventData.mToken)) { Log.d( TAG, eventToString(msg.what) + " for obsolete token " + ikeEventData.mToken); return; } } long mIkeTunnelEstablishmentDuration; switch (msg.what) { case EVENT_TUNNEL_BRINGUP_REQUEST: handleTunnelBringUpRequest((TunnelRequestWrapper) msg.obj); break; case EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE: EpdgSelectorResult selectorResult = (EpdgSelectorResult) msg.obj; printRequestQueue("EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE"); if (selectorResult.getTransactionId() != mTransactionId) { Log.e(TAG, "Mismatched transactionId"); break; } if (mPendingBringUpRequests.isEmpty()) { Log.d(TAG, "Empty request queue"); break; } if (selectorResult.getEpdgError().getErrorType() == IwlanError.NO_ERROR && selectorResult.getValidIpList() != null) { tunnelRequestWrapper = mPendingBringUpRequests.remove(); onBringUpTunnel( tunnelRequestWrapper, validateAndSetEpdgAddress(selectorResult.getValidIpList())); } else { IwlanError error = (selectorResult.getEpdgError().getErrorType() == IwlanError.NO_ERROR) ? new IwlanError( IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED) : selectorResult.getEpdgError(); failAllPendingRequests(error); } break; case EVENT_CHILD_SESSION_OPENED: TunnelOpenedData tunnelOpenedData = (TunnelOpenedData) msg.obj; apnName = tunnelOpenedData.mApnName; tunnelConfig = mApnNameToTunnelConfig.get(apnName); tunnelConfig.setDnsAddrList(tunnelOpenedData.mInternalDnsServers); tunnelConfig.setInternalAddrList(tunnelOpenedData.mInternalAddresses); IpSecManager.IpSecTunnelInterface tunnelInterface = tunnelConfig.getIface(); for (LinkAddress address : tunnelConfig.getInternalAddrList()) { try { tunnelInterface.addAddress( address.getAddress(), address.getPrefixLength()); } catch (IOException e) { Log.e(TAG, "Adding internal addresses to interface failed."); } } TunnelLinkProperties linkProperties = TunnelLinkProperties.builder() .setInternalAddresses(tunnelConfig.getInternalAddrList()) .setDnsAddresses(tunnelConfig.getDnsAddrList()) .setPcscfAddresses(tunnelConfig.getPcscfAddrList()) .setIfaceName(tunnelConfig.getIface().getInterfaceName()) .setSliceInfo(tunnelConfig.getSliceInfo()) .build(); mIkeTunnelEstablishmentDuration = System.currentTimeMillis() - mIkeTunnelEstablishmentStartTime; mIkeTunnelEstablishmentStartTime = 0; isNetworkValidated = isUnderlyingNetworkValidated(mIkeSessionNetwork); OnOpenedMetrics onOpenedMetrics = new OnOpenedMetrics.Builder() .setApnName(apnName) .setEpdgServerAddress(tunnelConfig.getEpdgAddress()) .setEpdgServerSelectionDuration( (int) mEpdgServerSelectionDuration) .setIkeTunnelEstablishmentDuration( (int) mIkeTunnelEstablishmentDuration) .setIsNetworkValidated(isNetworkValidated) .build(); tunnelConfig .getTunnelCallback() .onOpened(apnName, linkProperties, onOpenedMetrics); reportIwlanError(apnName, new IwlanError(IwlanError.NO_ERROR)); mEpdgSelector.onEpdgConnectedSuccessfully(); mEpdgMonitor.onApnConnectToEpdg(apnName, tunnelConfig.getEpdgAddress()); onConnectedToEpdg(true); mValidEpdgInfo.resetIndex(); printRequestQueue("EVENT_CHILD_SESSION_OPENED"); serviceAllPendingRequests(); tunnelConfig.setIkeSessionState(IkeSessionState.CHILD_SESSION_OPENED); break; case EVENT_IKE_SESSION_CLOSED: printRequestQueue("EVENT_IKE_SESSION_CLOSED"); SessionClosedData sessionClosedData = (SessionClosedData) msg.obj; apnName = sessionClosedData.mApnName; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { Log.e(TAG, "No callback found for apn: " + apnName); return; } // If IKE session closed exceptionally, we retrieve IwlanError directly from the // exception; otherwise, it is still possible that we triggered an IKE session // close due to an error (e.g. IwlanError.TUNNEL_TRANSFORM_FAILED), or because // the Child session closed exceptionally; in which case, we attempt to retrieve // the stored error (if any) from TunnelConfig. IwlanError iwlanError; if (sessionClosedData.mIkeException != null) { iwlanError = getErrorFromIkeException( sessionClosedData.mIkeException, tunnelConfig.getIkeSessionState()); } else { // If IKE session opened, then closed before child session (and IWLAN // tunnel) opened. // Iwlan reports IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED // instead of NO_ERROR if (!tunnelConfig.hasTunnelOpened()) { int errorType = IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED; iwlanError = new IwlanError(errorType); } else { iwlanError = tunnelConfig.getError(); } } IpSecManager.IpSecTunnelInterface iface = tunnelConfig.getIface(); if (iface != null) { iface.close(); } if (!tunnelConfig.hasTunnelOpened()) { if (tunnelConfig.isBackoffTimeValid()) { reportIwlanError(apnName, iwlanError, tunnelConfig.getBackoffTime()); } else { reportIwlanError(apnName, iwlanError); } mEpdgMonitor.onEpdgConnectionFailed( tunnelConfig.isEmergency(), tunnelConfig.getEpdgAddress()); if (sessionClosedData.mIkeException != null) { mEpdgSelector.onEpdgConnectionFailed( tunnelConfig.getEpdgAddress(), sessionClosedData.mIkeException); } } else { /* PDN disconnected case */ triggerUnderlyingNetworkValidationOnError(iwlanError); } Log.d(TAG, "Tunnel Closed: " + iwlanError); tunnelConfig.setIkeSessionState(IkeSessionState.NO_IKE_SESSION); onClosedMetricsBuilder = new OnClosedMetrics.Builder().setApnName(apnName); if (!mEpdgMonitor.hasEpdgConnected()) { failAllPendingRequests(iwlanError); } else { mIkeTunnelEstablishmentDuration = mIkeTunnelEstablishmentStartTime > 0 ? System.currentTimeMillis() - mIkeTunnelEstablishmentStartTime : 0; mIkeTunnelEstablishmentStartTime = 0; isNetworkValidated = isUnderlyingNetworkValidated(mIkeSessionNetwork); onClosedMetricsBuilder .setEpdgServerAddress(tunnelConfig.getEpdgAddress()) .setEpdgServerSelectionDuration((int) mEpdgServerSelectionDuration) .setIkeTunnelEstablishmentDuration( (int) mIkeTunnelEstablishmentDuration) .setIsNetworkValidated(isNetworkValidated); } tunnelConfig .getTunnelCallback() .onClosed(apnName, iwlanError, onClosedMetricsBuilder.build()); mApnNameToTunnelConfig.remove(apnName); mEpdgMonitor.onApnDisconnectFromEpdg(apnName); if (mApnNameToTunnelConfig.isEmpty() && mPendingBringUpRequests.isEmpty()) { onConnectedToEpdg(false); } break; case EVENT_UPDATE_NETWORK: UpdateNetworkWrapper updatedNetwork = (UpdateNetworkWrapper) msg.obj; mDefaultNetwork = updatedNetwork.getNetwork(); LinkProperties defaultLinkProperties = updatedNetwork.getLinkProperties(); String paraString = "Network: " + mDefaultNetwork; if (mEpdgMonitor.hasEpdgConnected()) { if (Objects.isNull(mDefaultNetwork)) { Log.w(TAG, "The default network has been removed."); } else if (Objects.isNull(defaultLinkProperties)) { Log.w( TAG, "The default network's LinkProperties is not ready ." + paraString); } else if (Objects.equals(mDefaultNetwork, mIkeSessionNetwork)) { Log.w( TAG, "The default network has not changed from the IKE session" + " network. " + paraString); } else { for (var entry : mApnNameToTunnelConfig.entrySet()) { String apn = entry.getKey(); TunnelConfig config = entry.getValue(); if (!defaultLinkProperties.isReachable(config.getEpdgAddress())) { Log.w( TAG, "The default network link " + defaultLinkProperties + " doesn't have a route to the ePDG " + config.getEpdgAddress() + paraString); } else { Log.d( TAG, "The Underlying Network is updating for APN (+" + apn + "). " + paraString); config.getIkeSession().setNetwork(mDefaultNetwork); config.setIkeSessionState( IkeSessionState.IKE_MOBILITY_IN_PROGRESS); mIkeSessionNetwork = mDefaultNetwork; } } } } break; case EVENT_TUNNEL_BRINGDOWN_REQUEST: TunnelBringdownRequest bringdownRequest = (TunnelBringdownRequest) msg.obj; apnName = bringdownRequest.mApnName; boolean forceClose = bringdownRequest.mForceClose; int reason = bringdownRequest.mBringDownReason; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { Log.w( TAG, "Bringdown request: No tunnel exists for apn: " + apnName + ", forced: " + forceClose + ", bringdown reason: " + bringdownReasonToString(reason)); } else { if (forceClose) { tunnelConfig.getIkeSession().kill(); } else { tunnelConfig.getIkeSession().close(); } } // TODO(b/309867892): Include tunnel bring down reason in metrics. int numClosed = closePendingRequestsForApn(apnName); if (numClosed > 0) { Log.d( TAG, "Closed " + numClosed + " pending requests for apn: " + apnName + ", bringdown reason: " + bringdownReasonToString(reason)); } if (tunnelConfig == null && numClosed == 0) { // IwlanDataService expected to close a (pending or up) tunnel but was not // found. Recovers state in IwlanDataService through TunnelCallback. iwlanError = new IwlanError(IwlanError.TUNNEL_NOT_FOUND); reportIwlanError(apnName, iwlanError); bringdownRequest.mTunnelCallback.onClosed( apnName, iwlanError, new OnClosedMetrics.Builder().setApnName(apnName).build()); } break; case EVENT_IPSEC_TRANSFORM_CREATED: IpsecTransformData transformData = (IpsecTransformData) msg.obj; apnName = transformData.getApnName(); tunnelConfig = mApnNameToTunnelConfig.get(apnName); try { mIpSecManager.applyTunnelModeTransform( tunnelConfig.getIface(), transformData.getDirection(), transformData.getTransform()); } catch (IOException | IllegalArgumentException e) { // If the IKE session was closed before the transform could be applied, the // IpSecService will throw an IAE on processing the IpSecTunnelInterface id. Log.e(TAG, "Failed to apply tunnel transform." + e); closeIkeSession( apnName, new IwlanError(IwlanError.TUNNEL_TRANSFORM_FAILED)); } mApnNameToIpsecTransform.put(apnName, transformData); if (tunnelConfig.getIkeSessionState() == IkeSessionState.IKE_MOBILITY_IN_PROGRESS) { tunnelConfig.setIkeSessionState(IkeSessionState.CHILD_SESSION_OPENED); } break; case EVENT_IPSEC_TRANSFORM_DELETED: transformData = (IpsecTransformData) msg.obj; IpSecTransform transform = transformData.getTransform(); transform.close(); mApnNameToIpsecTransform.remove(transformData.getApnName()); break; case EVENT_CHILD_SESSION_CLOSED: sessionClosedData = (SessionClosedData) msg.obj; apnName = sessionClosedData.mApnName; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { Log.d(TAG, "No tunnel callback for apn: " + apnName); return; } if (sessionClosedData.mIkeException != null) { tunnelConfig.setError( getErrorFromIkeException( sessionClosedData.mIkeException, tunnelConfig.getIkeSessionState())); } tunnelConfig.getIkeSession().close(); break; case EVENT_IKE_SESSION_OPENED: IkeSessionOpenedData ikeSessionOpenedData = (IkeSessionOpenedData) msg.obj; apnName = ikeSessionOpenedData.mApnName; IkeSessionConfiguration sessionConfiguration = ikeSessionOpenedData.mIkeSessionConfiguration; tunnelConfig = mApnNameToTunnelConfig.get(apnName); tunnelConfig.setPcscfAddrList(sessionConfiguration.getPcscfServers()); boolean enabledFastReauth = IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, CarrierConfigManager.Iwlan .KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL); Log.d( TAG, "CarrierConfigManager.Iwlan.KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL " + enabledFastReauth); if (enabledFastReauth) { EapInfo eapInfo = sessionConfiguration.getEapInfo(); if (eapInfo instanceof EapAkaInfo eapAkaInfo) { mNextReauthId = eapAkaInfo.getReauthId(); Log.d(TAG, "Update ReauthId: " + Arrays.toString(mNextReauthId)); } else { mNextReauthId = null; } } break; case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED: IkeSessionConnectionInfoData ikeSessionConnectionInfoData = (IkeSessionConnectionInfoData) msg.obj; Network network = ikeSessionConnectionInfoData.mIkeSessionConnectionInfo.getNetwork(); apnName = ikeSessionConnectionInfoData.mApnName; connectivityManager = mContext.getSystemService(ConnectivityManager.class); if (Objects.requireNonNull(connectivityManager).getLinkProperties(network) == null) { Log.e(TAG, "Network " + network + " has null LinkProperties!"); return; } tunnelConfig = mApnNameToTunnelConfig.get(apnName); tunnelInterface = tunnelConfig.getIface(); try { tunnelInterface.setUnderlyingNetwork(network); } catch (IOException | IllegalArgumentException e) { Log.e( TAG, "Failed to update underlying network for apn: " + apnName + " exception: " + e); } break; case EVENT_IKE_3GPP_DATA_RECEIVED: Ike3gppDataReceived ike3gppDataReceived = (Ike3gppDataReceived) msg.obj; apnName = ike3gppDataReceived.mApnName; List ike3gppData = ike3gppDataReceived.mIke3gppData; if (ike3gppData != null && !ike3gppData.isEmpty()) { tunnelConfig = mApnNameToTunnelConfig.get(apnName); for (Ike3gppData payload : ike3gppData) { if (payload.getDataType() == DATA_TYPE_NOTIFY_N1_MODE_INFORMATION) { Log.d(TAG, "Got payload DATA_TYPE_NOTIFY_N1_MODE_INFORMATION"); NetworkSliceInfo si = NetworkSliceSelectionAssistanceInformation.getSliceInfo( ((Ike3gppN1ModeInformation) payload).getSnssai()); if (si != null) { tunnelConfig.setSliceInfo(si); Log.d(TAG, "SliceInfo: " + si); } } else if (payload.getDataType() == DATA_TYPE_NOTIFY_BACKOFF_TIMER) { Log.d(TAG, "Got payload DATA_TYPE_NOTIFY_BACKOFF_TIMER"); long backoffTime = decodeBackoffTime( ((Ike3gppBackoffTimer) payload).getBackoffTimer()); if (backoffTime > 0) { tunnelConfig.setBackoffTime(backoffTime); Log.d(TAG, "Backoff Timer: " + backoffTime); } } } } else { Log.e(TAG, "Null or empty payloads received:"); } break; case EVENT_IKE_LIVENESS_STATUS_CHANGED: IkeSessionValidationStatusData ikeLivenessData = (IkeSessionValidationStatusData) msg.obj; @NetworkValidationStatus int validationStatus = ikeLivenessData.mStatus; apnName = ikeLivenessData.mApnName; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { Log.e(TAG, "No tunnel config found for apn: " + apnName); return; } tunnelConfig .getTunnelCallback() .onNetworkValidationStatusChanged(apnName, validationStatus); break; case EVENT_REQUEST_NETWORK_VALIDATION_CHECK: apnName = (String) msg.obj; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { Log.e(TAG, "No tunnel config found for apn: " + apnName); return; } tunnelConfig.getIkeSession().requestLivenessCheck(); break; default: throw new IllegalStateException("Unexpected value: " + msg.what); } } private void handleTunnelBringUpRequest(TunnelRequestWrapper tunnelRequestWrapper) { TunnelSetupRequest setupRequest = tunnelRequestWrapper.getSetupRequest(); String apnName = setupRequest.apnName(); IwlanError bringUpError = canBringUpTunnel(apnName, setupRequest.isEmergency()); if (Objects.nonNull(bringUpError)) { tunnelRequestWrapper .getTunnelCallback() .onClosed( apnName, bringUpError, new OnClosedMetrics.Builder().setApnName(apnName).build()); return; } serviceTunnelBringUpRequest(tunnelRequestWrapper); } private void serviceTunnelBringUpRequest(TunnelRequestWrapper tunnelRequestWrapper) { if (mEpdgMonitor.hasEpdgConnected()) { InetAddress epdgAddress = selectConnectedEpdgForTunnelBringUp(tunnelRequestWrapper); if (epdgAddress != null) { onBringUpTunnel(tunnelRequestWrapper, epdgAddress); return; } } if (!isEpdgSelectionOrFirstTunnelBringUpInProgress()) { // No tunnel bring-up in progress Or emergency request has attempted ePDG for normal // session but failed, select the ePDG address. selectEpdgAddress(tunnelRequestWrapper.getSetupRequest()); } // Another bring-up or ePDG selection is in progress, pending this request. mPendingBringUpRequests.add(tunnelRequestWrapper); } private InetAddress selectConnectedEpdgForTunnelBringUp( TunnelRequestWrapper tunnelRequestWrapper) { if (!mFeatureFlags.distinctEpdgSelectionForEmergencySessions() || !IwlanCarrierConfig.getConfigBoolean( mContext, mSlotId, IwlanCarrierConfig.KEY_DISTINCT_EPDG_FOR_EMERGENCY_ALLOWED_BOOL)) { // Attempt on ePDG for normal session since feature not enabled return mEpdgMonitor.getEpdgAddressForNormalSession(); } if (!tunnelRequestWrapper.getSetupRequest().isEmergency()) { if (mEpdgMonitor.hasSeparateEpdgConnectedForEmergencySession()) { // Attempt on ePDG for emergency session return mEpdgMonitor.getEpdgAddressForEmergencySession(); } else { // Attempt on ePDG for normal session. return mEpdgMonitor.getEpdgAddressForNormalSession(); } } if (!mEpdgMonitor.hasEmergencyPdnFailedWithConnectedEpdg()) { // First emergnecy attempt on ePDG for normal session. return mEpdgMonitor.getEpdgAddressForNormalSession(); } return null; // no suitable ePDG address found. Select new ePDG address needed. } TmHandler(Looper looper) { super(looper); } } private void closeIkeSession(String apnName, IwlanError error) { TunnelConfig tunnelConfig = mApnNameToTunnelConfig.get(apnName); tunnelConfig.setError(error); tunnelConfig.getIkeSession().close(); } private void selectEpdgAddress(TunnelSetupRequest setupRequest) { ++mTransactionId; mEpdgServerSelectionStartTime = System.currentTimeMillis(); final int ipPreference = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT); IpPreferenceConflict ipPreferenceConflict = isIpPreferenceConflictsWithNetwork(ipPreference); if (ipPreferenceConflict.mIsConflict) { sendSelectionRequestComplete( null, new IwlanError(ipPreferenceConflict.mErrorType), mTransactionId); return; } int protoFilter = EpdgSelector.PROTO_FILTER_IPV4V6; int epdgAddressOrder = EpdgSelector.SYSTEM_PREFERRED; switch (ipPreference) { case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_PREFERRED: epdgAddressOrder = EpdgSelector.IPV4_PREFERRED; break; case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_PREFERRED: epdgAddressOrder = EpdgSelector.IPV6_PREFERRED; break; case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_ONLY: protoFilter = EpdgSelector.PROTO_FILTER_IPV4; break; case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_ONLY: protoFilter = EpdgSelector.PROTO_FILTER_IPV6; break; case CarrierConfigManager.Iwlan.EPDG_ADDRESS_SYSTEM_PREFERRED: break; default: Log.w(TAG, "Invalid Ip preference : " + ipPreference); } IwlanError epdgError = mEpdgSelector.getValidatedServerList( mTransactionId, protoFilter, epdgAddressOrder, setupRequest.isRoaming(), setupRequest.isEmergency(), mDefaultNetwork, mSelectorCallback); if (epdgError.getErrorType() != IwlanError.NO_ERROR) { Log.e(TAG, "Epdg address selection failed with error:" + epdgError); sendSelectionRequestComplete(null, epdgError, mTransactionId); } } @VisibleForTesting int closePendingRequestsForApn(String apnName) { int numRequestsClosed = 0; int queueSize = mPendingBringUpRequests.size(); if (queueSize == 0) { return numRequestsClosed; } for (int count = 0; count < queueSize; count++) { TunnelRequestWrapper requestWrapper = mPendingBringUpRequests.remove(); if (requestWrapper.getSetupRequest().apnName().equals(apnName)) { requestWrapper .getTunnelCallback() .onClosed( apnName, new IwlanError(IwlanError.NO_ERROR), new OnClosedMetrics.Builder() .setApnName(apnName) .setEpdgServerAddress(null) .build()); numRequestsClosed++; } else { mPendingBringUpRequests.add(requestWrapper); } } return numRequestsClosed; } InetAddress validateAndSetEpdgAddressLegacy(List selectorResultList) { List addrList = mValidEpdgInfo.getAddrList(); if (addrList == null || !addrList.equals(selectorResultList)) { Log.d(TAG, "Update ePDG address list."); mValidEpdgInfo.setAddrList(selectorResultList); addrList = mValidEpdgInfo.getAddrList(); } int index = mValidEpdgInfo.getIndex(); Log.d( TAG, "Valid ePDG Address List: " + Arrays.toString(addrList.toArray()) + ", index = " + index); mValidEpdgInfo.incrementIndex(); return addrList.get(index); } @VisibleForTesting InetAddress validateAndSetEpdgAddress(List selectorResultList) { if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { return validateAndSetEpdgAddressLegacy(selectorResultList); } if (mEpdgMonitor.hasEmergencyPdnFailedWithConnectedEpdg() && selectorResultList .get(0) .equals(mEpdgMonitor.getEpdgAddressForNormalSession())) { List sublist = selectorResultList.subList(1, selectorResultList.size()); Log.d( TAG, "Selected separate ePDG address for emergency session " + sublist.get(0) + " from available ePDG address list: " + Arrays.toString(selectorResultList.toArray())); mValidEpdgInfo.setAddrList(sublist); return sublist.get(0); } Log.d( TAG, "Selected first ePDG address " + selectorResultList.get(0) + " from available ePDG address list: " + Arrays.toString(selectorResultList.toArray())); mValidEpdgInfo.setAddrList(selectorResultList); return selectorResultList.get(0); } private void serviceAllPendingRequests() { while (!mPendingBringUpRequests.isEmpty()) { Log.d(TAG, "serviceAllPendingRequests"); TunnelRequestWrapper requestWrapper = mPendingBringUpRequests.remove(); onBringUpTunnel(requestWrapper, mEpdgMonitor.getEpdgAddressForNormalSession()); } } private void failAllPendingRequests(IwlanError error) { while (!mPendingBringUpRequests.isEmpty()) { Log.d(TAG, "failAllPendingRequests"); TunnelRequestWrapper request = mPendingBringUpRequests.remove(); TunnelSetupRequest setupRequest = request.getSetupRequest(); String apnName = setupRequest.apnName(); reportIwlanError(apnName, error); request.getTunnelCallback() .onClosed( apnName, error, new OnClosedMetrics.Builder() .setApnName(apnName) .setEpdgServerAddress(null) .build()); } } // Prints mPendingBringUpRequests private void printRequestQueue(String info) { Log.d(TAG, info); Log.d( TAG, "mPendingBringUpRequests: " + Arrays.toString(mPendingBringUpRequests.toArray())); } // Update Network wrapper private static final class UpdateNetworkWrapper { private final Network mNetwork; private final LinkProperties mLinkProperties; private UpdateNetworkWrapper(Network network, LinkProperties linkProperties) { mNetwork = network; mLinkProperties = linkProperties; } public Network getNetwork() { return mNetwork; } public LinkProperties getLinkProperties() { return mLinkProperties; } } // Tunnel request + tunnel callback private static final class TunnelRequestWrapper { private final TunnelSetupRequest mSetupRequest; private final TunnelCallback mTunnelCallback; private TunnelRequestWrapper( TunnelSetupRequest setupRequest, TunnelCallback tunnelCallback) { mTunnelCallback = tunnelCallback; mSetupRequest = setupRequest; } public TunnelSetupRequest getSetupRequest() { return mSetupRequest; } public TunnelCallback getTunnelCallback() { return mTunnelCallback; } } private static final class TunnelBringdownRequest { final String mApnName; final boolean mForceClose; final TunnelCallback mTunnelCallback; final int mBringDownReason; private TunnelBringdownRequest( String apnName, boolean forceClose, TunnelCallback tunnelCallback, @TunnelBringDownReason int reason) { mApnName = apnName; mForceClose = forceClose; mTunnelCallback = tunnelCallback; mBringDownReason = reason; } } private static final class EpdgSelectorResult { private final List mValidIpList; public List getValidIpList() { return mValidIpList; } public IwlanError getEpdgError() { return mEpdgError; } public int getTransactionId() { return mTransactionId; } private final IwlanError mEpdgError; private final int mTransactionId; private EpdgSelectorResult( List validIpList, IwlanError epdgError, int transactionId) { mValidIpList = validIpList; mEpdgError = epdgError; mTransactionId = transactionId; } } // Data received from IkeSessionStateMachine on successful EVENT_CHILD_SESSION_OPENED. private static final class TunnelOpenedData extends IkeEventData { final List mInternalDnsServers; final List mInternalAddresses; private TunnelOpenedData( String apnName, int token, List internalDnsServers, List internalAddresses) { super(apnName, token); mInternalDnsServers = internalDnsServers; mInternalAddresses = internalAddresses; } } // Data received from IkeSessionStateMachine on successful EVENT_IKE_SESSION_OPENED. private static final class IkeSessionOpenedData extends IkeEventData { final IkeSessionConfiguration mIkeSessionConfiguration; private IkeSessionOpenedData( String apnName, int token, IkeSessionConfiguration ikeSessionConfiguration) { super(apnName, token); mIkeSessionConfiguration = ikeSessionConfiguration; } } private static final class IkeSessionConnectionInfoData extends IkeEventData { final IkeSessionConnectionInfo mIkeSessionConnectionInfo; private IkeSessionConnectionInfoData( String apnName, int token, IkeSessionConnectionInfo ikeSessionConnectionInfo) { super(apnName, token); mIkeSessionConnectionInfo = ikeSessionConnectionInfo; } } private static final class Ike3gppDataReceived extends IkeEventData { final List mIke3gppData; private Ike3gppDataReceived(String apnName, int token, List ike3gppData) { super(apnName, token); mIke3gppData = ike3gppData; } } // Data received from IkeSessionStateMachine if either IKE session or Child session have been // closed, normally or exceptionally. private static final class SessionClosedData extends IkeEventData { final IkeException mIkeException; private SessionClosedData(String apnName, int token, IkeException ikeException) { super(apnName, token); mIkeException = ikeException; } } private static final class IkeSessionValidationStatusData extends IkeEventData { @NetworkValidationStatus final int mStatus; private IkeSessionValidationStatusData(String apnName, int token, int status) { super(apnName, token); mStatus = status; } } private static final class IpsecTransformData extends IkeEventData { private final IpSecTransform mTransform; private final int mDirection; private IpsecTransformData( IpSecTransform transform, int direction, String apnName, int token) { super(apnName, token); mTransform = transform; mDirection = direction; } public IpSecTransform getTransform() { return mTransform; } public int getDirection() { return mDirection; } public String getApnName() { return super.mApnName; } } private abstract static class IkeEventData { final String mApnName; final int mToken; private IkeEventData(String apnName, int token) { mApnName = apnName; mToken = token; } } private static final class EpdgInfo { private List mAddrList; private int mIndex; private EpdgInfo() { mAddrList = null; mIndex = 0; } public List getAddrList() { return mAddrList; } public void setAddrList(@NonNull List AddrList) { mAddrList = AddrList; resetIndex(); } public int getIndex() { return mIndex; } public void incrementIndex() { if (getIndex() >= getAddrList().size() - 1) { resetIndex(); } else { mIndex++; } } public void resetIndex() { mIndex = 0; } } private static class IpPreferenceConflict { final boolean mIsConflict; final int mErrorType; private IpPreferenceConflict(boolean isConflict, int errorType) { mIsConflict = isConflict; mErrorType = errorType; } private IpPreferenceConflict() { mIsConflict = false; mErrorType = IwlanError.NO_ERROR; } } private int[] getRetransmissionTimeoutsFromConfig() { int[] timeList = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY); boolean isValid = timeList != null && timeList.length != 0 && timeList.length <= IKE_RETRANS_MAX_ATTEMPTS_MAX; for (int time : Objects.requireNonNull(timeList)) { if (time < IKE_RETRANS_TIMEOUT_MS_MIN || time > IKE_RETRANS_TIMEOUT_MS_MAX) { isValid = false; break; } } if (!isValid) { timeList = IwlanCarrierConfig.getDefaultConfigIntArray( CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY); } Log.d(TAG, "getRetransmissionTimeoutsFromConfig: " + Arrays.toString(timeList)); return timeList; } private int getDpdDelayFromConfig() { int dpdDelay = IwlanCarrierConfig.getConfigInt( mContext, mSlotId, CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT); if (dpdDelay < IKE_DPD_DELAY_SEC_MIN || dpdDelay > IKE_DPD_DELAY_SEC_MAX) { dpdDelay = IwlanCarrierConfig.getDefaultConfigInt( CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT); } return dpdDelay; } /** * Decodes backoff time as per TS 124 008 10.5.7.4a Bits 5 to 1 represent the binary coded timer * value * *

Bits 6 to 8 defines the timer value unit for the GPRS timer as follows: Bits 8 7 6 0 0 0 * value is incremented in multiples of 10 minutes 0 0 1 value is incremented in multiples of 1 * hour 0 1 0 value is incremented in multiples of 10 hours 0 1 1 value is incremented in * multiples of 2 seconds 1 0 0 value is incremented in multiples of 30 seconds 1 0 1 value is * incremented in multiples of 1 minute 1 1 0 value is incremented in multiples of 1 hour 1 1 1 * value indicates that the timer is deactivated. * * @param backoffTimeByte Byte value obtained from ike * @return long time value in seconds. -1 if the timer needs to be deactivated. */ private static long decodeBackoffTime(byte backoffTimeByte) { final int BACKOFF_TIME_VALUE_MASK = 0x1F; final int BACKOFF_TIMER_UNIT_MASK = 0xE0; final Long[] BACKOFF_TIMER_UNIT_INCREMENT_SECS = { 10L * 60L, // 10 minutes 60L * 60L, // 1 hour 10L * 60L * 60L, // 10 hours 2L, // 2 seconds 30L, // 30 seconds 60L, // 1 minute 60L * 60L, // 1 hour }; long time = backoffTimeByte & BACKOFF_TIME_VALUE_MASK; int timerUnit = (backoffTimeByte & BACKOFF_TIMER_UNIT_MASK) >> 5; if (timerUnit >= BACKOFF_TIMER_UNIT_INCREMENT_SECS.length) { return -1; } time *= BACKOFF_TIMER_UNIT_INCREMENT_SECS[timerUnit]; return time; } @VisibleForTesting String getTunnelSetupRequestApnName(TunnelSetupRequest setupRequest) { return setupRequest.apnName(); } @VisibleForTesting void putApnNameToTunnelConfig( String apnName, IkeSession ikeSession, TunnelCallback tunnelCallback, IpSecManager.IpSecTunnelInterface iface, InetAddress srcIpv6Addr, int srcIPv6AddrPrefixLen, boolean isEmergency, InetAddress epdgAddress) { mApnNameToTunnelConfig.put( apnName, new TunnelConfig( ikeSession, tunnelCallback, iface, srcIpv6Addr, srcIPv6AddrPrefixLen, isEmergency, epdgAddress)); Log.d(TAG, "Added APN: " + apnName + " to TunnelConfig"); } @VisibleForTesting int incrementAndGetCurrentTokenForApn(String apnName) { final int currentToken = mApnNameToCurrentToken.compute( apnName, (apn, token) -> token == null ? 0 : token + 1); Log.d(TAG, "Added token: " + currentToken + " for apn: " + apnName); return currentToken; } @VisibleForTesting boolean isTunnelConfigContainExistApn(String apnName) { return mApnNameToTunnelConfig.containsKey(apnName); } @VisibleForTesting List getAddressForNetwork(Network network) { return IwlanHelper.getAllAddressesForNetwork(mContext, network); } @VisibleForTesting IkeSessionCreator getIkeSessionCreator() { return mIkeSessionCreator; } @VisibleForTesting void sendSelectionRequestComplete( List validIPList, IwlanError result, int transactionId) { mEpdgServerSelectionDuration = System.currentTimeMillis() - mEpdgServerSelectionStartTime; mEpdgServerSelectionStartTime = 0; EpdgSelectorResult epdgSelectorResult = new EpdgSelectorResult(validIPList, result, transactionId); mHandler.obtainMessage(EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE, epdgSelectorResult) .sendToTarget(); } static boolean isValidApnProtocol(int proto) { return (proto == ApnSetting.PROTOCOL_IP || proto == ApnSetting.PROTOCOL_IPV4V6 || proto == ApnSetting.PROTOCOL_IPV6); } boolean isObsoleteToken(String apnName, int token) { if (!mApnNameToCurrentToken.containsKey(apnName)) { return true; } return token != mApnNameToCurrentToken.get(apnName); } private static String eventToString(int event) { return switch (event) { case EVENT_TUNNEL_BRINGUP_REQUEST -> "EVENT_TUNNEL_BRINGUP_REQUEST"; case EVENT_TUNNEL_BRINGDOWN_REQUEST -> "EVENT_TUNNEL_BRINGDOWN_REQUEST"; case EVENT_CHILD_SESSION_OPENED -> "EVENT_CHILD_SESSION_OPENED"; case EVENT_CHILD_SESSION_CLOSED -> "EVENT_CHILD_SESSION_CLOSED"; case EVENT_IKE_SESSION_CLOSED -> "EVENT_IKE_SESSION_CLOSED"; case EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE -> "EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE"; case EVENT_IPSEC_TRANSFORM_CREATED -> "EVENT_IPSEC_TRANSFORM_CREATED"; case EVENT_IPSEC_TRANSFORM_DELETED -> "EVENT_IPSEC_TRANSFORM_DELETED"; case EVENT_UPDATE_NETWORK -> "EVENT_UPDATE_NETWORK"; case EVENT_IKE_SESSION_OPENED -> "EVENT_IKE_SESSION_OPENED"; case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED -> "EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED"; case EVENT_IKE_3GPP_DATA_RECEIVED -> "EVENT_IKE_3GPP_DATA_RECEIVED"; case EVENT_IKE_LIVENESS_STATUS_CHANGED -> "EVENT_IKE_LIVENESS_STATUS_CHANGED"; case EVENT_REQUEST_NETWORK_VALIDATION_CHECK -> "EVENT_REQUEST_NETWORK_VALIDATION_CHECK"; default -> "Unknown(" + event + ")"; }; } @VisibleForTesting TmIkeSessionCallback getTmIkeSessionCallback(String apnName, int token) { return new TmIkeSessionCallback(apnName, token); } @VisibleForTesting void onConnectedToEpdg(boolean hasConnected) { mHasConnectedToEpdg = hasConnected; if (mHasConnectedToEpdg) { mIkeSessionNetwork = mDefaultNetwork; } else { mIkeSessionNetwork = null; } } @VisibleForTesting TunnelConfig getTunnelConfigForApn(String apnName) { return mApnNameToTunnelConfig.get(apnName); } @VisibleForTesting int getCurrentTokenForApn(String apnName) { if (!mApnNameToCurrentToken.containsKey(apnName)) { throw new IllegalArgumentException("There is no token for apn: " + apnName); } return mApnNameToCurrentToken.get(apnName); } @VisibleForTesting long reportIwlanError(String apnName, IwlanError error) { triggerUnderlyingNetworkValidationOnError(error); return ErrorPolicyManager.getInstance(mContext, mSlotId).reportIwlanError(apnName, error); } @VisibleForTesting long reportIwlanError(String apnName, IwlanError error, long backOffTime) { triggerUnderlyingNetworkValidationOnError(error); return ErrorPolicyManager.getInstance(mContext, mSlotId) .reportIwlanError(apnName, error, backOffTime); } @VisibleForTesting IwlanError getLastError(String apnName) { return ErrorPolicyManager.getInstance(mContext, mSlotId).getLastError(apnName); } @VisibleForTesting IwlanError canBringUpTunnel(String apnName, boolean isEmergency) { IwlanError bringUpError = null; if (IwlanHelper.getSubId(mContext, mSlotId) == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.e(TAG, "SIM isn't ready"); bringUpError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION); reportIwlanError(apnName, bringUpError); } else if (Objects.isNull(mDefaultNetwork)) { Log.e(TAG, "The default network is not ready"); bringUpError = new IwlanError(IwlanError.IKE_INTERNAL_IO_EXCEPTION); reportIwlanError(apnName, bringUpError); } else if (!isEmergency && !ErrorPolicyManager.getInstance(mContext, mSlotId).canBringUpTunnel(apnName)) { // TODO(b/343962773): Need to refactor emergency condition into ErrorPolicyManager Log.d(TAG, "Cannot bring up tunnel as retry time has not passed"); bringUpError = getLastError(apnName); } return bringUpError; } @VisibleForTesting IpPreferenceConflict isIpPreferenceConflictsWithNetwork( @CarrierConfigManager.Iwlan.EpdgAddressIpPreference int ipPreference) { List localAddresses = getAddressForNetwork(mDefaultNetwork); if (localAddresses == null || localAddresses.size() == 0) { Log.e(TAG, "No local addresses available for Network " + mDefaultNetwork); return new IpPreferenceConflict(true, IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED); } else if (!IwlanHelper.hasIpv6Address(localAddresses) && ipPreference == CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_ONLY) { Log.e( TAG, "ePDG IP preference: " + ipPreference + " conflicts with source IP type: " + EpdgSelector.PROTO_FILTER_IPV4); return new IpPreferenceConflict(true, IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED); } else if (!IwlanHelper.hasIpv4Address(localAddresses) && ipPreference == CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_ONLY) { // b/209938719 allows Iwlan to support VoWiFi for IPv4 ePDG server while on IPv6 WiFi. // Iwlan will receive a IPv4 address which is embedded in stacked IPv6 address. By using // this IPv4 address, UE will connect to IPv4 ePDG server through XLAT. However, there // are issues on connecting ePDG server through XLAT. Will allow IPV4_ONLY on IPv6 WiFi // after the issues are resolved. Log.e( TAG, "ePDG IP preference: " + ipPreference + " conflicts with source IP type: " + EpdgSelector.PROTO_FILTER_IPV6); return new IpPreferenceConflict(true, IwlanError.EPDG_ADDRESS_ONLY_IPV4_ALLOWED); } return new IpPreferenceConflict(); } private boolean isUnderlyingNetworkValidated(Network network) { if (network == null) return false; ConnectivityManager connectivityManager = Objects.requireNonNull(mContext).getSystemService(ConnectivityManager.class); NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network); return (networkCapabilities != null) && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } /** * Trigger network validation on the underlying network if needed to possibly update validation * status and cause system switch default network. */ void triggerUnderlyingNetworkValidationOnError(IwlanError error) { if (!isUnderlyingNetworkValidationRequired(error.getErrorType())) { return; } Log.d(TAG, "On triggering underlying network validation. Cause: " + error); validateUnderlyingNetwork(IwlanCarrierConfig.NETWORK_VALIDATION_EVENT_NO_RESPONSE); } public void validateUnderlyingNetwork(@IwlanCarrierConfig.NetworkValidationEvent int event) { int[] networkValidationEvents = IwlanCarrierConfig.getConfigIntArray( mContext, mSlotId, IwlanCarrierConfig.KEY_UNDERLYING_NETWORK_VALIDATION_EVENTS_INT_ARRAY); if (Arrays.stream(networkValidationEvents) .noneMatch(validationEvent -> validationEvent == event)) { return; } synchronized (sLastUnderlyingNetworkValidationLock) { long now = IwlanHelper.elapsedRealtime(); // TODO (b/356791418): Consolidate underlying network handling into a single centralized // subcomponent to prevent duplicate processing across different threads and classes // . Until then, we will prevent sending duplicate network validations by checking // the recent trigger time. if (now - sLastUnderlyingNetworkValidationMs > NETWORK_VALIDATION_MIN_INTERVAL_MS) { sLastUnderlyingNetworkValidationMs = now; Log.d( TAG, "On triggering underlying network validation. Event: " + IwlanCarrierConfig.getUnderlyingNetworkValidationEventString( event)); mHandler.post(() -> onTriggerUnderlyingNetworkValidation(event)); } } } private void onTriggerUnderlyingNetworkValidation(int event) { if (mDefaultNetwork == null) return; setupValidationMetricsAtom(mDefaultNetwork, event); if (!isUnderlyingNetworkValidated(mDefaultNetwork)) { Log.d(TAG, "Network " + mDefaultNetwork + " is already not validated."); reportValidationMetricsAtom( mDefaultNetwork, NETWORK_VALIDATION_RESULT_INVALID, /* validationTriggered */ false); return; } ConnectivityManager connectivityManager = Objects.requireNonNull(mContext).getSystemService(ConnectivityManager.class); Log.d(TAG, "Trigger underlying network validation on network: " + mDefaultNetwork); Objects.requireNonNull(connectivityManager) .reportNetworkConnectivity(mDefaultNetwork, false); } private void setupValidationMetricsAtom(@NonNull Network network, int event) { MetricsAtom metricsAtom = new MetricsAtom(); metricsAtom.setMessageId(IwlanStatsLog.IWLAN_UNDERLYING_NETWORK_VALIDATION_RESULT_REPORTED); metricsAtom.setTriggerReason(getMetricsTriggerReason(event)); ConnectivityManager connectivityManager = Objects.requireNonNull(mContext).getSystemService(ConnectivityManager.class); NetworkCapabilities networkCapabilities = Objects.requireNonNull(connectivityManager).getNetworkCapabilities(mDefaultNetwork); int validationTransportType = NETWORK_VALIDATION_TRANSPORT_TYPE_UNSPECIFIED; if (networkCapabilities != null) { if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { validationTransportType = NETWORK_VALIDATION_TRANSPORT_TYPE_CELLULAR; } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { validationTransportType = NETWORK_VALIDATION_TRANSPORT_TYPE_WIFI; } } metricsAtom.setValidationTransportType(validationTransportType); metricsAtom.setValidationStartTimeMills(IwlanHelper.elapsedRealtime()); mMetricsAtomForNetwork.put(network, metricsAtom); } boolean isUnderlyingNetworkValidationRequired(int error) { return switch (error) { case IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED, IwlanError.IKE_NETWORK_LOST_EXCEPTION, IwlanError.IKE_INIT_TIMEOUT, IwlanError.IKE_MOBILITY_TIMEOUT, IwlanError.IKE_DPD_TIMEOUT -> true; default -> false; }; } private int getMetricsValidationResult(int validationResult) { return switch (validationResult) { case ConnectivityReport.NETWORK_VALIDATION_RESULT_INVALID -> NETWORK_VALIDATION_RESULT_INVALID; case ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID -> NETWORK_VALIDATION_RESULT_VALID; case ConnectivityReport.NETWORK_VALIDATION_RESULT_PARTIALLY_VALID -> NETWORK_VALIDATION_RESULT_PARTIALLY_VALID; case ConnectivityReport.NETWORK_VALIDATION_RESULT_SKIPPED -> NETWORK_VALIDATION_RESULT_SKIPPED; default -> NETWORK_VALIDATION_RESULT_UNSPECIFIED; }; } private int getMetricsTriggerReason(int reason) { return switch (reason) { case IwlanCarrierConfig.NETWORK_VALIDATION_EVENT_MAKING_CALL -> NETWORK_VALIDATION_EVENT_MAKING_CALL; case IwlanCarrierConfig.NETWORK_VALIDATION_EVENT_SCREEN_ON -> NETWORK_VALIDATION_EVENT_SCREEN_ON; case IwlanCarrierConfig.NETWORK_VALIDATION_EVENT_NO_RESPONSE -> NETWORK_VALIDATION_EVENT_NO_RESPONSE; default -> NETWORK_VALIDATION_EVENT_UNSPECIFIED; }; } /** * Performs network validation check * * @param apnName APN for which to perform validation check */ public void requestNetworkValidationForApn(String apnName) { mHandler.obtainMessage(EVENT_REQUEST_NETWORK_VALIDATION_CHECK, apnName).sendToTarget(); } @VisibleForTesting protected void removeApnNameInTunnelConfig(String apnName) { mApnNameToTunnelConfig.remove(apnName); } public void prefetchEpdgServerList(Network network, boolean isRoaming) { mEpdgSelector.getValidatedServerList( 0 /* transactionId */, EpdgSelector.PROTO_FILTER_IPV4V6, EpdgSelector.SYSTEM_PREFERRED, isRoaming, false /* isEmergency */, network, null /* selectorCallback */); mEpdgSelector.getValidatedServerList( 0 /* transactionId */, EpdgSelector.PROTO_FILTER_IPV4V6, EpdgSelector.SYSTEM_PREFERRED, isRoaming, true /* isEmergency */, network, null /* selectorCallback */); } public void close() { mTunnelManagerInstances.remove(mSlotId); unregisterConnectivityDiagnosticsCallback(); } public void dump(PrintWriter pw) { pw.println("---- EpdgTunnelManager ----"); pw.println( "Has ePDG connected for normal session: " + mEpdgMonitor.hasEpdgConnectedForNormalSession()); pw.println( "Has Separate ePDG connected for emergency session: " + mEpdgMonitor.hasSeparateEpdgConnectedForEmergencySession()); pw.println("mIkeSessionNetwork: " + mIkeSessionNetwork); if (mEpdgMonitor.getEpdgAddressForNormalSession() != null) { pw.println( "EpdgAddressForNormalSession: " + mEpdgMonitor.getEpdgAddressForNormalSession()); } if (mEpdgMonitor.getEpdgAddressForEmergencySession() != null) { pw.println( "SeparateEpdgAddressForEmergencySession: " + mEpdgMonitor.getEpdgAddressForEmergencySession()); } pw.println("mApnNameToTunnelConfig:\n"); for (Map.Entry entry : mApnNameToTunnelConfig.entrySet()) { pw.println("APN: " + entry.getKey()); pw.println("TunnelConfig: " + entry.getValue()); pw.println(); } pw.println("---------------------------"); } }