blob: 8b53ee13c948bc1441527ac9fc60a311a65e4c97 [file] [log] [blame]
/*
* 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 android.content.Context;
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.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.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.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.IwlanTunnelMetricsImpl;
import com.google.android.iwlan.TunnelMetricsInterface;
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 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.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
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 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 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";
// "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<Integer, EpdgTunnelManager> mTunnelManagerInstances =
new ConcurrentHashMap<>();
private Queue<TunnelRequestWrapper> mPendingBringUpRequests = new LinkedList<>();
private final EpdgInfo mValidEpdgInfo = new EpdgInfo();
@Nullable private InetAddress mEpdgAddress;
// 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 IpSecManager mIpSecManager;
private Map<String, TunnelConfig> mApnNameToTunnelConfig = new ConcurrentHashMap<>();
private final Map<String, Integer> 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<Integer> VALID_DH_GROUPS;
private static final Set<Integer> VALID_KEY_LENGTHS;
private static final Set<Integer> VALID_PRF_ALGOS;
private static final Set<Integer> VALID_INTEGRITY_ALGOS;
private static final Set<Integer> VALID_ENCRYPTION_ALGOS;
private static final Set<Integer> 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);
}
// TODO(b/239753287): Some networks request DEVICE_IDENTITY, but errors out when parsing
// the response. Temporarily disabled.
private static final boolean INCLUDE_DEVICE_IDENTITY = false;
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) {
switch (reason) {
case BRINGDOWN_REASON_UNKNOWN:
return "BRINGDOWN_REASON_UNKNOWN";
case BRINGDOWN_REASON_DISABLE_N1_MODE:
return "BRINGDOWN_REASON_DISABLE_N1_MODE";
case BRINGDOWN_REASON_ENABLE_N1_MODE:
return "BRINGDOWN_REASON_ENABLE_N1_MODE";
case BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC:
return "BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC";
case BRINGDOWN_REASON_IN_DEACTIVATING_STATE:
return "BRINGDOWN_REASON_IN_DEACTIVATING_STATE";
case BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP:
return "BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP";
case BRINGDOWN_REASON_DEACTIVATE_DATA_CALL:
return "BRINGDOWN_REASON_DEACTIVATE_DATA_CALL";
default:
return "Unknown(" + reason + ")";
}
}
private final EpdgSelector.EpdgSelectorCallback mSelectorCallback =
new EpdgSelector.EpdgSelectorCallback() {
@Override
public void onServerListChanged(int transactionId, List<InetAddress> 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;
@NonNull final TunnelMetricsInterface mTunnelMetrics;
// TODO: Change this to TunnelLinkProperties after removing autovalue
private List<InetAddress> mPcscfAddrList;
private List<InetAddress> mDnsAddrList;
private List<LinkAddress> 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 IpSecManager.IpSecTunnelInterface mIface;
private IkeSessionState mIkeSessionState;
public TunnelConfig(
IkeSession ikeSession,
TunnelCallback tunnelCallback,
TunnelMetricsInterface tunnelMetrics,
IpSecManager.IpSecTunnelInterface iface,
InetAddress srcIpv6Addr,
int srcIpv6PrefixLength) {
mTunnelCallback = tunnelCallback;
mTunnelMetrics = tunnelMetrics;
mIkeSession = ikeSession;
mError = new IwlanError(IwlanError.NO_ERROR);
mSrcIpv6Address = srcIpv6Addr;
mSrcIpv6AddressPrefixLen = srcIpv6PrefixLength;
mIface = iface;
setIkeSessionState(IkeSessionState.IKE_SESSION_INIT_IN_PROGRESS);
}
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;
}
@NonNull
TunnelMetricsInterface getTunnelMetrics() {
return mTunnelMetrics;
}
List<InetAddress> getPcscfAddrList() {
return mPcscfAddrList;
}
void setPcscfAddrList(List<InetAddress> pcscfAddrList) {
mPcscfAddrList = pcscfAddrList;
}
public List<InetAddress> getDnsAddrList() {
return mDnsAddrList;
}
public void setDnsAddrList(List<InetAddress> dnsAddrList) {
this.mDnsAddrList = dnsAddrList;
}
public List<LinkAddress> 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<LinkAddress> internalAddrList) {
mInternalAddrList = new ArrayList<LinkAddress>(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 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) {
Log.d(TAG, "Ike session opened for apn: " + mApnName + " with token: " + mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_SESSION_OPENED,
new IkeSessionOpenedData(mApnName, mToken, sessionConfiguration)));
}
@Override
public void onClosed() {
Log.d(TAG, "Ike session closed for apn: " + mApnName + " with token: " + mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_SESSION_CLOSED,
new SessionClosedData(mApnName, mToken, null /* ikeException */)));
}
@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) {
Network network = ikeSessionConnectionInfo.getNetwork();
Log.d(
TAG,
"Ike session connection info changed for apn: "
+ mApnName
+ " with token: "
+ mToken
+ " Network: "
+ network);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED,
new IkeSessionConnectionInfoData(
mApnName, mToken, ikeSessionConnectionInfo)));
}
}
@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<Ike3gppData> payloads) {
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_3GPP_DATA_RECEIVED,
new Ike3gppDataReceived(mApnName, mToken, payloads)));
}
}
@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) {
Log.d(TAG, "onOpened child session for apn: " + mApnName + " with token: " + mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_CHILD_SESSION_OPENED,
new TunnelOpenedData(
mApnName,
mToken,
sessionConfiguration.getInternalDnsServers(),
sessionConfiguration.getInternalAddresses())));
}
@Override
public void onClosed() {
Log.d(TAG, "onClosed child session for apn: " + mApnName + " with token: " + mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_CHILD_SESSION_CLOSED,
new SessionClosedData(mApnName, mToken, null /* ikeException */)));
}
@Override
public void onClosedWithException(IkeException exception) {
onSessionClosedWithException(exception, mApnName, mToken, EVENT_CHILD_SESSION_CLOSED);
}
@Override
public void onIpSecTransformsMigrated(
IpSecTransform inIpSecTransform, IpSecTransform outIpSecTransform) {
// migration is similar to addition
Log.d(TAG, "Transforms migrated for apn: " + mApnName + " with token: " + mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_CREATED,
new IpsecTransformData(
inIpSecTransform,
IpSecManager.DIRECTION_IN,
mApnName,
mToken)));
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_CREATED,
new IpsecTransformData(
outIpSecTransform,
IpSecManager.DIRECTION_OUT,
mApnName,
mToken)));
}
@Override
public void onIpSecTransformCreated(IpSecTransform ipSecTransform, int direction) {
Log.d(
TAG,
"Transform created, direction: "
+ direction
+ ", apn: "
+ mApnName
+ ", token: "
+ mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_CREATED,
new IpsecTransformData(ipSecTransform, direction, mApnName, mToken)));
}
@Override
public void onIpSecTransformDeleted(IpSecTransform ipSecTransform, int direction) {
Log.d(
TAG,
"Transform deleted, direction: "
+ direction
+ ", apn: "
+ mApnName
+ ", token: "
+ mToken);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_DELETED,
new IpsecTransformData(ipSecTransform, direction, mApnName, mToken)));
}
}
@VisibleForTesting
EpdgTunnelManager(Context context, int slotId, FeatureFlags featureFlags) {
mContext = context;
mSlotId = slotId;
mFeatureFlags = featureFlags;
mIkeSessionCreator = new IkeSessionCreator();
mIpSecManager = mContext.getSystemService(IpSecManager.class);
TAG = EpdgTunnelManager.class.getSimpleName() + "[" + mSlotId + "]";
initHandler();
}
@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 application context
* @param subId subscription ID for the tunnel
* @return tunnel manager instance corresponding to the sub id
*/
public static EpdgTunnelManager getInstance(@NonNull Context context, int subId) {
return mTunnelManagerInstances.computeIfAbsent(
subId, k -> new EpdgTunnelManager(context, subId, new FeatureFlagsImpl()));
}
@VisibleForTesting
public static void resetAllInstances() {
mTunnelManagerInstances.clear();
}
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
*/
void onOpened(@NonNull String apnName, @NonNull TunnelLinkProperties linkProperties);
/**
* Called when the tunnel is closed OR if bringup fails
*
* @param apnName apn for which the tunnel was closed
* @param error IwlanError carrying details of the error
*/
void onClosed(@NonNull String apnName, @NonNull IwlanError error);
}
/**
* 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 iwlanTunnelMetrics The metrics to be reported
* @param reason The reason for tunnel to be closed
*/
public void closeTunnel(
@NonNull String apnName,
boolean forceClose,
@NonNull TunnelCallback tunnelCallback,
@NonNull IwlanTunnelMetricsImpl iwlanTunnelMetrics,
@TunnelBringDownReason int reason) {
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_TUNNEL_BRINGDOWN_REQUEST,
new TunnelBringdownRequest(
apnName, forceClose, tunnelCallback, iwlanTunnelMetrics, reason)));
}
/**
* 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 network the linkProperties to be updated
*/
public void updateNetwork(Network network, LinkProperties linkProperties) {
UpdateNetworkWrapper updateNetworkWrapper =
new UpdateNetworkWrapper(network, linkProperties);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UPDATE_NETWORK, updateNetworkWrapper));
}
/**
* 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,
@NonNull TunnelMetricsInterface tunnelMetrics) {
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, tunnelMetrics);
mHandler.sendMessage(
mHandler.obtainMessage(EVENT_TUNNEL_BRINGUP_REQUEST, tunnelRequestWrapper));
return true;
}
private IkeSessionParams tryBuildIkeSessionParams(
TunnelSetupRequest setupRequest, String apnName, int token) {
try {
return buildIkeSessionParams(setupRequest, apnName, token);
} 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(
TunnelSetupRequest setupRequest,
TunnelCallback tunnelCallback,
TunnelMetricsInterface tunnelMetrics) {
String apnName = setupRequest.apnName();
IkeSessionParams ikeSessionParams;
IpSecManager.IpSecTunnelInterface iface;
Log.d(
TAG,
"Bringing up tunnel for apn: "
+ apnName
+ " ePDG: "
+ mEpdgAddress.getHostAddress());
final int token = incrementAndGetCurrentTokenForApn(apnName);
ikeSessionParams = tryBuildIkeSessionParams(setupRequest, apnName, token);
if (Objects.isNull(ikeSessionParams)) {
IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION);
reportIwlanError(apnName, iwlanError);
tunnelCallback.onClosed(apnName, iwlanError);
tunnelMetrics.onClosed(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);
tunnelMetrics.onClosed(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,
tunnelMetrics,
iface,
isSrcIpv6Present ? setupRequest.srcIpv6Address().get() : null,
setupRequest.srcIpv6AddressPrefixLength());
}
/**
* 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 =
getConfig(CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT);
int softTimeSeconds =
getConfig(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 =
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT);
softTimeSeconds =
IwlanHelper.getDefaultConfig(
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);
if (mFeatureFlags.multipleSaProposals()
&& IwlanCarrierConfig.getConfigBoolean(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL)) {
EpdgChildSaProposal epdgChildSaProposal = createEpdgChildSaProposal();
if (mFeatureFlags.highSecureTransformsPrioritized()) {
epdgChildSaProposal.enableReorderingSaferProposals();
}
if (isChildSessionAeadAlgosAvailable()) {
childSessionParamsBuilder.addChildSaProposal(
epdgChildSaProposal.buildProposedChildSaAeadProposal());
}
childSessionParamsBuilder.addChildSaProposal(
epdgChildSaProposal.buildProposedChildSaProposal());
childSessionParamsBuilder.addChildSaProposal(
epdgChildSaProposal.buildSupportedChildSaAeadProposal());
childSessionParamsBuilder.addChildSaProposal(
epdgChildSaProposal.buildSupportedChildSaProposal());
} else {
if (mFeatureFlags.aeadAlgosEnabled() && 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 int numPdnTunnels() {
return mApnNameToTunnelConfig.size();
}
// 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)
throws IwlanSimNotReadyException {
int hardTimeSeconds =
getConfig(CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT);
int softTimeSeconds =
getConfig(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 =
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT);
softTimeSeconds =
IwlanHelper.getDefaultConfig(
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(mEpdgAddress.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());
if (mFeatureFlags.multipleSaProposals()
&& IwlanCarrierConfig.getConfigBoolean(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL)) {
EpdgIkeSaProposal epdgIkeSaProposal = createEpdgIkeSaProposal();
if (mFeatureFlags.highSecureTransformsPrioritized()) {
epdgIkeSaProposal.enableReorderingSaferProposals();
}
if (isIkeSessionAeadAlgosAvailable()) {
builder.addIkeSaProposal(epdgIkeSaProposal.buildProposedIkeSaAeadProposal());
}
builder.addIkeSaProposal(epdgIkeSaProposal.buildProposedIkeSaProposal());
builder.addIkeSaProposal(epdgIkeSaProposal.buildSupportedIkeSaAeadProposal());
builder.addIkeSaProposal(epdgIkeSaProposal.buildSupportedIkeSaProposal());
} else {
if (mFeatureFlags.aeadAlgosEnabled() && isIkeSessionAeadAlgosAvailable()) {
builder.addIkeSaProposal(buildIkeSaAeadProposal());
} else {
builder.addIkeSaProposal(buildIkeSaProposal());
}
}
if (numPdnTunnels() == 0) {
builder.addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
Log.d(TAG, "IKE_OPTION_INITIAL_CONTACT");
}
if ((int) getConfig(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 (mEpdgAddress instanceof Inet6Address) {
builder.removeIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE);
}
builder.setIke3gppExtension(buildIke3gppExtension(setupRequest, apnName, token));
int nattKeepAliveTimer =
getConfig(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 =
IwlanHelper.getDefaultConfig(
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 (INCLUDE_DEVICE_IDENTITY) {
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() {
int[] encryptionAlgos =
getConfig(
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 isIkeSessionAeadAlgosAvailable() {
int[] encryptionAlgos =
getConfig(
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 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 <T> T getConfig(String configKey) {
return IwlanHelper.getConfig(configKey, mContext, mSlotId);
}
private void createEpdgSaProposal(EpdgSaProposal epdgSaProposal, boolean isChildProposal) {
epdgSaProposal.addProposedDhGroups(
IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan.KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY));
int[] encryptionAlgos =
isChildProposal
? IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY)
: IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY);
for (int encryptionAlgo : encryptionAlgos) {
if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CBC) {
int[] aesCbcKeyLens =
isChildProposal
? IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY)
: IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY);
epdgSaProposal.addProposedEncryptionAlgorithm(encryptionAlgo, aesCbcKeyLens);
}
if (encryptionAlgo == SaProposal.ENCRYPTION_ALGORITHM_AES_CTR) {
int[] aesCtrKeyLens =
isChildProposal
? IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY)
: IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY);
epdgSaProposal.addProposedEncryptionAlgorithm(encryptionAlgo, aesCtrKeyLens);
}
}
if (encryptionAlgos.length > 0) {
epdgSaProposal.addProposedIntegrityAlgorithm(
IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY));
}
int[] aeadAlgos =
isChildProposal
? IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY)
: IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY);
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)) {
int[] aesGcmKeyLens =
isChildProposal
? IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY)
: IwlanCarrierConfig.getConfigIntArray(
mContext,
mSlotId,
CarrierConfigManager.Iwlan
.KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY);
epdgSaProposal.addProposedAeadAlgorithm(aeadAlgo, aesGcmKeyLens);
}
}
}
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 = getConfig(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 =
getConfig(
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 =
getConfig(
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 =
getConfig(
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 =
getConfig(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 =
getConfig(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 = getConfig(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 =
getConfig(
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 =
getConfig(
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 =
getConfig(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<Integer> 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 (getConfig(CarrierConfigManager.Iwlan.KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL)) {
int[] dhGroups =
getConfig(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 =
getConfig(
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 =
getConfig(
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 =
getConfig(
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 =
getConfig(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 = getConfig(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 =
getConfig(
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 =
getConfig(
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 = getConfig(idTypeConfig);
switch (idType) {
case CarrierConfigManager.Iwlan.ID_TYPE_FQDN:
return new IkeFqdnIdentification(id);
case CarrierConfigManager.Iwlan.ID_TYPE_KEY_ID:
return new IkeKeyIdIdentification(id.getBytes(StandardCharsets.US_ASCII));
case CarrierConfigManager.Iwlan.ID_TYPE_RFC822_ADDR:
return 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.sendMessage(
mHandler.obtainMessage(
sessionType, new SessionClosedData(apnName, token, exception)));
}
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;
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:
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:
TunnelRequestWrapper tunnelRequestWrapper = (TunnelRequestWrapper) msg.obj;
TunnelSetupRequest setupRequest = tunnelRequestWrapper.getSetupRequest();
IwlanError bringUpError = null;
onClosedMetricsBuilder =
new OnClosedMetrics.Builder().setApnName(setupRequest.apnName());
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(setupRequest.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(setupRequest.apnName(), bringUpError);
} else if (!canBringUpTunnel(setupRequest.apnName())) {
Log.d(TAG, "Cannot bring up tunnel as retry time has not passed");
bringUpError = getLastError(setupRequest.apnName());
}
if (Objects.nonNull(bringUpError)) {
tunnelRequestWrapper
.getTunnelCallback()
.onClosed(setupRequest.apnName(), bringUpError);
tunnelRequestWrapper
.getTunnelMetrics()
.onClosed(onClosedMetricsBuilder.build());
return;
}
if (mHasConnectedToEpdg) {
// Service the request immediately when epdg address is available
onBringUpTunnel(
setupRequest,
tunnelRequestWrapper.getTunnelCallback(),
tunnelRequestWrapper.getTunnelMetrics());
break;
}
if (!isEpdgSelectionOrFirstTunnelBringUpInProgress()) {
// No tunnel bring-up in progress. Select the ePDG address first
selectEpdgAddress(setupRequest);
}
// Another bring-up or ePDG selection is in progress, pending this request.
mPendingBringUpRequests.add(tunnelRequestWrapper);
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();
validateAndSetEpdgAddress(selectorResult.getValidIpList());
onBringUpTunnel(
tunnelRequestWrapper.getSetupRequest(),
tunnelRequestWrapper.getTunnelCallback(),
tunnelRequestWrapper.getTunnelMetrics());
} 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();
tunnelConfig.getTunnelCallback().onOpened(apnName, linkProperties);
reportIwlanError(apnName, new IwlanError(IwlanError.NO_ERROR));
getEpdgSelector().onEpdgConnectedSuccessfully();
mIkeTunnelEstablishmentDuration =
System.currentTimeMillis() - mIkeTunnelEstablishmentStartTime;
mIkeTunnelEstablishmentStartTime = 0;
tunnelConfig
.getTunnelMetrics()
.onOpened(
new OnOpenedMetrics.Builder()
.setApnName(apnName)
.setEpdgServerAddress(mEpdgAddress)
.setEpdgServerSelectionDuration(
(int) mEpdgServerSelectionDuration)
.setIkeTunnelEstablishmentDuration(
(int) mIkeTunnelEstablishmentDuration)
.build());
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()) {
iwlanError = new IwlanError(
IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED);
} 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);
}
getEpdgSelector().onEpdgConnectionFailed(mEpdgAddress);
}
Log.d(TAG, "Tunnel Closed: " + iwlanError);
tunnelConfig.setIkeSessionState(IkeSessionState.NO_IKE_SESSION);
tunnelConfig.getTunnelCallback().onClosed(apnName, iwlanError);
onClosedMetricsBuilder = new OnClosedMetrics.Builder().setApnName(apnName);
if (!mHasConnectedToEpdg) {
failAllPendingRequests(iwlanError);
tunnelConfig.getTunnelMetrics().onClosed(onClosedMetricsBuilder.build());
} else {
mIkeTunnelEstablishmentDuration =
mIkeTunnelEstablishmentStartTime > 0
? System.currentTimeMillis()
- mIkeTunnelEstablishmentStartTime
: 0;
mIkeTunnelEstablishmentStartTime = 0;
onClosedMetricsBuilder
.setEpdgServerAddress(mEpdgAddress)
.setEpdgServerSelectionDuration((int) mEpdgServerSelectionDuration)
.setIkeTunnelEstablishmentDuration(
(int) mIkeTunnelEstablishmentDuration);
tunnelConfig.getTunnelMetrics().onClosed(onClosedMetricsBuilder.build());
}
mApnNameToTunnelConfig.remove(apnName);
if (mApnNameToTunnelConfig.size() == 0 && 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 (mHasConnectedToEpdg) {
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 (!defaultLinkProperties.isReachable(mEpdgAddress)) {
Log.w(
TAG,
"The default network link "
+ defaultLinkProperties
+ " doesn't have a route to the ePDG "
+ mEpdgAddress
+ paraString);
} else if (Objects.equals(mDefaultNetwork, mIkeSessionNetwork)) {
Log.w(
TAG,
"The default network has not changed from the IKE session"
+ " network. "
+ paraString);
} else {
mApnNameToTunnelConfig.forEach(
(apn, config) -> {
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);
bringdownRequest.mIwlanTunnelMetrics.onClosed(
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));
}
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();
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 =
getConfig(
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) {
mNextReauthId = ((EapAkaInfo) eapInfo).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 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> 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;
default:
throw new IllegalStateException("Unexpected value: " + msg.what);
}
}
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 =
IwlanHelper.getConfig(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT,
mContext,
mSlotId);
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);
}
EpdgSelector epdgSelector = getEpdgSelector();
IwlanError epdgError =
epdgSelector.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
EpdgSelector getEpdgSelector() {
return EpdgSelector.getSelectorInstance(mContext, mSlotId);
}
@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));
requestWrapper
.getTunnelMetrics()
.onClosed(
new OnClosedMetrics.Builder()
.setApnName(apnName)
.setEpdgServerAddress(mEpdgAddress)
.build());
numRequestsClosed++;
} else {
mPendingBringUpRequests.add(requestWrapper);
}
}
return numRequestsClosed;
}
@VisibleForTesting
void validateAndSetEpdgAddress(List<InetAddress> selectorResultList) {
if (mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) {
Log.d(
TAG,
"Selected first ePDG address "
+ selectorResultList.get(0)
+ " from available ePDG address list: "
+ Arrays.toString(selectorResultList.toArray()));
mValidEpdgInfo.setAddrList(selectorResultList);
mEpdgAddress = selectorResultList.get(0);
return;
}
List<InetAddress> 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);
mEpdgAddress = addrList.get(index);
mValidEpdgInfo.incrementIndex();
}
private void serviceAllPendingRequests() {
while (!mPendingBringUpRequests.isEmpty()) {
Log.d(TAG, "serviceAllPendingRequests");
TunnelRequestWrapper request = mPendingBringUpRequests.remove();
onBringUpTunnel(
request.getSetupRequest(),
request.getTunnelCallback(),
request.getTunnelMetrics());
}
}
private void failAllPendingRequests(IwlanError error) {
while (!mPendingBringUpRequests.isEmpty()) {
Log.d(TAG, "failAllPendingRequests");
TunnelRequestWrapper request = mPendingBringUpRequests.remove();
TunnelSetupRequest setupRequest = request.getSetupRequest();
reportIwlanError(setupRequest.apnName(), error);
request.getTunnelCallback().onClosed(setupRequest.apnName(), error);
request.getTunnelMetrics()
.onClosed(
new OnClosedMetrics.Builder()
.setApnName(setupRequest.apnName())
.setEpdgServerAddress(mEpdgAddress)
.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 final TunnelMetricsInterface mTunnelMetrics;
private TunnelRequestWrapper(
TunnelSetupRequest setupRequest,
TunnelCallback tunnelCallback,
TunnelMetricsInterface tunnelMetrics) {
mTunnelCallback = tunnelCallback;
mSetupRequest = setupRequest;
mTunnelMetrics = tunnelMetrics;
}
public TunnelSetupRequest getSetupRequest() {
return mSetupRequest;
}
public TunnelCallback getTunnelCallback() {
return mTunnelCallback;
}
public TunnelMetricsInterface getTunnelMetrics() {
return mTunnelMetrics;
}
}
private static final class TunnelBringdownRequest {
final String mApnName;
final boolean mForceClose;
final TunnelCallback mTunnelCallback;
final IwlanTunnelMetricsImpl mIwlanTunnelMetrics;
final int mBringDownReason;
private TunnelBringdownRequest(
String apnName,
boolean forceClose,
TunnelCallback tunnelCallback,
IwlanTunnelMetricsImpl iwlanTunnelMetrics,
@TunnelBringDownReason int reason) {
mApnName = apnName;
mForceClose = forceClose;
mTunnelCallback = tunnelCallback;
mIwlanTunnelMetrics = iwlanTunnelMetrics;
mBringDownReason = reason;
}
}
private static final class EpdgSelectorResult {
private final List<InetAddress> mValidIpList;
public List<InetAddress> getValidIpList() {
return mValidIpList;
}
public IwlanError getEpdgError() {
return mEpdgError;
}
public int getTransactionId() {
return mTransactionId;
}
private final IwlanError mEpdgError;
private final int mTransactionId;
private EpdgSelectorResult(
List<InetAddress> 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<InetAddress> mInternalDnsServers;
final List<LinkAddress> mInternalAddresses;
private TunnelOpenedData(
String apnName,
int token,
List<InetAddress> internalDnsServers,
List<LinkAddress> 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<Ike3gppData> mIke3gppData;
private Ike3gppDataReceived(String apnName, int token, List<Ike3gppData> 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 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<InetAddress> mAddrList;
private int mIndex;
private EpdgInfo() {
mAddrList = null;
mIndex = 0;
}
public List<InetAddress> getAddrList() {
return mAddrList;
}
public void setAddrList(@NonNull List<InetAddress> 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 = getConfig(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 =
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY);
}
Log.d(TAG, "getRetransmissionTimeoutsFromConfig: " + Arrays.toString(timeList));
return timeList;
}
private int getDpdDelayFromConfig() {
int dpdDelay = getConfig(CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT);
if (dpdDelay < IKE_DPD_DELAY_SEC_MIN || dpdDelay > IKE_DPD_DELAY_SEC_MAX) {
dpdDelay =
IwlanHelper.getDefaultConfig(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
*
* <p>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,
TunnelMetricsInterface tunnelMetrics,
IpSecManager.IpSecTunnelInterface iface,
InetAddress srcIpv6Addr,
int srcIPv6AddrPrefixLen) {
mApnNameToTunnelConfig.put(
apnName,
new TunnelConfig(
ikeSession,
tunnelCallback,
tunnelMetrics,
iface,
srcIpv6Addr,
srcIPv6AddrPrefixLen));
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<InetAddress> getAddressForNetwork(Network network) {
return IwlanHelper.getAllAddressesForNetwork(mContext, network);
}
@VisibleForTesting
IkeSessionCreator getIkeSessionCreator() {
return mIkeSessionCreator;
}
@VisibleForTesting
void sendSelectionRequestComplete(
List<InetAddress> validIPList, IwlanError result, int transactionId) {
mEpdgServerSelectionDuration = System.currentTimeMillis() - mEpdgServerSelectionStartTime;
mEpdgServerSelectionStartTime = 0;
EpdgSelectorResult epdgSelectorResult =
new EpdgSelectorResult(validIPList, result, transactionId);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE, epdgSelectorResult));
}
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) {
switch (event) {
case EVENT_TUNNEL_BRINGUP_REQUEST:
return "EVENT_TUNNEL_BRINGUP_REQUEST";
case EVENT_TUNNEL_BRINGDOWN_REQUEST:
return "EVENT_TUNNEL_BRINGDOWN_REQUEST";
case EVENT_CHILD_SESSION_OPENED:
return "EVENT_CHILD_SESSION_OPENED";
case EVENT_CHILD_SESSION_CLOSED:
return "EVENT_CHILD_SESSION_CLOSED";
case EVENT_IKE_SESSION_CLOSED:
return "EVENT_IKE_SESSION_CLOSED";
case EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE:
return "EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE";
case EVENT_IPSEC_TRANSFORM_CREATED:
return "EVENT_IPSEC_TRANSFORM_CREATED";
case EVENT_IPSEC_TRANSFORM_DELETED:
return "EVENT_IPSEC_TRANSFORM_DELETED";
case EVENT_UPDATE_NETWORK:
return "EVENT_UPDATE_NETWORK";
case EVENT_IKE_SESSION_OPENED:
return "EVENT_IKE_SESSION_OPENED";
case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED:
return "EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED";
case EVENT_IKE_3GPP_DATA_RECEIVED:
return "EVENT_IKE_3GPP_DATA_RECEIVED";
default:
return "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;
mEpdgAddress = 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) {
return ErrorPolicyManager.getInstance(mContext, mSlotId).reportIwlanError(apnName, error);
}
@VisibleForTesting
long reportIwlanError(String apnName, IwlanError error, long backOffTime) {
return ErrorPolicyManager.getInstance(mContext, mSlotId)
.reportIwlanError(apnName, error, backOffTime);
}
@VisibleForTesting
IwlanError getLastError(String apnName) {
return ErrorPolicyManager.getInstance(mContext, mSlotId).getLastError(apnName);
}
@VisibleForTesting
boolean canBringUpTunnel(String apnName) {
return ErrorPolicyManager.getInstance(mContext, mSlotId).canBringUpTunnel(apnName);
}
@VisibleForTesting
void setEpdgAddress(InetAddress inetAddress) {
mEpdgAddress = inetAddress;
}
@VisibleForTesting
IpPreferenceConflict isIpPreferenceConflictsWithNetwork(
@CarrierConfigManager.Iwlan.EpdgAddressIpPreference int ipPreference) {
List<InetAddress> 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();
}
public void dump(PrintWriter pw) {
pw.println("---- EpdgTunnelManager ----");
pw.println("mHasConnectedToEpdg: " + mHasConnectedToEpdg);
pw.println("mIkeSessionNetwork: " + mIkeSessionNetwork);
if (mEpdgAddress != null) {
pw.println("mEpdgAddress: " + mEpdgAddress);
}
pw.println("mApnNameToTunnelConfig:\n");
for (Map.Entry<String, TunnelConfig> entry : mApnNameToTunnelConfig.entrySet()) {
pw.println("APN: " + entry.getKey());
pw.println("TunnelConfig: " + entry.getValue());
pw.println();
}
pw.println("---------------------------");
}
}