Merge "Implement Ikev2VpnRunner"
This commit is contained in:
@@ -51,7 +51,7 @@ import java.net.Socket;
|
||||
*
|
||||
* <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
|
||||
* transport mode security associations and apply them to individual sockets. Applications looking
|
||||
* to create a VPN should use {@link VpnService}.
|
||||
* to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
|
||||
* Internet Protocol</a>
|
||||
|
||||
@@ -1557,16 +1557,16 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
}
|
||||
|
||||
checkNotNull(callingPackage, "Null calling package cannot create IpSec tunnels");
|
||||
switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
|
||||
case AppOpsManager.MODE_DEFAULT:
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
|
||||
break;
|
||||
case AppOpsManager.MODE_ALLOWED:
|
||||
return;
|
||||
default:
|
||||
throw new SecurityException("Request to ignore AppOps for non-legacy API");
|
||||
|
||||
// OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
|
||||
// server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
|
||||
// permission or is the System Server.
|
||||
if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
|
||||
TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
|
||||
return;
|
||||
}
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
|
||||
}
|
||||
|
||||
private void createOrUpdateTransform(
|
||||
|
||||
@@ -48,8 +48,12 @@ import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.INetworkManagementEventObserver;
|
||||
import android.net.Ikev2VpnProfile;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.IpSecManager;
|
||||
import android.net.IpSecManager.IpSecTunnelInterface;
|
||||
import android.net.IpSecManager.UdpEncapsulationSocket;
|
||||
import android.net.IpSecTransform;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.LocalSocket;
|
||||
@@ -65,6 +69,12 @@ import android.net.RouteInfo;
|
||||
import android.net.UidRange;
|
||||
import android.net.VpnManager;
|
||||
import android.net.VpnService;
|
||||
import android.net.ipsec.ike.ChildSessionCallback;
|
||||
import android.net.ipsec.ike.ChildSessionConfiguration;
|
||||
import android.net.ipsec.ike.ChildSessionParams;
|
||||
import android.net.ipsec.ike.IkeSession;
|
||||
import android.net.ipsec.ike.IkeSessionCallback;
|
||||
import android.net.ipsec.ike.IkeSessionParams;
|
||||
import android.os.Binder;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
@@ -113,6 +123,7 @@ import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -122,6 +133,9 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
@@ -176,14 +190,14 @@ public class Vpn {
|
||||
|
||||
private final Context mContext;
|
||||
private final NetworkInfo mNetworkInfo;
|
||||
private String mPackage;
|
||||
@VisibleForTesting protected String mPackage;
|
||||
private int mOwnerUID;
|
||||
private boolean mIsPackageTargetingAtLeastQ;
|
||||
private String mInterface;
|
||||
private Connection mConnection;
|
||||
|
||||
/** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
|
||||
private VpnRunner mVpnRunner;
|
||||
@VisibleForTesting protected VpnRunner mVpnRunner;
|
||||
|
||||
private PendingIntent mStatusIntent;
|
||||
private volatile boolean mEnableTeardown = true;
|
||||
@@ -196,6 +210,7 @@ public class Vpn {
|
||||
@VisibleForTesting
|
||||
protected final NetworkCapabilities mNetworkCapabilities;
|
||||
private final SystemServices mSystemServices;
|
||||
private final Ikev2SessionCreator mIkev2SessionCreator;
|
||||
|
||||
/**
|
||||
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
|
||||
@@ -238,17 +253,20 @@ public class Vpn {
|
||||
|
||||
public Vpn(Looper looper, Context context, INetworkManagementService netService,
|
||||
@UserIdInt int userHandle) {
|
||||
this(looper, context, netService, userHandle, new SystemServices(context));
|
||||
this(looper, context, netService, userHandle,
|
||||
new SystemServices(context), new Ikev2SessionCreator());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected Vpn(Looper looper, Context context, INetworkManagementService netService,
|
||||
int userHandle, SystemServices systemServices) {
|
||||
int userHandle, SystemServices systemServices,
|
||||
Ikev2SessionCreator ikev2SessionCreator) {
|
||||
mContext = context;
|
||||
mNetd = netService;
|
||||
mUserHandle = userHandle;
|
||||
mLooper = looper;
|
||||
mSystemServices = systemServices;
|
||||
mIkev2SessionCreator = ikev2SessionCreator;
|
||||
|
||||
mPackage = VpnConfig.LEGACY_VPN;
|
||||
mOwnerUID = getAppUid(mPackage, mUserHandle);
|
||||
@@ -749,8 +767,9 @@ public class Vpn {
|
||||
|
||||
private boolean isCurrentPreparedPackage(String packageName) {
|
||||
// We can't just check that packageName matches mPackage, because if the app was uninstalled
|
||||
// and reinstalled it will no longer be prepared. Instead check the UID.
|
||||
return getAppUid(packageName, mUserHandle) == mOwnerUID;
|
||||
// and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
|
||||
// calling package may not be the same as the prepared package. Check both UID and package.
|
||||
return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
|
||||
}
|
||||
|
||||
/** Prepare the VPN for the given package. Does not perform permission checks. */
|
||||
@@ -975,7 +994,11 @@ public class Vpn {
|
||||
}
|
||||
lp.setDomains(buffer.toString().trim());
|
||||
|
||||
// TODO: Stop setting the MTU in jniCreate and set it here.
|
||||
if (mConfig.mtu > 0) {
|
||||
lp.setMtu(mConfig.mtu);
|
||||
}
|
||||
|
||||
// TODO: Stop setting the MTU in jniCreate
|
||||
|
||||
return lp;
|
||||
}
|
||||
@@ -2000,30 +2023,369 @@ public class Vpn {
|
||||
protected abstract void exit();
|
||||
}
|
||||
|
||||
private class IkeV2VpnRunner extends VpnRunner {
|
||||
private static final String TAG = "IkeV2VpnRunner";
|
||||
interface IkeV2VpnRunnerCallback {
|
||||
void onDefaultNetworkChanged(@NonNull Network network);
|
||||
|
||||
private final IpSecManager mIpSecManager;
|
||||
private final VpnProfile mProfile;
|
||||
void onChildOpened(
|
||||
@NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
|
||||
|
||||
IkeV2VpnRunner(VpnProfile profile) {
|
||||
void onChildTransformCreated(
|
||||
@NonNull Network network, @NonNull IpSecTransform transform, int direction);
|
||||
|
||||
void onSessionLost(@NonNull Network network);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class managing IKEv2/IPsec VPN connectivity
|
||||
*
|
||||
* <p>The IKEv2 VPN will listen to, and run based on the lifecycle of Android's default Network.
|
||||
* As a new default is selected, old IKE sessions will be torn down, and a new one will be
|
||||
* started.
|
||||
*
|
||||
* <p>This class uses locking minimally - the Vpn instance lock is only ever held when fields of
|
||||
* the outer class are modified. As such, care must be taken to ensure that no calls are added
|
||||
* that might modify the outer class' state without acquiring a lock.
|
||||
*
|
||||
* <p>The overall structure of the Ikev2VpnRunner is as follows:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Upon startup, a NetworkRequest is registered with ConnectivityManager. This is called
|
||||
* any time a new default network is selected
|
||||
* <li>When a new default is connected, an IKE session is started on that Network. If there
|
||||
* were any existing IKE sessions on other Networks, they are torn down before starting
|
||||
* the new IKE session
|
||||
* <li>Upon establishment, the onChildTransformCreated() callback is called twice, one for
|
||||
* each direction, and finally onChildOpened() is called
|
||||
* <li>Upon the onChildOpened() call, the VPN is fully set up.
|
||||
* <li>Subsequent Network changes result in new onDefaultNetworkChanged() callbacks. See (2).
|
||||
* </ol>
|
||||
*/
|
||||
class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
|
||||
@NonNull private static final String TAG = "IkeV2VpnRunner";
|
||||
|
||||
@NonNull private final IpSecManager mIpSecManager;
|
||||
@NonNull private final Ikev2VpnProfile mProfile;
|
||||
@NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
|
||||
|
||||
/**
|
||||
* Executor upon which ALL callbacks must be run.
|
||||
*
|
||||
* <p>This executor MUST be a single threaded executor, in order to ensure the consistency
|
||||
* of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
|
||||
* virtue of everything being serialized on this executor.
|
||||
*/
|
||||
@NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
/** Signal to ensure shutdown is honored even if a new Network is connected. */
|
||||
private boolean mIsRunning = true;
|
||||
|
||||
@Nullable private UdpEncapsulationSocket mEncapSocket;
|
||||
@Nullable private IpSecTunnelInterface mTunnelIface;
|
||||
@Nullable private IkeSession mSession;
|
||||
@Nullable private Network mActiveNetwork;
|
||||
|
||||
IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
|
||||
super(TAG);
|
||||
mProfile = profile;
|
||||
|
||||
// TODO: move this to startVpnRunnerPrivileged()
|
||||
mConfig = new VpnConfig();
|
||||
mIpSecManager = mContext.getSystemService(IpSecManager.class);
|
||||
mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
|
||||
mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO: Build IKE config, start IKE session
|
||||
// Explicitly use only the network that ConnectivityService thinks is the "best." In
|
||||
// other words, only ever use the currently selected default network. This does mean
|
||||
// that in both onLost() and onConnected(), any old sessions MUST be torn down. This
|
||||
// does NOT include VPNs.
|
||||
final ConnectivityManager cm = ConnectivityManager.from(mContext);
|
||||
cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
|
||||
}
|
||||
|
||||
private boolean isActiveNetwork(@Nullable Network network) {
|
||||
return Objects.equals(mActiveNetwork, network) && mIsRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an IKE Child session has been opened, signalling completion of the startup.
|
||||
*
|
||||
* <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
|
||||
* thread in order to ensure consistency of the Ikev2VpnRunner fields.
|
||||
*/
|
||||
public void onChildOpened(
|
||||
@NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
|
||||
if (!isActiveNetwork(network)) {
|
||||
Log.d(TAG, "onOpened called for obsolete network " + network);
|
||||
|
||||
// Do nothing; this signals that either: (1) a new/better Network was found,
|
||||
// and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
|
||||
// IKE session was already shut down (exited, or an error was encountered somewhere
|
||||
// else). In both cases, all resources and sessions are torn down via
|
||||
// resetIkeState().
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final String interfaceName = mTunnelIface.getInterfaceName();
|
||||
final int maxMtu = mProfile.getMaxMtu();
|
||||
final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
|
||||
|
||||
final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
|
||||
childConfig.getOutboundTrafficSelectors());
|
||||
for (final LinkAddress address : internalAddresses) {
|
||||
mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
|
||||
}
|
||||
|
||||
final NetworkAgent networkAgent;
|
||||
final LinkProperties lp;
|
||||
|
||||
synchronized (Vpn.this) {
|
||||
mInterface = interfaceName;
|
||||
mConfig.mtu = maxMtu;
|
||||
mConfig.interfaze = mInterface;
|
||||
|
||||
mConfig.addresses.clear();
|
||||
mConfig.addresses.addAll(internalAddresses);
|
||||
|
||||
mConfig.routes.clear();
|
||||
mConfig.routes.addAll(newRoutes);
|
||||
|
||||
// TODO: Add DNS servers from negotiation
|
||||
|
||||
networkAgent = mNetworkAgent;
|
||||
|
||||
// The below must be done atomically with the mConfig update, otherwise
|
||||
// isRunningLocked() will be racy.
|
||||
if (networkAgent == null) {
|
||||
if (isSettingsVpnLocked()) {
|
||||
prepareStatusIntent();
|
||||
}
|
||||
agentConnect();
|
||||
return; // Link properties are already sent.
|
||||
}
|
||||
|
||||
lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
|
||||
}
|
||||
|
||||
networkAgent.sendLinkProperties(lp);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Error in ChildOpened for network " + network, e);
|
||||
onSessionLost(network);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an IPsec transform has been created, and should be applied.
|
||||
*
|
||||
* <p>This method is called multiple times over the lifetime of an IkeSession (or default
|
||||
* network), and is MUST always be called on the mExecutor thread in order to ensure
|
||||
* consistency of the Ikev2VpnRunner fields.
|
||||
*/
|
||||
public void onChildTransformCreated(
|
||||
@NonNull Network network, @NonNull IpSecTransform transform, int direction) {
|
||||
if (!isActiveNetwork(network)) {
|
||||
Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
|
||||
|
||||
// Do nothing; this signals that either: (1) a new/better Network was found,
|
||||
// and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
|
||||
// IKE session was already shut down (exited, or an error was encountered somewhere
|
||||
// else). In both cases, all resources and sessions are torn down via
|
||||
// resetIkeState().
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Transforms do not need to be persisted; the IkeSession will keep
|
||||
// them alive for us
|
||||
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Transform application failed for network " + network, e);
|
||||
onSessionLost(network);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new default network is connected.
|
||||
*
|
||||
* <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
|
||||
* state in the process, and starting a new IkeSession instance.
|
||||
*
|
||||
* <p>This method is called multiple times over the lifetime of the Ikev2VpnRunner, and is
|
||||
* called on the ConnectivityService thread. Thus, the actual work MUST be proxied to the
|
||||
* mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
|
||||
*/
|
||||
public void onDefaultNetworkChanged(@NonNull Network network) {
|
||||
Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
|
||||
|
||||
// Proxy to the Ikev2VpnRunner (single-thread) executor to ensure consistency in lieu
|
||||
// of locking.
|
||||
mExecutor.execute(() -> {
|
||||
try {
|
||||
if (!mIsRunning) {
|
||||
Log.d(TAG, "onDefaultNetworkChanged after exit");
|
||||
return; // VPN has been shut down.
|
||||
}
|
||||
|
||||
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
|
||||
// (non-default) network, and start the new one.
|
||||
resetIkeState();
|
||||
mActiveNetwork = network;
|
||||
|
||||
// TODO(b/149356682): Update this based on new IKE API
|
||||
mEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
|
||||
|
||||
// TODO(b/149356682): Update this based on new IKE API
|
||||
final IkeSessionParams ikeSessionParams =
|
||||
VpnIkev2Utils.buildIkeSessionParams(mProfile, mEncapSocket);
|
||||
final ChildSessionParams childSessionParams =
|
||||
VpnIkev2Utils.buildChildSessionParams();
|
||||
|
||||
// TODO: Remove the need for adding two unused addresses with
|
||||
// IPsec tunnels.
|
||||
mTunnelIface =
|
||||
mIpSecManager.createIpSecTunnelInterface(
|
||||
ikeSessionParams.getServerAddress() /* unused */,
|
||||
ikeSessionParams.getServerAddress() /* unused */,
|
||||
network);
|
||||
mNetd.setInterfaceUp(mTunnelIface.getInterfaceName());
|
||||
|
||||
// Socket must be bound to prevent network switches from causing
|
||||
// the IKE teardown to fail/timeout.
|
||||
// TODO(b/149356682): Update this based on new IKE API
|
||||
network.bindSocket(mEncapSocket.getFileDescriptor());
|
||||
|
||||
mSession = mIkev2SessionCreator.createIkeSession(
|
||||
mContext,
|
||||
ikeSessionParams,
|
||||
childSessionParams,
|
||||
mExecutor,
|
||||
new VpnIkev2Utils.IkeSessionCallbackImpl(
|
||||
TAG, IkeV2VpnRunner.this, network),
|
||||
new VpnIkev2Utils.ChildSessionCallbackImpl(
|
||||
TAG, IkeV2VpnRunner.this, network));
|
||||
Log.d(TAG, "Ike Session started for network " + network);
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
|
||||
onSessionLost(network);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles loss of a session
|
||||
*
|
||||
* <p>The loss of a session might be due to an onLost() call, the IKE session getting torn
|
||||
* down for any reason, or an error in updating state (transform application, VPN setup)
|
||||
*
|
||||
* <p>This method MUST always be called on the mExecutor thread in order to ensure
|
||||
* consistency of the Ikev2VpnRunner fields.
|
||||
*/
|
||||
public void onSessionLost(@NonNull Network network) {
|
||||
if (!isActiveNetwork(network)) {
|
||||
Log.d(TAG, "onSessionLost() called for obsolete network " + network);
|
||||
|
||||
// Do nothing; this signals that either: (1) a new/better Network was found,
|
||||
// and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
|
||||
// IKE session was already shut down (exited, or an error was encountered somewhere
|
||||
// else). In both cases, all resources and sessions are torn down via
|
||||
// onSessionLost() and resetIkeState().
|
||||
return;
|
||||
}
|
||||
|
||||
mActiveNetwork = null;
|
||||
|
||||
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
|
||||
// (Mirrors VpnService behavior)
|
||||
Log.d(TAG, "Resetting state for network: " + network);
|
||||
|
||||
synchronized (Vpn.this) {
|
||||
// Since this method handles non-fatal errors only, set mInterface to null to
|
||||
// prevent the NetworkManagementEventObserver from killing this VPN based on the
|
||||
// interface going down (which we expect).
|
||||
mInterface = null;
|
||||
mConfig.interfaze = null;
|
||||
|
||||
// Set as unroutable to prevent traffic leaking while the interface is down.
|
||||
if (mConfig != null && mConfig.routes != null) {
|
||||
final List<RouteInfo> oldRoutes = new ArrayList<>(mConfig.routes);
|
||||
|
||||
mConfig.routes.clear();
|
||||
for (final RouteInfo route : oldRoutes) {
|
||||
mConfig.routes.add(new RouteInfo(route.getDestination(), RTN_UNREACHABLE));
|
||||
}
|
||||
if (mNetworkAgent != null) {
|
||||
mNetworkAgent.sendLinkProperties(makeLinkProperties());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resetIkeState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up all IKE state
|
||||
*
|
||||
* <p>This method MUST always be called on the mExecutor thread in order to ensure
|
||||
* consistency of the Ikev2VpnRunner fields.
|
||||
*/
|
||||
private void resetIkeState() {
|
||||
if (mTunnelIface != null) {
|
||||
// No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
|
||||
mTunnelIface.close();
|
||||
mTunnelIface = null;
|
||||
}
|
||||
if (mSession != null) {
|
||||
mSession.kill(); // Kill here to make sure all resources are released immediately
|
||||
mSession = null;
|
||||
}
|
||||
|
||||
// TODO(b/149356682): Update this based on new IKE API
|
||||
if (mEncapSocket != null) {
|
||||
try {
|
||||
mEncapSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to close encap socket", e);
|
||||
}
|
||||
mEncapSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers cleanup of outer class' state
|
||||
*
|
||||
* <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
|
||||
*/
|
||||
private void cleanupVpnState() {
|
||||
synchronized (Vpn.this) {
|
||||
agentDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up all Ikev2VpnRunner internal state
|
||||
*
|
||||
* <p>This method MUST always be called on the mExecutor thread in order to ensure
|
||||
* consistency of the Ikev2VpnRunner fields.
|
||||
*/
|
||||
private void shutdownVpnRunner() {
|
||||
mActiveNetwork = null;
|
||||
mIsRunning = false;
|
||||
|
||||
resetIkeState();
|
||||
|
||||
final ConnectivityManager cm = ConnectivityManager.from(mContext);
|
||||
cm.unregisterNetworkCallback(mNetworkCallback);
|
||||
|
||||
mExecutor.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
// TODO: Teardown IKE session & any resources.
|
||||
agentDisconnect();
|
||||
// Cleanup outer class' state immediately, otherwise race conditions may ensue.
|
||||
cleanupVpnState();
|
||||
|
||||
mExecutor.execute(() -> {
|
||||
shutdownVpnRunner();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2484,12 +2846,46 @@ public class Vpn {
|
||||
throw new IllegalArgumentException("No profile found for " + packageName);
|
||||
}
|
||||
|
||||
startVpnProfilePrivileged(profile);
|
||||
startVpnProfilePrivileged(profile, packageName);
|
||||
});
|
||||
}
|
||||
|
||||
private void startVpnProfilePrivileged(@NonNull VpnProfile profile) {
|
||||
// TODO: Start PlatformVpnRunner
|
||||
private void startVpnProfilePrivileged(
|
||||
@NonNull VpnProfile profile, @NonNull String packageName) {
|
||||
// Ensure that no other previous instance is running.
|
||||
if (mVpnRunner != null) {
|
||||
mVpnRunner.exit();
|
||||
mVpnRunner = null;
|
||||
}
|
||||
updateState(DetailedState.CONNECTING, "startPlatformVpn");
|
||||
|
||||
try {
|
||||
// Build basic config
|
||||
mConfig = new VpnConfig();
|
||||
mConfig.user = packageName;
|
||||
mConfig.isMetered = profile.isMetered;
|
||||
mConfig.startTime = SystemClock.elapsedRealtime();
|
||||
mConfig.proxyInfo = profile.proxy;
|
||||
|
||||
switch (profile.type) {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||
mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
|
||||
mVpnRunner.start();
|
||||
break;
|
||||
default:
|
||||
updateState(DetailedState.FAILED, "Invalid platform VPN type");
|
||||
Log.d(TAG, "Unknown VPN profile type: " + profile.type);
|
||||
break;
|
||||
}
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
// Reset mConfig
|
||||
mConfig = null;
|
||||
|
||||
updateState(DetailedState.FAILED, "VPN startup failed");
|
||||
throw new IllegalArgumentException("VPN startup failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2503,13 +2899,37 @@ public class Vpn {
|
||||
public synchronized void stopVpnProfile(@NonNull String packageName) {
|
||||
checkNotNull(packageName, "No package name provided");
|
||||
|
||||
// To stop the VPN profile, the caller must be the current prepared package. Otherwise,
|
||||
// the app is not prepared, and we can just return.
|
||||
if (!isCurrentPreparedPackage(packageName)) {
|
||||
// TODO: Also check to make sure that the running VPN is a VPN profile.
|
||||
// To stop the VPN profile, the caller must be the current prepared package and must be
|
||||
// running an Ikev2VpnProfile.
|
||||
if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
|
||||
return;
|
||||
}
|
||||
|
||||
prepareInternal(VpnConfig.LEGACY_VPN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to allow testing
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class Ikev2SessionCreator {
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* Copyright (C) 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.android.server.connectivity;
|
||||
|
||||
import static android.net.ConnectivityManager.NetworkCallback;
|
||||
import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
|
||||
import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
|
||||
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
|
||||
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
|
||||
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
|
||||
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
|
||||
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
|
||||
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
|
||||
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
|
||||
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
|
||||
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
|
||||
import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
|
||||
import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
|
||||
import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
|
||||
import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
|
||||
import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.Ikev2VpnProfile;
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.IpSecManager.UdpEncapsulationSocket;
|
||||
import android.net.IpSecTransform;
|
||||
import android.net.Network;
|
||||
import android.net.RouteInfo;
|
||||
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.IkeIpv4AddrIdentification;
|
||||
import android.net.ipsec.ike.IkeIpv6AddrIdentification;
|
||||
import android.net.ipsec.ike.IkeKeyIdIdentification;
|
||||
import android.net.ipsec.ike.IkeRfc822AddrIdentification;
|
||||
import android.net.ipsec.ike.IkeSaProposal;
|
||||
import android.net.ipsec.ike.IkeSessionCallback;
|
||||
import android.net.ipsec.ike.IkeSessionConfiguration;
|
||||
import android.net.ipsec.ike.IkeSessionParams;
|
||||
import android.net.ipsec.ike.IkeTrafficSelector;
|
||||
import android.net.ipsec.ike.TunnelModeChildSessionParams;
|
||||
import android.net.ipsec.ike.exceptions.IkeException;
|
||||
import android.net.ipsec.ike.exceptions.IkeProtocolException;
|
||||
import android.net.util.IpRange;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.internal.util.HexDump;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to build and convert IKEv2/IPsec parameters.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class VpnIkev2Utils {
|
||||
static IkeSessionParams buildIkeSessionParams(
|
||||
@NonNull Ikev2VpnProfile profile, @NonNull UdpEncapsulationSocket socket) {
|
||||
// TODO(b/149356682): Update this based on new IKE API. Only numeric addresses supported
|
||||
// until then. All others throw IAE (caught by caller).
|
||||
final InetAddress serverAddr = InetAddresses.parseNumericAddress(profile.getServerAddr());
|
||||
final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
|
||||
final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
|
||||
|
||||
// TODO(b/149356682): Update this based on new IKE API.
|
||||
final IkeSessionParams.Builder ikeOptionsBuilder =
|
||||
new IkeSessionParams.Builder()
|
||||
.setServerAddress(serverAddr)
|
||||
.setUdpEncapsulationSocket(socket)
|
||||
.setLocalIdentification(localId)
|
||||
.setRemoteIdentification(remoteId);
|
||||
setIkeAuth(profile, ikeOptionsBuilder);
|
||||
|
||||
for (final IkeSaProposal ikeProposal : getIkeSaProposals()) {
|
||||
ikeOptionsBuilder.addSaProposal(ikeProposal);
|
||||
}
|
||||
|
||||
return ikeOptionsBuilder.build();
|
||||
}
|
||||
|
||||
static ChildSessionParams buildChildSessionParams() {
|
||||
final TunnelModeChildSessionParams.Builder childOptionsBuilder =
|
||||
new TunnelModeChildSessionParams.Builder();
|
||||
|
||||
for (final ChildSaProposal childProposal : getChildSaProposals()) {
|
||||
childOptionsBuilder.addSaProposal(childProposal);
|
||||
}
|
||||
|
||||
childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET);
|
||||
childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET6);
|
||||
childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET);
|
||||
childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET6);
|
||||
|
||||
return childOptionsBuilder.build();
|
||||
}
|
||||
|
||||
private static void setIkeAuth(
|
||||
@NonNull Ikev2VpnProfile profile, @NonNull IkeSessionParams.Builder builder) {
|
||||
switch (profile.getType()) {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
final EapSessionConfig eapConfig =
|
||||
new EapSessionConfig.Builder()
|
||||
.setEapMsChapV2Config(profile.getUsername(), profile.getPassword())
|
||||
.build();
|
||||
builder.setAuthEap(profile.getServerRootCaCert(), eapConfig);
|
||||
break;
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
builder.setAuthPsk(profile.getPresharedKey());
|
||||
break;
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||
builder.setAuthDigitalSignature(
|
||||
profile.getServerRootCaCert(),
|
||||
profile.getUserCert(),
|
||||
profile.getRsaPrivateKey());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown auth method set");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<IkeSaProposal> getIkeSaProposals() {
|
||||
// TODO: filter this based on allowedAlgorithms
|
||||
final List<IkeSaProposal> proposals = new ArrayList<>();
|
||||
|
||||
// Encryption Algorithms: Currently only AES_CBC is supported.
|
||||
final IkeSaProposal.Builder normalModeBuilder = new IkeSaProposal.Builder();
|
||||
|
||||
// Currently only AES_CBC is supported.
|
||||
normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
|
||||
normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
|
||||
normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
|
||||
|
||||
// Authentication/Integrity Algorithms
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
|
||||
|
||||
// Add AEAD options
|
||||
final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder();
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
|
||||
|
||||
// Add dh, prf for both builders
|
||||
for (final IkeSaProposal.Builder builder : Arrays.asList(normalModeBuilder, aeadBuilder)) {
|
||||
builder.addDhGroup(DH_GROUP_2048_BIT_MODP);
|
||||
builder.addDhGroup(DH_GROUP_1024_BIT_MODP);
|
||||
builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC);
|
||||
builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
|
||||
}
|
||||
|
||||
proposals.add(normalModeBuilder.build());
|
||||
proposals.add(aeadBuilder.build());
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private static List<ChildSaProposal> getChildSaProposals() {
|
||||
// TODO: filter this based on allowedAlgorithms
|
||||
final List<ChildSaProposal> proposals = new ArrayList<>();
|
||||
|
||||
// Add non-AEAD options
|
||||
final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();
|
||||
|
||||
// Encryption Algorithms: Currently only AES_CBC is supported.
|
||||
normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
|
||||
normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
|
||||
normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
|
||||
|
||||
// Authentication/Integrity Algorithms
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
|
||||
normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
|
||||
|
||||
// Add AEAD options
|
||||
final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder();
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
|
||||
aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
|
||||
|
||||
proposals.add(normalModeBuilder.build());
|
||||
proposals.add(aeadBuilder.build());
|
||||
return proposals;
|
||||
}
|
||||
|
||||
static class IkeSessionCallbackImpl implements IkeSessionCallback {
|
||||
private final String mTag;
|
||||
private final Vpn.IkeV2VpnRunnerCallback mCallback;
|
||||
private final Network mNetwork;
|
||||
|
||||
IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
|
||||
mTag = tag;
|
||||
mCallback = callback;
|
||||
mNetwork = network;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
|
||||
Log.d(mTag, "IkeOpened for network " + mNetwork);
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
Log.d(mTag, "IkeClosed for network " + mNetwork);
|
||||
mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosedExceptionally(@NonNull IkeException exception) {
|
||||
Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
|
||||
mCallback.onSessionLost(mNetwork);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull IkeProtocolException exception) {
|
||||
Log.d(mTag, "IkeError for network " + mNetwork, exception);
|
||||
// Non-fatal, log and continue.
|
||||
}
|
||||
}
|
||||
|
||||
static class ChildSessionCallbackImpl implements ChildSessionCallback {
|
||||
private final String mTag;
|
||||
private final Vpn.IkeV2VpnRunnerCallback mCallback;
|
||||
private final Network mNetwork;
|
||||
|
||||
ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
|
||||
mTag = tag;
|
||||
mCallback = callback;
|
||||
mNetwork = network;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
|
||||
Log.d(mTag, "ChildOpened for network " + mNetwork);
|
||||
mCallback.onChildOpened(mNetwork, childConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
Log.d(mTag, "ChildClosed for network " + mNetwork);
|
||||
mCallback.onSessionLost(mNetwork);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosedExceptionally(@NonNull IkeException exception) {
|
||||
Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
|
||||
mCallback.onSessionLost(mNetwork);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
|
||||
Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
|
||||
mCallback.onChildTransformCreated(mNetwork, transform, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
|
||||
// Nothing to be done; no references to the IpSecTransform are held by the
|
||||
// Ikev2VpnRunner (or this callback class), and this transform will be closed by the
|
||||
// IKE library.
|
||||
Log.d(mTag,
|
||||
"ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
static class Ikev2VpnNetworkCallback extends NetworkCallback {
|
||||
private final String mTag;
|
||||
private final Vpn.IkeV2VpnRunnerCallback mCallback;
|
||||
|
||||
Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback) {
|
||||
mTag = tag;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailable(@NonNull Network network) {
|
||||
Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
|
||||
mCallback.onDefaultNetworkChanged(network);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(@NonNull Network network) {
|
||||
Log.d(mTag, "Tearing down; lost network: " + network);
|
||||
mCallback.onSessionLost(network);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity parsing logic using similar logic to open source implementations of IKEv2
|
||||
*
|
||||
* <p>This method does NOT support using type-prefixes (eg 'fqdn:' or 'keyid'), or ASN.1 encoded
|
||||
* identities.
|
||||
*/
|
||||
private static IkeIdentification parseIkeIdentification(@NonNull String identityStr) {
|
||||
// TODO: Add identity formatting to public API javadocs.
|
||||
if (identityStr.contains("@")) {
|
||||
if (identityStr.startsWith("@#")) {
|
||||
// KEY_ID
|
||||
final String hexStr = identityStr.substring(2);
|
||||
return new IkeKeyIdIdentification(HexDump.hexStringToByteArray(hexStr));
|
||||
} else if (identityStr.startsWith("@@")) {
|
||||
// RFC822 (USER_FQDN)
|
||||
return new IkeRfc822AddrIdentification(identityStr.substring(2));
|
||||
} else if (identityStr.startsWith("@")) {
|
||||
// FQDN
|
||||
return new IkeFqdnIdentification(identityStr.substring(1));
|
||||
} else {
|
||||
// RFC822 (USER_FQDN)
|
||||
return new IkeRfc822AddrIdentification(identityStr);
|
||||
}
|
||||
} else if (InetAddresses.isNumericAddress(identityStr)) {
|
||||
final InetAddress addr = InetAddresses.parseNumericAddress(identityStr);
|
||||
if (addr instanceof Inet4Address) {
|
||||
// IPv4
|
||||
return new IkeIpv4AddrIdentification((Inet4Address) addr);
|
||||
} else if (addr instanceof Inet6Address) {
|
||||
// IPv6
|
||||
return new IkeIpv6AddrIdentification((Inet6Address) addr);
|
||||
} else {
|
||||
throw new IllegalArgumentException("IP version not supported");
|
||||
}
|
||||
} else {
|
||||
if (identityStr.contains(":")) {
|
||||
// KEY_ID
|
||||
return new IkeKeyIdIdentification(identityStr.getBytes());
|
||||
} else {
|
||||
// FQDN
|
||||
return new IkeFqdnIdentification(identityStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Collection<RouteInfo> getRoutesFromTrafficSelectors(
|
||||
List<IkeTrafficSelector> trafficSelectors) {
|
||||
final HashSet<RouteInfo> routes = new HashSet<>();
|
||||
|
||||
for (final IkeTrafficSelector selector : trafficSelectors) {
|
||||
for (final IpPrefix prefix :
|
||||
new IpRange(selector.startingAddress, selector.endingAddress).asIpPrefixes()) {
|
||||
routes.add(new RouteInfo(prefix, null));
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,7 @@ public class VpnTest {
|
||||
@Mock private AppOpsManager mAppOps;
|
||||
@Mock private NotificationManager mNotificationManager;
|
||||
@Mock private Vpn.SystemServices mSystemServices;
|
||||
@Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
|
||||
@Mock private ConnectivityManager mConnectivityManager;
|
||||
@Mock private KeyStore mKeyStore;
|
||||
private final VpnProfile mVpnProfile = new VpnProfile("key");
|
||||
@@ -867,7 +868,8 @@ public class VpnTest {
|
||||
* Mock some methods of vpn object.
|
||||
*/
|
||||
private Vpn createVpn(@UserIdInt int userId) {
|
||||
return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
|
||||
return new Vpn(Looper.myLooper(), mContext, mNetService,
|
||||
userId, mSystemServices, mIkev2SessionCreator);
|
||||
}
|
||||
|
||||
private static void assertBlocked(Vpn vpn, int... uids) {
|
||||
|
||||
Reference in New Issue
Block a user