blob: 6ac0f3d3afab34af0a64a5dd150c52fded75f42a [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.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.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.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.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.IwlanError;
import com.google.android.iwlan.IwlanHelper;
import com.google.android.iwlan.exceptions.IwlanSimNotReadyException;
import java.io.FileDescriptor;
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.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
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 Context mContext;
private final int mSlotId;
private HandlerThread mHandlerThread;
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";
private static Map<Integer, EpdgTunnelManager> mTunnelManagerInstances =
new ConcurrentHashMap<>();
private Queue<TunnelRequestWrapper> mPendingBringUpRequests = new LinkedList<>();
private EpdgInfo mValidEpdgInfo = new EpdgInfo();
private InetAddress mEpdgAddress;
private Network mNetwork;
private int mTransactionId = 0;
private int mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4V6;
private boolean mHasConnectedToEpdg;
private IkeSessionCreator mIkeSessionCreator;
private Map<String, TunnelConfig> mApnNameToTunnelConfig = new ConcurrentHashMap<>();
private final String TAG;
private List<InetAddress> mLocalAddresses;
@Nullable private byte[] mNextReauthId = null;
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 String CONFIG_TYPE_DH_GROUP = "dh group";
private static final String CONFIG_TYPE_KEY_LEN = "alogrithm 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 =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
SaProposal.DH_GROUP_1024_BIT_MODP,
SaProposal.DH_GROUP_1536_BIT_MODP,
SaProposal.DH_GROUP_2048_BIT_MODP)));
VALID_KEY_LENGTHS =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
SaProposal.KEY_LEN_AES_128,
SaProposal.KEY_LEN_AES_192,
SaProposal.KEY_LEN_AES_256)));
VALID_ENCRYPTION_ALGOS =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
SaProposal.ENCRYPTION_ALGORITHM_AES_CTR)));
VALID_INTEGRITY_ALGOS =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
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_PRF_ALGOS =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
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)));
}
private final EpdgSelector.EpdgSelectorCallback mSelectorCallback =
new EpdgSelector.EpdgSelectorCallback() {
@Override
public void onServerListChanged(
int transactionId, ArrayList<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;
// TODO: Change this to TunnelLinkProperties after removing autovalue
private List<InetAddress> mPcscfAddrList;
private List<InetAddress> mDnsAddrList;
private List<LinkAddress> mInternalAddrList;
private InetAddress mSrcIpv6Address;
private int mSrcIpv6AddressPrefixLen;
private NetworkSliceInfo mSliceInfo;
private boolean mIsBackoffTimeValid = false;
private long mBackoffTime;
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 final IkeSession mIkeSession;
IwlanError mError;
private IpSecManager.IpSecTunnelInterface mIface;
public TunnelConfig(
IkeSession ikeSession,
TunnelCallback tunnelCallback,
InetAddress srcIpv6Addr,
int srcIpv6PrefixLength) {
mTunnelCallback = tunnelCallback;
mIkeSession = ikeSession;
mError = new IwlanError(IwlanError.NO_ERROR);
mSrcIpv6Address = srcIpv6Addr;
mSrcIpv6AddressPrefixLen = srcIpv6PrefixLength;
}
@NonNull
TunnelCallback getTunnelCallback() {
return mTunnelCallback;
}
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);
if (assignedPrefix.equals(srcPrefix)) {
return true;
}
}
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 void setIface(IpSecManager.IpSecTunnelInterface iface) {
mIface = iface;
}
public InetAddress getSrcIpv6Address() {
return mSrcIpv6Address;
}
public int getSrcIpv6AddressPrefixLen() {
return mSrcIpv6AddressPrefixLen;
}
private String addressListString(List<InetAddress> list) {
StringBuilder sb = new StringBuilder();
sb.append("{ ");
for (InetAddress addr : list) {
sb.append(addr);
sb.append(", ");
}
sb.append(" }");
return sb.toString();
}
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 (mPcscfAddrList != null) {
sb.append("mPcscfAddrList: " + addressListString(mPcscfAddrList));
sb.append(", ");
}
if (mDnsAddrList != null) {
sb.append("mDnsAddrList: " + addressListString(mDnsAddrList));
sb.append(", ");
}
if (mInternalAddrList != null) {
sb.append("mInternalAddrList: { ");
for (LinkAddress addr : mInternalAddrList) {
sb.append(addr + ", ");
}
sb.append(" }, ");
}
if (mSrcIpv6Address != null) {
sb.append("{mSrcIpv6Address: " + mSrcIpv6Address + "}, ");
} else {
sb.append("{NULL mSrcIpv6Address}, ");
} */
if (mSliceInfo != null) {
sb.append("mSliceInfo: " + mSliceInfo + ", ");
}
if (mIsBackoffTimeValid) {
sb.append("mBackoffTime: " + mBackoffTime + ", ");
}
sb.append(" }");
return sb.toString();
}
}
@VisibleForTesting
class TmIkeSessionCallback implements IkeSessionCallback {
private final String mApnName;
TmIkeSessionCallback(String apnName) {
this.mApnName = apnName;
}
@Override
public void onOpened(IkeSessionConfiguration sessionConfiguration) {
Log.d(TAG, "Ike session opened for apn: " + mApnName);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_SESSION_OPENED,
new IkeSessionOpenedData(mApnName, sessionConfiguration)));
}
@Override
public void onClosed() {
Log.d(TAG, "Ike session closed for apn: " + mApnName);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_SESSION_CLOSED,
new SessionClosedData(mApnName, new IwlanError(IwlanError.NO_ERROR))));
}
@Override
public void onClosedExceptionally(IkeException exception) {
mNextReauthId = null;
onSessionClosedWithException(exception, mApnName, EVENT_IKE_SESSION_CLOSED);
}
@Override
public void onError(IkeProtocolException exception) {
Log.d(TAG, "Ike session onError for apn: " + mApnName);
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
+ " Network: "
+ network);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED,
new IkeSessionConnectionInfoData(mApnName, ikeSessionConnectionInfo)));
}
}
@VisibleForTesting
class TmIke3gppCallback implements Ike3gppExtension.Ike3gppDataListener {
private final String mApnName;
private TmIke3gppCallback(String apnName) {
mApnName = apnName;
}
@Override
public void onIke3gppDataReceived(List<Ike3gppData> payloads) {
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IKE_3GPP_DATA_RECEIVED,
new Ike3gppDataReceived(mApnName, payloads)));
}
}
@VisibleForTesting
class TmChildSessionCallback implements ChildSessionCallback {
private final String mApnName;
TmChildSessionCallback(String apnName) {
this.mApnName = apnName;
}
@Override
public void onOpened(ChildSessionConfiguration sessionConfiguration) {
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_CHILD_SESSION_OPENED,
new TunnelOpenedData(
mApnName,
sessionConfiguration.getInternalDnsServers(),
sessionConfiguration.getInternalAddresses())));
}
@Override
public void onClosed() {
Log.d(TAG, "onClosed child session for apn: " + mApnName);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_CHILD_SESSION_CLOSED,
new SessionClosedData(mApnName, new IwlanError(IwlanError.NO_ERROR))));
}
@Override
public void onClosedExceptionally(IkeException exception) {
onSessionClosedWithException(exception, mApnName, 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);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_CREATED,
new IpsecTransformData(
inIpSecTransform, IpSecManager.DIRECTION_IN, mApnName)));
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_CREATED,
new IpsecTransformData(
outIpSecTransform, IpSecManager.DIRECTION_OUT, mApnName)));
}
@Override
public void onIpSecTransformCreated(IpSecTransform ipSecTransform, int direction) {
Log.d(TAG, "Transform created, direction: " + direction + ", apn:" + mApnName);
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_IPSEC_TRANSFORM_CREATED,
new IpsecTransformData(ipSecTransform, direction, mApnName)));
}
@Override
public void onIpSecTransformDeleted(IpSecTransform ipSecTransform, int direction) {
Log.d(TAG, "Transform deleted, direction: " + direction + ", apn:" + mApnName);
mHandler.sendMessage(
mHandler.obtainMessage(EVENT_IPSEC_TRANSFORM_DELETED, ipSecTransform));
}
}
private EpdgTunnelManager(Context context, int slotId) {
mContext = context;
mSlotId = slotId;
mIkeSessionCreator = new IkeSessionCreator();
TAG = EpdgTunnelManager.class.getSimpleName() + "[" + mSlotId + "]";
initHandler();
}
@VisibleForTesting
void initHandler() {
mHandler = new TmHandler(getLooper());
}
@VisibleForTesting
Looper getLooper() {
mHandlerThread = new HandlerThread("EpdgTunnelManagerThread");
mHandlerThread.start();
return mHandlerThread.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));
}
@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}
*
* @param apnName apn name
* @param forceClose if true, results in local cleanup of tunnel
* @return true if params are valid and tunnel exists. False otherwise.
*/
public boolean closeTunnel(@NonNull String apnName, boolean forceClose) {
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_TUNNEL_BRINGDOWN_REQUEST,
forceClose ? 1 : 0,
0 /*not used*/,
apnName));
return true;
}
/**
* Update the local Network. This will trigger a revaluation for every tunnel for which tunnel
* manager has state.
*
* <p>Tunnels in bringup state will be for closed since IKE currently keeps retrying.
*
* <p>For rest of the tunnels, update IKE session wth new network. This will either result in
* MOBIKE callflow or just a rekey over new Network
*/
public void updateNetwork(@NonNull Network network, String apnName) {
UpdateNetworkWrapper updateNetworkWrapper = new UpdateNetworkWrapper(network, apnName);
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) {
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.sendMessage(
mHandler.obtainMessage(EVENT_TUNNEL_BRINGUP_REQUEST, tunnelRequestWrapper));
return true;
}
private void onBringUpTunnel(TunnelSetupRequest setupRequest, TunnelCallback tunnelCallback) {
String apnName = setupRequest.apnName();
IkeSessionParams ikeSessionParams = null;
Log.d(
TAG,
"Bringing up tunnel for apn: "
+ apnName
+ "ePDG : "
+ mEpdgAddress.getHostAddress());
try {
ikeSessionParams = buildIkeSessionParams(setupRequest, apnName);
} catch (IwlanSimNotReadyException e) {
IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION);
reportIwlanError(apnName, iwlanError);
tunnelCallback.onClosed(apnName, iwlanError);
return;
}
IkeSession ikeSession =
getIkeSessionCreator()
.createIkeSession(
mContext,
ikeSessionParams,
buildChildSessionParams(setupRequest),
Executors.newSingleThreadExecutor(),
getTmIkeSessionCallback(apnName),
new TmChildSessionCallback(apnName));
boolean isSrcIpv6Present = setupRequest.srcIpv6Address().isPresent();
putApnNameToTunnelConfig(
apnName,
ikeSession,
tunnelCallback,
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 =
(int) getConfig(CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT);
int softTimeSeconds =
(int) 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 =
(int)
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan
.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT);
softTimeSeconds =
(int)
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);
childSessionParamsBuilder.addSaProposal(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 =
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)
throws IwlanSimNotReadyException {
int hardTimeSeconds =
(int) getConfig(CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT);
int softTimeSeconds =
(int) 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 =
(int)
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan
.KEY_IKE_REKEY_HARD_TIMER_SEC_INT);
softTimeSeconds =
(int)
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(mContext)
// 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())
.addSaProposal(buildIkeSaProposal())
.setNetwork(mNetwork)
.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 (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);
}
}
Ike3gppParams.Builder builder3gppParams = null;
// TODO(b/239753287): Telus carrier requests DEVICE_IDENTITY, but errors out when parsing
// the response. Temporarily disabled.
if (false) {
String imei = getMobileDeviceIdentity();
if (imei != null) {
if (builder3gppParams == null) {
builder3gppParams = new Ike3gppParams.Builder();
}
Log.d(TAG, "DEVICE_IDENTITY set in Ike3gppParams");
builder3gppParams.setMobileDeviceIdentity(imei);
}
}
if (setupRequest.pduSessionId() != 0) {
if (builder3gppParams == null) {
builder3gppParams = new Ike3gppParams.Builder();
}
builder3gppParams.setPduSessionId((byte) setupRequest.pduSessionId());
}
if (builder3gppParams != null) {
Ike3gppExtension extension =
new Ike3gppExtension(builder3gppParams.build(), new TmIke3gppCallback(apnName));
builder.setIke3gppExtension(extension);
}
int nattKeepAliveTimer =
(int) 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 =
(int)
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT);
}
builder.setNattKeepAliveDelaySeconds(nattKeepAliveTimer);
return builder.build();
}
private boolean isValidChildSessionLifetime(int hardLifetimeSeconds, int softLifetimeSeconds) {
if (hardLifetimeSeconds < CHILD_HARD_LIFETIME_SEC_MINIMUM
|| hardLifetimeSeconds > CHILD_HARD_LIFETIME_SEC_MAXIMUM
|| softLifetimeSeconds < CHILD_SOFT_LIFETIME_SEC_MINIMUM
|| hardLifetimeSeconds - softLifetimeSeconds < LIFETIME_MARGIN_SEC_MINIMUM) {
return false;
}
return true;
}
private boolean isValidIkeSessionLifetime(int hardLifetimeSeconds, int softLifetimeSeconds) {
if (hardLifetimeSeconds < IKE_HARD_LIFETIME_SEC_MINIMUM
|| hardLifetimeSeconds > IKE_HARD_LIFETIME_SEC_MAXIMUM
|| softLifetimeSeconds < IKE_SOFT_LIFETIME_SEC_MINIMUM
|| hardLifetimeSeconds - softLifetimeSeconds < LIFETIME_MARGIN_SEC_MINIMUM) {
return false;
}
return true;
}
private <T> T getConfig(String configKey) {
return IwlanHelper.getConfig(configKey, mContext, mSlotId);
}
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 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 alog: " + 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 alog: " + integrityAlgo);
}
}
}
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 sessionType) {
IwlanError error = new IwlanError(exception);
Log.e(
TAG,
"Closing tunnel with exception for apn: "
+ apnName
+ " sessionType:"
+ sessionType
+ " error: "
+ error);
exception.printStackTrace();
mHandler.sendMessage(
mHandler.obtainMessage(sessionType, new SessionClosedData(apnName, error)));
}
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 final class TmHandler extends Handler {
private final String TAG = TmHandler.class.getSimpleName();
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "msg.what = " + msg.what);
String apnName;
TunnelConfig tunnelConfig;
switch (msg.what) {
case EVENT_TUNNEL_BRINGUP_REQUEST:
TunnelRequestWrapper tunnelRequestWrapper = (TunnelRequestWrapper) msg.obj;
TunnelSetupRequest setupRequest = tunnelRequestWrapper.getSetupRequest();
if (IwlanHelper.getSubId(mContext, mSlotId)
== SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.e(TAG, "SIM isn't ready");
IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION);
reportIwlanError(setupRequest.apnName(), iwlanError);
tunnelRequestWrapper
.getTunnelCallback()
.onClosed(setupRequest.apnName(), iwlanError);
return;
}
if (!canBringUpTunnel(setupRequest.apnName())) {
Log.d(TAG, "Cannot bring up tunnel as retry time has not passed");
tunnelRequestWrapper
.getTunnelCallback()
.onClosed(
setupRequest.apnName(),
getLastError(setupRequest.apnName()));
return;
}
if (mHasConnectedToEpdg) {
// Service the request immediately when epdg address is available
onBringUpTunnel(setupRequest, tunnelRequestWrapper.getTunnelCallback());
break;
}
if (!isEpdgSelectionOrFirstTunnelBringUpInProgress()) {
// No tunnel bring-up in progress. Select the ePDG address first
mNetwork = setupRequest.network();
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());
} 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));
setHasConnectedToEpdg(true);
mValidEpdgInfo.resetIndex();
printRequestQueue("EVENT_CHILD_SESSION_OPENED");
serviceAllPendingRequests();
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 (eg. 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.mIwlanError.getErrorType() != IwlanError.NO_ERROR) {
iwlanError = sessionClosedData.mIwlanError;
} else {
// If IKE session setup failed without error cause, Iwlan reports
// NETWORK_FAILURE instead of NO_ERROR
if (!tunnelConfig.hasTunnelOpened()) {
iwlanError = new IwlanError(IwlanError.NETWORK_FAILURE);
} 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);
}
}
Log.d(TAG, "Tunnel Closed: " + iwlanError);
tunnelConfig.getTunnelCallback().onClosed(apnName, iwlanError);
if (!mHasConnectedToEpdg) {
failAllPendingRequests(iwlanError);
}
mApnNameToTunnelConfig.remove(apnName);
if (mApnNameToTunnelConfig.size() == 0 && mPendingBringUpRequests.isEmpty()) {
resetTunnelManagerState();
}
break;
case EVENT_UPDATE_NETWORK:
UpdateNetworkWrapper updatedNetwork = (UpdateNetworkWrapper) msg.obj;
apnName = updatedNetwork.getApnName();
Network network = updatedNetwork.getNetwork();
tunnelConfig = mApnNameToTunnelConfig.get(apnName);
// Update the global cache if they aren't equal
if (mNetwork == null || !mNetwork.equals(network)) {
Log.d(TAG, "Updating mNetwork to " + network);
mNetwork = network;
}
if (tunnelConfig == null) {
Log.d(TAG, "Update Network request: No tunnel exists for apn: " + apnName);
} else {
Log.d(TAG, "Updating Network for apn: " + apnName + " Network: " + network);
tunnelConfig.getIkeSession().setNetwork(network);
}
break;
case EVENT_TUNNEL_BRINGDOWN_REQUEST:
apnName = (String) msg.obj;
int forceClose = msg.arg1;
tunnelConfig = mApnNameToTunnelConfig.get(apnName);
if (tunnelConfig == null) {
Log.d(
TAG,
"Bringdown request: No tunnel exists for apn: "
+ apnName
+ "forced: "
+ forceClose);
} else {
if (forceClose == 1) {
tunnelConfig.getIkeSession().kill();
} else {
tunnelConfig.getIkeSession().close();
}
}
int numClosed = closePendingRequestsForApn(apnName);
if (numClosed > 0) {
Log.d(TAG, "Closed " + numClosed + " pending requests for apn: " + apnName);
}
break;
case EVENT_IPSEC_TRANSFORM_CREATED:
IpsecTransformData transformData = (IpsecTransformData) msg.obj;
apnName = transformData.getApnName();
IpSecManager ipSecManager = mContext.getSystemService(IpSecManager.class);
tunnelConfig = mApnNameToTunnelConfig.get(apnName);
if (tunnelConfig.getIface() == null) {
if (mLocalAddresses == null
|| mLocalAddresses.size() == 0
|| ipSecManager == null) {
Log.e(TAG, "No local addresses found.");
closeIkeSession(
apnName, new IwlanError(IwlanError.TUNNEL_TRANSFORM_FAILED));
return;
}
try {
if (mEpdgAddress instanceof Inet4Address
&& mProtoFilter == EpdgSelector.PROTO_FILTER_IPV6) {
mLocalAddresses =
IwlanHelper.getStackedAddressesForNetwork(
mNetwork, mContext);
}
InetAddress localAddress =
(mEpdgAddress instanceof Inet4Address)
? IwlanHelper.getIpv4Address(mLocalAddresses)
: IwlanHelper.getIpv6Address(mLocalAddresses);
Log.d(TAG, "Local address = " + localAddress);
tunnelConfig.setIface(
ipSecManager.createIpSecTunnelInterface(
localAddress, mEpdgAddress, mNetwork));
} catch (IpSecManager.ResourceUnavailableException | IOException e) {
Log.e(TAG, "Failed to create tunnel interface. " + e);
closeIkeSession(
apnName, new IwlanError(IwlanError.TUNNEL_TRANSFORM_FAILED));
return;
}
}
try {
ipSecManager.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));
}
break;
case EVENT_IPSEC_TRANSFORM_DELETED:
IpSecTransform transform = (IpSecTransform) msg.obj;
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;
}
tunnelConfig.setError(sessionClosedData.mIwlanError);
tunnelConfig.getIkeSession().close();
break;
case EVENT_IKE_SESSION_OPENED:
IkeSessionOpenedData ikeSessionOpenedData = (IkeSessionOpenedData) msg.obj;
IkeSessionConfiguration sessionConfiguration =
ikeSessionOpenedData.mIkeSessionConfiguration;
tunnelConfig = mApnNameToTunnelConfig.get(ikeSessionOpenedData.mApnName);
tunnelConfig.setPcscfAddrList(sessionConfiguration.getPcscfServers());
boolean enabledFastReauth =
(boolean)
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 != null && 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 = ikeSessionConnectionInfoData.mIkeSessionConnectionInfo.getNetwork();
apnName = ikeSessionConnectionInfoData.mApnName;
ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
if (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) {
mLocalAddresses = getAddressForNetwork(mNetwork, mContext);
if (mLocalAddresses == null || mLocalAddresses.size() == 0) {
Log.e(TAG, "No local addresses available.");
failAllPendingRequests(
new IwlanError(IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED));
return;
}
mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4V6;
if (!IwlanHelper.hasIpv6Address(mLocalAddresses)) {
mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4;
}
if (!IwlanHelper.hasIpv4Address(mLocalAddresses)) {
mProtoFilter = EpdgSelector.PROTO_FILTER_IPV6;
}
EpdgSelector epdgSelector = getEpdgSelector();
IwlanError epdgError =
epdgSelector.getValidatedServerList(
++mTransactionId,
mProtoFilter,
setupRequest.isRoaming(),
setupRequest.isEmergency(),
mNetwork,
mSelectorCallback);
if (epdgError.getErrorType() != IwlanError.NO_ERROR) {
Log.e(TAG, "Epdg address selection failed with error:" + epdgError);
failAllPendingRequests(epdgError);
}
}
@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() == apnName) {
requestWrapper
.getTunnelCallback()
.onClosed(apnName, new IwlanError(IwlanError.NO_ERROR));
numRequestsClosed++;
} else {
mPendingBringUpRequests.add(requestWrapper);
}
}
return numRequestsClosed;
}
@VisibleForTesting
void validateAndSetEpdgAddress(List<InetAddress> selectorResultList) {
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();
}
@VisibleForTesting
void resetTunnelManagerState() {
Log.d(TAG, "resetTunnelManagerState");
mEpdgAddress = null;
setHasConnectedToEpdg(false);
mNetwork = null;
mPendingBringUpRequests = new LinkedList<>();
mApnNameToTunnelConfig = new ConcurrentHashMap<>();
mLocalAddresses = null;
}
private void serviceAllPendingRequests() {
while (!mPendingBringUpRequests.isEmpty()) {
Log.d(TAG, "serviceAllPendingRequests");
TunnelRequestWrapper request = mPendingBringUpRequests.remove();
onBringUpTunnel(request.getSetupRequest(), request.getTunnelCallback());
}
}
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);
}
}
// 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 String mApnName;
private UpdateNetworkWrapper(Network network, String apnName) {
mNetwork = network;
mApnName = apnName;
}
public String getApnName() {
return mApnName;
}
public Network getNetwork() {
return mNetwork;
}
}
// 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 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 {
final String mApnName;
final List<InetAddress> mInternalDnsServers;
final List<LinkAddress> mInternalAddresses;
private TunnelOpenedData(
String apnName,
List<InetAddress> internalDnsServers,
List<LinkAddress> internalAddresses) {
mApnName = apnName;
mInternalDnsServers = internalDnsServers;
mInternalAddresses = internalAddresses;
}
}
// Data received from IkeSessionStateMachine on successful EVENT_IKE_SESSION_OPENED.
private static final class IkeSessionOpenedData {
final String mApnName;
final IkeSessionConfiguration mIkeSessionConfiguration;
private IkeSessionOpenedData(
String apnName, IkeSessionConfiguration ikeSessionConfiguration) {
mApnName = apnName;
mIkeSessionConfiguration = ikeSessionConfiguration;
}
}
private static final class IkeSessionConnectionInfoData {
final String mApnName;
final IkeSessionConnectionInfo mIkeSessionConnectionInfo;
private IkeSessionConnectionInfoData(
String apnName, IkeSessionConnectionInfo ikeSessionConnectionInfo) {
mApnName = apnName;
mIkeSessionConnectionInfo = ikeSessionConnectionInfo;
}
}
private static final class Ike3gppDataReceived {
final String mApnName;
final List<Ike3gppData> mIke3gppData;
private Ike3gppDataReceived(String apnName, List<Ike3gppData> ike3gppData) {
mApnName = apnName;
mIke3gppData = ike3gppData;
}
}
// Data received from IkeSessionStateMachine if either IKE session or Child session have been
// closed, normally or exceptionally.
private static final class SessionClosedData {
final String mApnName;
final IwlanError mIwlanError;
private SessionClosedData(String apnName, IwlanError iwlanError) {
mApnName = apnName;
mIwlanError = iwlanError;
}
}
public void releaseInstance() {
mHandlerThread.quit();
mTunnelManagerInstances.remove(mSlotId);
}
private static final class IpsecTransformData {
private final IpSecTransform mTransform;
private final int mDirection;
private final String mApnName;
private IpsecTransformData(IpSecTransform transform, int direction, String apnName) {
mTransform = transform;
mDirection = direction;
mApnName = apnName;
}
public IpSecTransform getTransform() {
return mTransform;
}
public int getDirection() {
return mDirection;
}
public String getApnName() {
return mApnName;
}
}
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 int[] getRetransmissionTimeoutsFromConfig() {
int[] timeList =
(int[]) getConfig(CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY);
boolean isValid = true;
if (timeList == null
|| timeList.length == 0
|| timeList.length > IKE_RETRANS_MAX_ATTEMPTS_MAX) {
isValid = false;
}
for (int time : timeList) {
if (time < IKE_RETRANS_TIMEOUT_MS_MIN || time > IKE_RETRANS_TIMEOUT_MS_MAX) {
isValid = false;
break;
}
}
if (!isValid) {
timeList =
(int[])
IwlanHelper.getDefaultConfig(
CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY);
}
Log.d(TAG, "getRetransmissionTimeoutsFromConfig: " + Arrays.toString(timeList));
return timeList;
}
private int getDpdDelayFromConfig() {
int dpdDelay = (int) getConfig(CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT);
if (dpdDelay < IKE_DPD_DELAY_SEC_MIN || dpdDelay > IKE_DPD_DELAY_SEC_MAX) {
dpdDelay =
(int)
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 backoffTimerByte 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 mins
1L * 60L * 60L, // 1 hour
10L * 60L * 60L, // 10 hours
2L, // 2 seconds
30L, // 30 seconds
1L * 60L, // 1 minute
1L * 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) {
String apnName = setupRequest.apnName();
return apnName;
}
@VisibleForTesting
void putApnNameToTunnelConfig(
String apnName,
IkeSession ikeSession,
TunnelCallback tunnelCallback,
InetAddress srcIpv6Addr,
int srcIPv6AddrPrefixLen) {
mApnNameToTunnelConfig.put(
apnName,
new TunnelConfig(ikeSession, tunnelCallback, srcIpv6Addr, srcIPv6AddrPrefixLen));
Log.d(TAG, "Added apn: " + apnName + " to TunnelConfig");
}
@VisibleForTesting
boolean isTunnelConfigContainExistApn(String apnName) {
boolean ret = mApnNameToTunnelConfig.containsKey(apnName);
return ret;
}
@VisibleForTesting
List<InetAddress> getAddressForNetwork(Network network, Context context) {
List<InetAddress> ret = IwlanHelper.getAddressesForNetwork(network, context);
return ret;
}
@VisibleForTesting
EpdgSelector.EpdgSelectorCallback getSelectorCallback() {
return mSelectorCallback;
}
@VisibleForTesting
IkeSessionCreator getIkeSessionCreator() {
return mIkeSessionCreator;
}
@VisibleForTesting
void sendSelectionRequestComplete(
ArrayList<InetAddress> validIPList, IwlanError result, int transactionId) {
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);
}
@VisibleForTesting
TmIkeSessionCallback getTmIkeSessionCallback(String apnName) {
return new TmIkeSessionCallback(apnName);
}
@VisibleForTesting
void setHasConnectedToEpdg(boolean value) {
mHasConnectedToEpdg = value;
}
@VisibleForTesting
TunnelConfig getTunnelConfigForApn(String apnName) {
return mApnNameToTunnelConfig.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);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("---- EpdgTunnelManager ----");
pw.println("mHasConnectedToEpdg: " + mHasConnectedToEpdg);
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("---------------------------");
}
}