Merge changes I915e0c4e,Iae187c0b am: c8387d8128 am: 551f4c523a
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1515263 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I32b7147de91fa5878dbba5e95bf897912136cedf
This commit is contained in:
@@ -16,33 +16,438 @@
|
|||||||
|
|
||||||
package com.android.server.vcn;
|
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.NonNull;
|
||||||
import android.annotation.Nullable;
|
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.net.vcn.VcnGatewayConnectionConfig;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerExecutor;
|
||||||
|
import android.os.Message;
|
||||||
import android.os.ParcelUuid;
|
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.UnderlyingNetworkRecord;
|
||||||
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
|
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.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single VCN Gateway Connection, providing a single public-facing VCN network.
|
* 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.
|
* <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
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTrackerCallback {
|
public class VcnGatewayConnection extends StateMachine {
|
||||||
private static final String TAG = VcnGatewayConnection.class.getSimpleName();
|
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 VcnContext mVcnContext;
|
||||||
@NonNull private final ParcelUuid mSubscriptionGroup;
|
@NonNull private final ParcelUuid mSubscriptionGroup;
|
||||||
@NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
|
@NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
|
||||||
@NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
|
@NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
|
||||||
@NonNull private final Dependencies mDeps;
|
@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(
|
public VcnGatewayConnection(
|
||||||
@NonNull VcnContext vcnContext,
|
@NonNull VcnContext vcnContext,
|
||||||
@NonNull ParcelUuid subscriptionGroup,
|
@NonNull ParcelUuid subscriptionGroup,
|
||||||
@@ -55,30 +460,350 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr
|
|||||||
@NonNull ParcelUuid subscriptionGroup,
|
@NonNull ParcelUuid subscriptionGroup,
|
||||||
@NonNull VcnGatewayConnectionConfig connectionConfig,
|
@NonNull VcnGatewayConnectionConfig connectionConfig,
|
||||||
@NonNull Dependencies deps) {
|
@NonNull Dependencies deps) {
|
||||||
super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
|
super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
|
||||||
mVcnContext = vcnContext;
|
mVcnContext = vcnContext;
|
||||||
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
|
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
|
||||||
mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
|
mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
|
||||||
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
||||||
|
|
||||||
|
mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
|
||||||
|
|
||||||
mUnderlyingNetworkTracker =
|
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() {
|
public void teardownAsynchronously() {
|
||||||
mUnderlyingNetworkTracker.teardown();
|
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 static class Dependencies {
|
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 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(
|
public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
|
||||||
VcnContext vcnContext,
|
VcnContext vcnContext,
|
||||||
ParcelUuid subscriptionGroup,
|
ParcelUuid subscriptionGroup,
|
||||||
UnderlyingNetworkTrackerCallback callback) {
|
UnderlyingNetworkTrackerCallback callback) {
|
||||||
return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
|
return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/** Builds a new IkeSession. */
|
||||||
public void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying) {}
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ android_test {
|
|||||||
"services.core",
|
"services.core",
|
||||||
],
|
],
|
||||||
libs: [
|
libs: [
|
||||||
|
"android.net.ipsec.ike.stubs.module_lib",
|
||||||
"android.test.runner",
|
"android.test.runner",
|
||||||
"android.test.base",
|
"android.test.base",
|
||||||
"android.test.mock",
|
"android.test.mock",
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class VcnGatewayConnectionConfigTest {
|
public class VcnGatewayConnectionConfigTest {
|
||||||
private static final int[] EXPOSED_CAPS =
|
// Public for use in VcnGatewayConnectionTest
|
||||||
|
public static final int[] EXPOSED_CAPS =
|
||||||
new int[] {
|
new int[] {
|
||||||
NetworkCapabilities.NET_CAPABILITY_INTERNET, NetworkCapabilities.NET_CAPABILITY_MMS
|
NetworkCapabilities.NET_CAPABILITY_INTERNET, NetworkCapabilities.NET_CAPABILITY_MMS
|
||||||
};
|
};
|
||||||
private static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
|
public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
|
||||||
private static final long[] RETRY_INTERVALS_MS =
|
public static final long[] RETRY_INTERVALS_MS =
|
||||||
new long[] {
|
new long[] {
|
||||||
TimeUnit.SECONDS.toMillis(5),
|
TimeUnit.SECONDS.toMillis(5),
|
||||||
TimeUnit.SECONDS.toMillis(30),
|
TimeUnit.SECONDS.toMillis(30),
|
||||||
@@ -47,10 +48,10 @@ public class VcnGatewayConnectionConfigTest {
|
|||||||
TimeUnit.MINUTES.toMillis(15),
|
TimeUnit.MINUTES.toMillis(15),
|
||||||
TimeUnit.MINUTES.toMillis(30)
|
TimeUnit.MINUTES.toMillis(30)
|
||||||
};
|
};
|
||||||
private static final int MAX_MTU = 1360;
|
public static final int MAX_MTU = 1360;
|
||||||
|
|
||||||
// Package protected for use in VcnConfigTest
|
// Public for use in VcnGatewayConnectionTest
|
||||||
static VcnGatewayConnectionConfig buildTestConfig() {
|
public static VcnGatewayConnectionConfig buildTestConfig() {
|
||||||
final VcnGatewayConnectionConfig.Builder builder =
|
final VcnGatewayConnectionConfig.Builder builder =
|
||||||
new VcnGatewayConnectionConfig.Builder()
|
new VcnGatewayConnectionConfig.Builder()
|
||||||
.setRetryInterval(RETRY_INTERVALS_MS)
|
.setRetryInterval(RETRY_INTERVALS_MS)
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user