diff options
| author | 2021-01-14 04:41:30 +0000 | |
|---|---|---|
| committer | 2021-01-14 04:41:30 +0000 | |
| commit | 9f41f95bedcded6ec1ee6bdeaa60ff42706dc595 (patch) | |
| tree | 586326735b6f5389f24ab7bd59c14fd3585cc968 | |
| parent | f65debad4853be4b4a492163a10d379a0af699e5 (diff) | |
| parent | a363ff407015336a209d714ae60b5e4f0d2a7e1d (diff) | |
Merge changes I915e0c4e,Iae187c0b am: c8387d8128 am: 551f4c523a am: a363ff4070
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1515263
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I80553b02c97bf3e97eaf8af8b83a9b7239f22b75
4 files changed, 823 insertions, 14 deletions
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 7ea8e04580f7..7024e67a8204 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -16,33 +16,438 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + +import static com.android.server.VcnManagementService.VDBG; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.ConnectivityManager; +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.IpSecManager; +import android.net.IpSecManager.IpSecTunnelInterface; +import android.net.IpSecManager.ResourceUnavailableException; +import android.net.IpSecTransform; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.RouteInfo; +import android.net.annotations.PolicyDirection; +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.IkeSessionConfiguration; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.vcn.VcnGatewayConnectionConfig; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Message; import android.os.ParcelUuid; +import android.telephony.TelephonyManager; +import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.util.Objects; +import java.util.concurrent.TimeUnit; /** * A single VCN Gateway Connection, providing a single public-facing VCN network. * * <p>This class handles mobility events, performs retries, and tracks safe-mode conditions. * + * <pre>Internal state transitions are as follows: + * + * +----------------------------+ +------------------------------+ + * | DisconnectedState | Teardown or | DisconnectingState | + * | |<--no available--| | + * | Initial state. | underlying | Transitive state for tearing | + * +----------------------------+ networks | tearing down an IKE session. | + * | +------------------------------+ + * | ^ | + * Underlying Network Teardown requested | Not tearing down + * changed +--or retriable error--+ and has available + * | | occurred underlying network + * | ^ | + * v | v + * +----------------------------+ | +------------------------------+ + * | ConnectingState |<----------------| RetryTimeoutState | + * | | | | | + * | Transitive state for | | | Transitive state for | + * | starting IKE negotiation. |---+ | handling retriable errors. | + * +----------------------------+ | +------------------------------+ + * | | + * IKE session | + * negotiated | + * | | + * v | + * +----------------------------+ ^ + * | ConnectedState | | + * | | | + * | Stable state where | | + * | gateway connection is set | | + * | up, and Android Network is | | + * | connected. |---+ + * +----------------------------+ + * </pre> + * * @hide */ -public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTrackerCallback { +public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); + private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; + + private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: "; + private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST = + "Underlying Network lost"; + private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; + private static final int TOKEN_ANY = Integer.MIN_VALUE; + + private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; + private static final int TEARDOWN_TIMEOUT_SECONDS = 5; + + private interface EventInfo {} + + /** + * Sent when there are changes to the underlying network (per the UnderlyingNetworkTracker). + * + * <p>May indicate an entirely new underlying network, OR a change in network properties. + * + * <p>Relevant in ALL states. + * + * <p>In the Connected state, this MAY indicate a mobility even occurred. + * + * @param arg1 The "any" token; this event is always applicable. + * @param obj @NonNull An EventUnderlyingNetworkChangedInfo instance with relevant data. + */ + private static final int EVENT_UNDERLYING_NETWORK_CHANGED = 1; + + private static class EventUnderlyingNetworkChangedInfo implements EventInfo { + @Nullable public final UnderlyingNetworkRecord newUnderlying; + + EventUnderlyingNetworkChangedInfo(@Nullable UnderlyingNetworkRecord newUnderlying) { + this.newUnderlying = newUnderlying; + } + + @Override + public int hashCode() { + return Objects.hash(newUnderlying); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof EventUnderlyingNetworkChangedInfo)) { + return false; + } + + final EventUnderlyingNetworkChangedInfo rhs = (EventUnderlyingNetworkChangedInfo) other; + return Objects.equals(newUnderlying, rhs.newUnderlying); + } + } + + /** + * Sent (delayed) to trigger an attempt to reestablish the tunnel. + * + * <p>Only relevant in the Retry-timeout state, discarded in all other states. + * + * <p>Upon receipt of this signal, the state machine will transition from the Retry-timeout + * state to the Connecting state. + * + * @param arg1 The "any" token; no sessions are active in the RetryTimeoutState. + */ + private static final int EVENT_RETRY_TIMEOUT_EXPIRED = 2; + + /** + * Sent when a gateway connection has been lost, either due to a IKE or child failure. + * + * <p>Relevant in all states that have an IKE session. + * + * <p>Upon receipt of this signal, the state machine will (unless loss of the session is + * expected) transition to the Disconnecting state, to ensure IKE session closure before + * retrying, or fully shutting down. + * + * @param arg1 The session token for the IKE Session that was lost, used to prevent out-of-date + * signals from propagating. + * @param obj @NonNull An EventSessionLostInfo instance with relevant data. + */ + private static final int EVENT_SESSION_LOST = 3; + + private static class EventSessionLostInfo implements EventInfo { + @Nullable public final Exception exception; + + EventSessionLostInfo(@NonNull Exception exception) { + this.exception = exception; + } + + @Override + public int hashCode() { + return Objects.hash(exception); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof EventSessionLostInfo)) { + return false; + } + + final EventSessionLostInfo rhs = (EventSessionLostInfo) other; + return Objects.equals(exception, rhs.exception); + } + } + + /** + * Sent when an IKE session has completely closed. + * + * <p>Relevant only in the Disconnecting State, used to identify that a session being torn down + * was fully closed. If this event is not fired within a timely fashion, the IKE session will be + * forcibly terminated. + * + * <p>Upon receipt of this signal, the state machine will (unless closure of the session is + * expected) transition to the Disconnected or RetryTimeout states, depending on whether the + * GatewayConnection is being fully torn down. + * + * @param arg1 The session token for the IKE Session that was lost, used to prevent out-of-date + * signals from propagating. + * @param obj @NonNull An EventSessionLostInfo instance with relevant data. + */ + private static final int EVENT_SESSION_CLOSED = 4; + + /** + * Sent when an IKE Child Transform was created, and should be applied to the tunnel. + * + * <p>Only relevant in the Connecting, Connected and Migrating states. This callback MUST be + * handled in the Connected or Migrating states, and should be deferred if necessary. + * + * @param arg1 The session token for the IKE Session that had a new child created, used to + * prevent out-of-date signals from propagating. + * @param obj @NonNull An EventTransformCreatedInfo instance with relevant data. + */ + private static final int EVENT_TRANSFORM_CREATED = 5; + + private static class EventTransformCreatedInfo implements EventInfo { + @PolicyDirection public final int direction; + @NonNull public final IpSecTransform transform; + + EventTransformCreatedInfo( + @PolicyDirection int direction, @NonNull IpSecTransform transform) { + this.direction = direction; + this.transform = Objects.requireNonNull(transform); + } + + @Override + public int hashCode() { + return Objects.hash(direction, transform); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof EventTransformCreatedInfo)) { + return false; + } + + final EventTransformCreatedInfo rhs = (EventTransformCreatedInfo) other; + return direction == rhs.direction && Objects.equals(transform, rhs.transform); + } + } + + /** + * Sent when an IKE Child Session was completely opened and configured successfully. + * + * <p>Only relevant in the Connected and Migrating states. + * + * @param arg1 The session token for the IKE Session for which a child was opened and configured + * successfully, used to prevent out-of-date signals from propagating. + * @param obj @NonNull An EventSetupCompletedInfo instance with relevant data. + */ + private static final int EVENT_SETUP_COMPLETED = 6; + + private static class EventSetupCompletedInfo implements EventInfo { + @NonNull public final ChildSessionConfiguration childSessionConfig; + + EventSetupCompletedInfo(@NonNull ChildSessionConfiguration childSessionConfig) { + this.childSessionConfig = Objects.requireNonNull(childSessionConfig); + } + + @Override + public int hashCode() { + return Objects.hash(childSessionConfig); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof EventSetupCompletedInfo)) { + return false; + } + + final EventSetupCompletedInfo rhs = (EventSetupCompletedInfo) other; + return Objects.equals(childSessionConfig, rhs.childSessionConfig); + } + } + + /** + * Sent when conditions (internal or external) require a disconnect. + * + * <p>Relevant in all states except the Disconnected state. + * + * <p>This signal is often fired with a timeout in order to prevent disconnecting during + * transient conditions, such as network switches. Upon the transient passing, the signal is + * canceled based on the disconnect reason. + * + * <p>Upon receipt of this signal, the state machine MUST tear down all active sessions, cancel + * any pending work items, and move to the Disconnected state. + * + * @param arg1 The "any" token; this signal is always honored. + * @param obj @NonNull An EventDisconnectRequestedInfo instance with relevant data. + */ + private static final int EVENT_DISCONNECT_REQUESTED = 7; + + private static class EventDisconnectRequestedInfo implements EventInfo { + /** The reason why the disconnect was requested. */ + @NonNull public final String reason; + + EventDisconnectRequestedInfo(@NonNull String reason) { + this.reason = Objects.requireNonNull(reason); + } + + @Override + public int hashCode() { + return Objects.hash(reason); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof EventDisconnectRequestedInfo)) { + return false; + } + + final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other; + return reason.equals(rhs.reason); + } + } + + /** + * Sent (delayed) to trigger a forcible close of an IKE session. + * + * <p>Only relevant in the Disconnecting state, discarded in all other states. + * + * <p>Upon receipt of this signal, the state machine will transition from the Disconnecting + * state to the Disconnected state. + * + * @param arg1 The session token for the IKE Session that is being torn down, used to prevent + * out-of-date signals from propagating. + */ + private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8; + + @NonNull private final DisconnectedState mDisconnectedState = new DisconnectedState(); + @NonNull private final DisconnectingState mDisconnectingState = new DisconnectingState(); + @NonNull private final ConnectingState mConnectingState = new ConnectingState(); + @NonNull private final ConnectedState mConnectedState = new ConnectedState(); + @NonNull private final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; @NonNull private final Dependencies mDeps; + @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; + + @NonNull private final IpSecManager mIpSecManager; + @NonNull private final IpSecTunnelInterface mTunnelIface; + + /** Running state of this VcnGatewayConnection. */ + private boolean mIsRunning = true; + + /** + * The token used by the primary/current/active session. + * + * <p>This token MUST be updated when a new stateful/async session becomes the + * primary/current/active session. Example cases where the session changes are: + * + * <ul> + * <li>Switching to an IKE session as the primary session + * </ul> + * + * <p>In the migrating state, where two sessions may be active, this value MUST represent the + * primary session. This is USUALLY the existing session, and is only switched to the new + * session when: + * + * <ul> + * <li>The new session connects successfully, and becomes the primary session + * <li>The existing session is lost, and the remaining (new) session becomes the primary + * session + * </ul> + */ + private int mCurrentToken = -1; + + /** + * The next usable token. + * + * <p>A new token MUST be used for all new IKE sessions. + */ + private int mNextToken = 0; + + /** + * The number of unsuccessful attempts since the last successful connection. + * + * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared + * each time the Connected state is entered. + */ + private int mFailedAttempts = 0; + + /** + * The current underlying network. + * + * <p>Set in any states, always @NonNull in all states except Disconnected, null otherwise. + */ + private UnderlyingNetworkRecord mUnderlying; + + /** + * The active IKE session. + * + * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and + * Migrating states, null otherwise. + */ + private IkeSession mIkeSession; + + /** + * The last known child configuration. + * + * <p>Set in Connected and Migrating states, always @NonNull in Connected, Migrating + * states, @Nullable otherwise. + */ + private ChildSessionConfiguration mChildConfig; + + /** + * The active network agent. + * + * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable + * otherwise. + */ + private NetworkAgent mNetworkAgent; + public VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @@ -55,30 +460,350 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr @NonNull ParcelUuid subscriptionGroup, @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull Dependencies deps) { - super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); + super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); + mUnderlyingNetworkTracker = - mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this); + mDeps.newUnderlyingNetworkTracker( + mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback); + mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); + + IpSecTunnelInterface iface; + try { + iface = + mIpSecManager.createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, new Network(-1)); + } catch (IOException | ResourceUnavailableException e) { + teardownAsynchronously(); + mTunnelIface = null; + + return; + } + + mTunnelIface = iface; + + addState(mDisconnectedState); + addState(mDisconnectingState); + addState(mConnectingState); + addState(mConnectedState); + addState(mRetryTimeoutState); + + setInitialState(mDisconnectedState); + setDbg(VDBG); + start(); } - /** Asynchronously tears down this GatewayConnection, and any resources used */ + /** + * Asynchronously tears down this GatewayConnection, and any resources used. + * + * <p>Once torn down, this VcnTunnel CANNOT be started again. + */ public void teardownAsynchronously() { mUnderlyingNetworkTracker.teardown(); + + // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down. + if (mTunnelIface != null) { + mTunnelIface.close(); + } + + sendMessage( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ANY, + new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); + quit(); + + // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this + // is also called asynchronously when a NetworkAgent becomes unwanted + } + + private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { + @Override + public void onSelectedUnderlyingNetworkChanged( + @Nullable UnderlyingNetworkRecord underlying) { + // If underlying is null, all underlying networks have been lost. Disconnect VCN after a + // timeout. + if (underlying == null) { + sendMessageDelayed( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ANY, + new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST), + TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS)); + return; + } + + // Cancel any existing disconnect due to loss of underlying network + // getHandler() can return null if the state machine has already quit. Since this is + // called + // from other classes, this condition must be verified. + if (getHandler() != null) { + getHandler() + .removeEqualMessages( + EVENT_DISCONNECT_REQUESTED, + new EventDisconnectRequestedInfo( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + } + sendMessage( + EVENT_UNDERLYING_NETWORK_CHANGED, + TOKEN_ANY, + new EventUnderlyingNetworkChangedInfo(underlying)); + } + } + + private void sendMessage(int what, int token, EventInfo data) { + super.sendMessage(what, token, ARG_NOT_PRESENT, data); + } + + private void sendMessage(int what, int token, int arg2, EventInfo data) { + super.sendMessage(what, token, arg2, data); + } + + private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) { + super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout); + } + + private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) { + super.sendMessageDelayed(what, token, arg2, data, timeout); + } + + private void sessionLost(int token, @Nullable Exception exception) { + sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); + } + + private void sessionClosed(int token, @Nullable Exception exception) { + // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the + // Disconnecting state. + sessionLost(token, exception); + sendMessage(EVENT_SESSION_CLOSED, token); + } + + private void childTransformCreated( + int token, @NonNull IpSecTransform transform, int direction) { + sendMessage( + EVENT_TRANSFORM_CREATED, + token, + new EventTransformCreatedInfo(direction, transform)); + } + + private void childOpened(int token, @NonNull ChildSessionConfiguration childConfig) { + sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig)); + } + + private abstract class BaseState extends State { + protected void enterState() throws Exception {} + + protected abstract void processStateMsg(Message msg) throws Exception; + } + /** + * State representing the a disconnected VCN tunnel. + * + * <p>This is also is the initial state. + */ + private class DisconnectedState extends BaseState { + @Override + protected void processStateMsg(Message msg) {} + } + + private abstract class ActiveBaseState extends BaseState {} + + /** + * Transitive state representing a VCN that is tearing down an IKE session. + * + * <p>In this state, the IKE session is in the process of being torn down. If the IKE session + * does not complete teardown in a timely fashion, it will be killed (forcibly closed). + */ + private class DisconnectingState extends ActiveBaseState { + @Override + protected void processStateMsg(Message msg) {} + } + + /** + * Transitive state representing a VCN that is making an primary (non-handover) connection. + * + * <p>This state starts IKE negotiation, but defers transform application & network setup to the + * Connected state. + */ + private class ConnectingState extends ActiveBaseState { + @Override + protected void processStateMsg(Message msg) {} + } + + private abstract class ConnectedStateBase extends ActiveBaseState {} + + /** + * Stable state representing a VCN that has a functioning connection to the mobility anchor. + * + * <p>This state handles IPsec transform application (initial and rekey), NetworkAgent setup, + * and monitors for mobility events. + */ + class ConnectedState extends ConnectedStateBase { + @Override + protected void processStateMsg(Message msg) {} + } + + /** + * Transitive state representing a VCN that failed to establish a connection, and will retry. + * + * <p>This state will be exited upon a new underlying network being found, or timeout expiry. + */ + class RetryTimeoutState extends ActiveBaseState { + @Override + protected void processStateMsg(Message msg) {} + } + + // TODO: Remove this when migrating to new NetworkAgent API + private static NetworkInfo buildNetworkInfo(boolean isConnected) { + NetworkInfo info = + new NetworkInfo( + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_UNKNOWN, + "MOBILE", + "VCN"); + info.setDetailedState( + isConnected ? DetailedState.CONNECTED : DetailedState.DISCONNECTED, null, null); + + return info; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static NetworkCapabilities buildNetworkCapabilities( + @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { + final NetworkCapabilities caps = new NetworkCapabilities(); + + caps.addTransportType(TRANSPORT_CELLULAR); + caps.addCapability(NET_CAPABILITY_NOT_CONGESTED); + caps.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + + // Add exposed capabilities + for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) { + caps.addCapability(cap); + } + + return caps; + } + + private static LinkProperties buildConnectedLinkProperties( + @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull ChildSessionConfiguration childConfig) { + final LinkProperties lp = new LinkProperties(); + + lp.setInterfaceName(tunnelIface.getInterfaceName()); + for (LinkAddress addr : childConfig.getInternalAddresses()) { + lp.addLinkAddress(addr); + } + for (InetAddress addr : childConfig.getInternalDnsServers()) { + lp.addDnsServer(addr); + } + + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + + lp.setMtu(gatewayConnectionConfig.getMaxMtu()); + + return lp; } - private static class Dependencies { + private class IkeSessionCallbackImpl implements IkeSessionCallback { + private final int mToken; + + IkeSessionCallbackImpl(int token) { + mToken = token; + } + + @Override + public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) { + Slog.v(TAG, "IkeOpened for token " + mToken); + // Nothing to do here. + } + + @Override + public void onClosed() { + Slog.v(TAG, "IkeClosed for token " + mToken); + sessionClosed(mToken, null); + } + + @Override + public void onClosedExceptionally(@NonNull IkeException exception) { + Slog.v(TAG, "IkeClosedExceptionally for token " + mToken, exception); + sessionClosed(mToken, exception); + } + + @Override + public void onError(@NonNull IkeProtocolException exception) { + Slog.v(TAG, "IkeError for token " + mToken, exception); + // Non-fatal, log and continue. + } + } + + private class ChildSessionCallbackImpl implements ChildSessionCallback { + private final int mToken; + + ChildSessionCallbackImpl(int token) { + mToken = token; + } + + @Override + public void onOpened(@NonNull ChildSessionConfiguration childConfig) { + Slog.v(TAG, "ChildOpened for token " + mToken); + childOpened(mToken, childConfig); + } + + @Override + public void onClosed() { + Slog.v(TAG, "ChildClosed for token " + mToken); + sessionLost(mToken, null); + } + + @Override + public void onClosedExceptionally(@NonNull IkeException exception) { + Slog.v(TAG, "ChildClosedExceptionally for token " + mToken, exception); + sessionLost(mToken, exception); + } + + @Override + public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) { + Slog.v(TAG, "ChildTransformCreated; Direction: " + direction + "; token " + mToken); + childTransformCreated(mToken, transform, direction); + } + + @Override + public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) { + // Nothing to be done; no references to the IpSecTransform are held, and this transform + // will be closed by the IKE library. + Slog.v(TAG, "ChildTransformDeleted; Direction: " + direction + "; for token " + mToken); + } + } + + /** External dependencies used by VcnGatewayConnection, for injection in tests. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class Dependencies { + /** Builds a new UnderlyingNetworkTracker. */ public UnderlyingNetworkTracker newUnderlyingNetworkTracker( VcnContext vcnContext, ParcelUuid subscriptionGroup, UnderlyingNetworkTrackerCallback callback) { return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback); } - } - @Override - public void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying) {} + /** Builds a new IkeSession. */ + public IkeSession newIkeSession( + VcnContext vcnContext, + IkeSessionParams ikeSessionParams, + ChildSessionParams childSessionParams, + IkeSessionCallback ikeSessionCallback, + ChildSessionCallback childSessionCallback) { + return new IkeSession( + vcnContext.getContext(), + ikeSessionParams, + childSessionParams, + new HandlerExecutor(new Handler(vcnContext.getLooper())), + ikeSessionCallback, + childSessionCallback); + } + } } diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp index f967bf0d8f6b..3c08d347b19a 100644 --- a/tests/vcn/Android.bp +++ b/tests/vcn/Android.bp @@ -20,6 +20,7 @@ android_test { "services.core", ], libs: [ + "android.net.ipsec.ike.stubs.module_lib", "android.test.runner", "android.test.base", "android.test.mock", diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index e98b6ef2b3a6..dfd0c8a75172 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -33,12 +33,13 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionConfigTest { - private static final int[] EXPOSED_CAPS = + // Public for use in VcnGatewayConnectionTest + public static final int[] EXPOSED_CAPS = new int[] { NetworkCapabilities.NET_CAPABILITY_INTERNET, NetworkCapabilities.NET_CAPABILITY_MMS }; - private static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; - private static final long[] RETRY_INTERVALS_MS = + public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; + public static final long[] RETRY_INTERVALS_MS = new long[] { TimeUnit.SECONDS.toMillis(5), TimeUnit.SECONDS.toMillis(30), @@ -47,10 +48,10 @@ public class VcnGatewayConnectionConfigTest { TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(30) }; - private static final int MAX_MTU = 1360; + public static final int MAX_MTU = 1360; - // Package protected for use in VcnConfigTest - static VcnGatewayConnectionConfig buildTestConfig() { + // Public for use in VcnGatewayConnectionTest + public static VcnGatewayConnectionConfig buildTestConfig() { final VcnGatewayConnectionConfig.Builder builder = new VcnGatewayConnectionConfig.Builder() .setRetryInterval(RETRY_INTERVALS_MS) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java new file mode 100644 index 000000000000..d741e5cf4b35 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -0,0 +1,82 @@ +/* + * 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.vcn; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.NetworkCapabilities; +import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.os.ParcelUuid; +import android.os.test.TestLooper; +import android.telephony.SubscriptionInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** Tests for TelephonySubscriptionTracker */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionTest { + private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); + private static final int TEST_SIM_SLOT_INDEX = 1; + private static final int TEST_SUBSCRIPTION_ID_1 = 2; + private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class); + private static final int TEST_SUBSCRIPTION_ID_2 = 3; + private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class); + private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP; + + static { + final Map<Integer, ParcelUuid> subIdToGroupMap = new HashMap<>(); + subIdToGroupMap.put(TEST_SUBSCRIPTION_ID_1, TEST_PARCEL_UUID); + subIdToGroupMap.put(TEST_SUBSCRIPTION_ID_2, TEST_PARCEL_UUID); + TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap); + } + + @NonNull private final Context mContext; + @NonNull private final TestLooper mTestLooper; + @NonNull private final VcnNetworkProvider mVcnNetworkProvider; + @NonNull private final VcnGatewayConnection.Dependencies mDeps; + + public VcnGatewayConnectionTest() { + mContext = mock(Context.class); + mTestLooper = new TestLooper(); + mVcnNetworkProvider = mock(VcnNetworkProvider.class); + mDeps = mock(VcnGatewayConnection.Dependencies.class); + } + + @Test + public void testBuildNetworkCapabilities() throws Exception { + final NetworkCapabilities caps = + VcnGatewayConnection.buildNetworkCapabilities( + VcnGatewayConnectionConfigTest.buildTestConfig()); + + for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { + assertTrue(caps.hasCapability(exposedCapability)); + } + } +} |