From 16952dceec70c7a5c3b6143ad62c25c616844140 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Thu, 1 Apr 2021 14:11:31 -0700 Subject: [PATCH 1/4] Add dump to VcnManagementService This change adds basic dump functionality for debugging purposes to the VcnManagementService Test: atest FrameworksVcnTests Change-Id: Id9494f69450ffff57945701255181052f52bbb37 --- .../android/server/VcnManagementService.java | 31 ++++++++++++++ .../core/java/com/android/server/vcn/Vcn.java | 23 +++++++++++ .../server/vcn/VcnGatewayConnection.java | 22 ++++++++++ .../server/vcn/VcnNetworkProvider.java | 41 +++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index f7ae58ca0eb41..fc1162e1b55f8 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.Manifest.permission.DUMP; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; @@ -69,6 +70,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.IndentingPrintWriter; import com.android.net.module.util.LocationPermissionChecker; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; @@ -76,7 +78,9 @@ import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -929,6 +933,33 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + /** + * Dumps the state of the VcnManagementService for logging and debugging purposes. + * + *

PII and credentials MUST NEVER be dumped here. + */ + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(DUMP, TAG); + + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + + pw.println("VcnManagementService dump:"); + pw.increaseIndent(); + + mNetworkProvider.dump(pw); + + synchronized (mLock) { + pw.println("mVcns:"); + for (Vcn vcn : mVcns.values()) { + vcn.dump(pw); + } + pw.println(); + } + + pw.decreaseIndent(); + } + // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService /** Callback for Vcn signals sent up to VcnManagementService. */ public interface VcnCallback { diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index ae806aa500a67..7bc6056f91f30 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -38,6 +38,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -328,6 +329,8 @@ public class Vcn extends Handler { private void handleNetworkRequested( @NonNull NetworkRequest request, int score, int providerId) { + Slog.v(getLogTag(), "Received request " + request); + if (score > getNetworkScore()) { if (VDBG) { Slog.v( @@ -409,6 +412,26 @@ public class Vcn extends Handler { return TAG + " [" + mSubscriptionGroup.hashCode() + "]"; } + /** + * Dumps the state of this Vcn for logging and debugging purposes. + * + *

PII and credentials MUST NEVER be dumped here. + */ + public void dump(IndentingPrintWriter pw) { + pw.println("Vcn (" + mSubscriptionGroup + "):"); + pw.increaseIndent(); + + pw.println("mCurrentStatus: " + mCurrentStatus); + + pw.println("mVcnGatewayConnections:"); + for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) { + gw.dump(pw); + } + pw.println(); + + pw.decreaseIndent(); + } + /** Retrieves the network score for a VCN Network */ // Package visibility for use in VcnGatewayConnection static int getNetworkScore() { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 20c08eb2ce928..4f1989019ca52 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -77,6 +77,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; @@ -1924,6 +1925,27 @@ public class VcnGatewayConnection extends StateMachine { } } + /** + * Dumps the state of this VcnGatewayConnection for logging and debugging purposes. + * + *

PII and credentials MUST NEVER be dumped here. + */ + public void dump(IndentingPrintWriter pw) { + pw.println("VcnGatewayConnection (" + mConnectionConfig.getGatewayConnectionName() + "):"); + pw.increaseIndent(); + + pw.println("Current state: " + getCurrentState().getClass().getSimpleName()); + pw.println("mIsQuitting: " + mIsQuitting); + pw.println("mIsInSafeMode: " + mIsInSafeMode); + pw.println("mCurrentToken: " + mCurrentToken); + pw.println("mFailedAttempts: " + mFailedAttempts); + pw.println( + "mNetworkAgent.getNetwork(): " + + (mNetworkAgent == null ? null : mNetworkAgent.getNetwork())); + + pw.decreaseIndent(); + } + @VisibleForTesting(visibility = Visibility.PRIVATE) void setTunnelInterface(IpSecTunnelInterface tunnelIface) { mTunnelIface = tunnelIface; diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index a909695991593..be0deb57ee769 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -29,6 +29,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.IndentingPrintWriter; import java.util.Objects; import java.util.Set; @@ -129,10 +130,50 @@ public class VcnNetworkProvider extends NetworkProvider { mScore = score; mProviderId = providerId; } + + /** + * Dumps the state of this NetworkRequestEntry for logging and debugging purposes. + * + *

PII and credentials MUST NEVER be dumped here. + */ + public void dump(IndentingPrintWriter pw) { + pw.println("NetworkRequestEntry:"); + pw.increaseIndent(); + + pw.println("mRequest: " + mRequest); + pw.println("mScore: " + mScore); + pw.println("mProviderId: " + mProviderId); + + pw.decreaseIndent(); + } } // package-private interface NetworkRequestListener { void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId); } + + /** + * Dumps the state of this VcnNetworkProvider for logging and debugging purposes. + * + *

PII and credentials MUST NEVER be dumped here. + */ + public void dump(IndentingPrintWriter pw) { + pw.println("VcnNetworkProvider:"); + pw.increaseIndent(); + + pw.println("mListeners:"); + for (NetworkRequestListener listener : mListeners) { + pw.println(listener); + } + pw.println(); + + pw.println("mRequests.values:"); + for (NetworkRequestEntry entry : mRequests.values()) { + entry.dump(pw); + } + pw.println(); + + pw.decreaseIndent(); + } } From a4f28edac308ac45c8539b21eb52a1f44fe368dc Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Mon, 5 Apr 2021 18:11:20 -0700 Subject: [PATCH 2/4] Ensure teardown() only triggered on the right NetworkAgent. This change ensures that teardown() is only ever triggered when the NetworkAgent matches the one that the unwanted() callback is triggered on. This ensures that in cases where the unwanted() is called as a result of unregister(), or even after a new NetworkAgent is created (and stored in the mNetworkAgent field), the teardown does not occur. Bug: 184304972 Test: atest FrameworksVcnTests Change-Id: Ic8ab66ff1e48d43be784974cda7574d0191e557f --- .../server/vcn/VcnGatewayConnection.java | 26 ++++---- ...cnGatewayConnectionConnectedStateTest.java | 60 +++++++++++++++++++ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 4f1989019ca52..ddf8b4618128b 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -1473,15 +1473,21 @@ public class VcnGatewayConnection extends StateMachine { Vcn.getNetworkScore(), nac, mVcnContext.getVcnNetworkProvider(), - () -> { - Slog.d(TAG, "NetworkAgent was unwanted"); - // If network agent has already been torn down, skip sending the - // disconnect. Unwanted() is always called, even when networkAgents - // are unregistered in teardownNetwork(), so prevent duplicate - // notifications. - if (mNetworkAgent != null) { - teardownAsynchronously(); + (agentRef) -> { + // Only trigger teardown if the NetworkAgent hasn't been replaced or + // changed. This guards against two cases - the first where + // unwanted() may be called as a result of the + // NetworkAgent.unregister() call, which might trigger a teardown + // instead of just a Network disconnect, as well as the case where a + // new NetworkAgent replaces an old one before the unwanted() call + // is processed. + if (mNetworkAgent != agentRef) { + Slog.d(TAG, "unwanted() called on stale NetworkAgent"); + return; } + + Slog.d(TAG, "NetworkAgent was unwanted"); + teardownAsynchronously(); } /* networkUnwantedCallback */, (status) -> { if (status == NetworkAgent.VALIDATION_STATUS_VALID) { @@ -2089,7 +2095,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull int score, @NonNull NetworkAgentConfig nac, @NonNull NetworkProvider provider, - @NonNull Runnable networkUnwantedCallback, + @NonNull Consumer networkUnwantedCallback, @NonNull Consumer validationStatusCallback) { return new NetworkAgent( vcnContext.getContext(), @@ -2102,7 +2108,7 @@ public class VcnGatewayConnection extends StateMachine { provider) { @Override public void onNetworkUnwanted() { - networkUnwantedCallback.run(); + networkUnwantedCallback.accept(this); } @Override diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 34c00182f855a..5dcf4a8621bd4 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -323,6 +323,66 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertFalse(mGatewayConnection.isInSafeMode()); } + private Consumer setupNetworkAndGetUnwantedCallback() { + triggerChildOpened(); + mTestLooper.dispatchAll(); + + final ArgumentCaptor> unwantedCallbackCaptor = + ArgumentCaptor.forClass(Consumer.class); + verify(mDeps) + .newNetworkAgent( + any(), + any(), + any(), + any(), + anyInt(), + any(), + any(), + unwantedCallbackCaptor.capture(), + any()); + + return unwantedCallbackCaptor.getValue(); + } + + @Test + public void testUnwantedNetworkAgentTriggersTeardown() throws Exception { + final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); + + unwantedCallback.accept(mNetworkAgent); + mTestLooper.dispatchAll(); + + assertTrue(mGatewayConnection.isQuitting()); + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testUnwantedNetworkAgentWithDisconnectedNetworkAgent() throws Exception { + final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); + + mGatewayConnection.setNetworkAgent(null); + unwantedCallback.accept(mNetworkAgent); + mTestLooper.dispatchAll(); + + // Verify that the call was ignored; the state machine is still running, and the state has + // not changed. + assertFalse(mGatewayConnection.isQuitting()); + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testUnwantedNetworkAgentWithNewNetworkAgent() throws Exception { + final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); + final NetworkAgent testAgent = mock(NetworkAgent.class); + + mGatewayConnection.setNetworkAgent(testAgent); + unwantedCallback.accept(mNetworkAgent); + mTestLooper.dispatchAll(); + + assertFalse(mGatewayConnection.isQuitting()); + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + assertEquals(testAgent, mGatewayConnection.getNetworkAgent()); + } + @Test public void testChildSessionClosedTriggersDisconnect() throws Exception { // Verify scheduled but not canceled when entering ConnectedState From 7087a4ac87394676fa9fc397071e18bf6f7da482 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Tue, 6 Apr 2021 18:08:13 -0700 Subject: [PATCH 3/4] Create proxy NetworkAgent for testing This change adds a proxy NetworkAgent to facilitate testing, adding the ability to test final methods such as sendNetworkCapabilities() and sendLinkProperties(), which would otherwise call a real implementation, and throw a null pointer while trying to access internal fields. Bug: 184579891 Bug: 184697651 Test: atest FrameworksVcnTests Change-Id: I8f13159bd266bf488f095a13d6ffe444bec65f38 --- .../server/vcn/VcnGatewayConnection.java | 106 ++++++++++++++---- ...cnGatewayConnectionConnectedStateTest.java | 22 ++-- .../vcn/VcnGatewayConnectionTestBase.java | 6 +- 3 files changed, 98 insertions(+), 36 deletions(-) diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index ddf8b4618128b..8d24c3ab0ca2d 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -575,7 +575,7 @@ public class VcnGatewayConnection extends StateMachine { *

Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable * otherwise. */ - private NetworkAgent mNetworkAgent; + private VcnNetworkAgent mNetworkAgent; @Nullable private WakeupMessage mTeardownTimeoutAlarm; @Nullable private WakeupMessage mDisconnectRequestAlarm; @@ -1441,7 +1441,7 @@ public class VcnGatewayConnection extends StateMachine { private abstract class ConnectedStateBase extends ActiveBaseState { protected void updateNetworkAgent( @NonNull IpSecTunnelInterface tunnelIface, - @NonNull NetworkAgent agent, + @NonNull VcnNetworkAgent agent, @NonNull VcnChildSessionConfiguration childConfig) { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); @@ -1452,7 +1452,7 @@ public class VcnGatewayConnection extends StateMachine { agent.sendLinkProperties(lp); } - protected NetworkAgent buildNetworkAgent( + protected VcnNetworkAgent buildNetworkAgent( @NonNull IpSecTunnelInterface tunnelIface, @NonNull VcnChildSessionConfiguration childConfig) { final NetworkCapabilities caps = @@ -1464,7 +1464,7 @@ public class VcnGatewayConnection extends StateMachine { .setLegacyType(ConnectivityManager.TYPE_MOBILE) .build(); - final NetworkAgent agent = + final VcnNetworkAgent agent = mDeps.newNetworkAgent( mVcnContext, TAG, @@ -1993,12 +1993,12 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) - NetworkAgent getNetworkAgent() { + VcnNetworkAgent getNetworkAgent() { return mNetworkAgent; } @VisibleForTesting(visibility = Visibility.PRIVATE) - void setNetworkAgent(@Nullable NetworkAgent networkAgent) { + void setNetworkAgent(@Nullable VcnNetworkAgent networkAgent) { mNetworkAgent = networkAgent; } @@ -2086,8 +2086,8 @@ public class VcnGatewayConnection extends StateMachine { return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable); } - /** Builds a new NetworkAgent. */ - public NetworkAgent newNetworkAgent( + /** Builds a new VcnNetworkAgent. */ + public VcnNetworkAgent newNetworkAgent( @NonNull VcnContext vcnContext, @NonNull String tag, @NonNull NetworkCapabilities caps, @@ -2095,27 +2095,18 @@ public class VcnGatewayConnection extends StateMachine { @NonNull int score, @NonNull NetworkAgentConfig nac, @NonNull NetworkProvider provider, - @NonNull Consumer networkUnwantedCallback, + @NonNull Consumer networkUnwantedCallback, @NonNull Consumer validationStatusCallback) { - return new NetworkAgent( - vcnContext.getContext(), - vcnContext.getLooper(), + return new VcnNetworkAgent( + vcnContext, tag, caps, lp, score, nac, - provider) { - @Override - public void onNetworkUnwanted() { - networkUnwantedCallback.accept(this); - } - - @Override - public void onValidationStatus(int status, @Nullable Uri redirectUri) { - validationStatusCallback.accept(status); - } - }; + provider, + networkUnwantedCallback, + validationStatusCallback); } /** Gets the elapsed real time since boot, in millis. */ @@ -2231,4 +2222,73 @@ public class VcnGatewayConnection extends StateMachine { mImpl.release(); } } + + /** Proxy Implementation of NetworkAgent, used for testing. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnNetworkAgent { + private final NetworkAgent mImpl; + + public VcnNetworkAgent( + @NonNull VcnContext vcnContext, + @NonNull String tag, + @NonNull NetworkCapabilities caps, + @NonNull LinkProperties lp, + @NonNull int score, + @NonNull NetworkAgentConfig nac, + @NonNull NetworkProvider provider, + @NonNull Consumer networkUnwantedCallback, + @NonNull Consumer validationStatusCallback) { + mImpl = + new NetworkAgent( + vcnContext.getContext(), + vcnContext.getLooper(), + tag, + caps, + lp, + score, + nac, + provider) { + @Override + public void onNetworkUnwanted() { + networkUnwantedCallback.accept(VcnNetworkAgent.this); + } + + @Override + public void onValidationStatus(int status, @Nullable Uri redirectUri) { + validationStatusCallback.accept(status); + } + }; + } + + /** Registers the underlying NetworkAgent */ + public void register() { + mImpl.register(); + } + + /** Marks the underlying NetworkAgent as connected */ + public void markConnected() { + mImpl.markConnected(); + } + + /** Unregisters the underlying NetworkAgent */ + public void unregister() { + mImpl.unregister(); + } + + /** Sends new NetworkCapabilities for the underlying NetworkAgent */ + public void sendNetworkCapabilities(@NonNull NetworkCapabilities caps) { + mImpl.sendNetworkCapabilities(caps); + } + + /** Sends new LinkProperties for the underlying NetworkAgent */ + public void sendLinkProperties(@NonNull LinkProperties lp) { + mImpl.sendLinkProperties(lp); + } + + /** Retrieves the Network for the underlying NetworkAgent */ + @Nullable + public Network getNetwork() { + return mImpl.getNetwork(); + } + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 5dcf4a8621bd4..cb7c96a273dd7 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -26,6 +26,7 @@ import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR; import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; +import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -73,13 +74,13 @@ import java.util.function.Consumer; @SmallTest public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase { private VcnIkeSession mIkeSession; - private NetworkAgent mNetworkAgent; + private VcnNetworkAgent mNetworkAgent; @Before public void setUp() throws Exception { super.setUp(); - mNetworkAgent = mock(NetworkAgent.class); + mNetworkAgent = mock(VcnNetworkAgent.class); doReturn(mNetworkAgent) .when(mDeps) .newNetworkAgent(any(), any(), any(), any(), anyInt(), any(), any(), any(), any()); @@ -299,8 +300,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection .removeAddressFromTunnelInterface( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(TEST_INTERNAL_ADDR), any()); - // TODO(b/184579891): Also verify link properties updated and sent when sendLinkProperties - // is mockable + verify(mNetworkAgent).sendLinkProperties(argThat( + lp -> newInternalAddrs.equals(lp.getLinkAddresses()) + && Collections.singletonList(TEST_DNS_ADDR_2).equals(lp.getDnsServers()))); // Verify that IpSecTunnelInterface only created once verify(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any()); @@ -323,11 +325,11 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertFalse(mGatewayConnection.isInSafeMode()); } - private Consumer setupNetworkAndGetUnwantedCallback() { + private Consumer setupNetworkAndGetUnwantedCallback() { triggerChildOpened(); mTestLooper.dispatchAll(); - final ArgumentCaptor> unwantedCallbackCaptor = + final ArgumentCaptor> unwantedCallbackCaptor = ArgumentCaptor.forClass(Consumer.class); verify(mDeps) .newNetworkAgent( @@ -346,7 +348,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testUnwantedNetworkAgentTriggersTeardown() throws Exception { - final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); + final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); unwantedCallback.accept(mNetworkAgent); mTestLooper.dispatchAll(); @@ -357,7 +359,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testUnwantedNetworkAgentWithDisconnectedNetworkAgent() throws Exception { - final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); + final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); mGatewayConnection.setNetworkAgent(null); unwantedCallback.accept(mNetworkAgent); @@ -371,8 +373,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testUnwantedNetworkAgentWithNewNetworkAgent() throws Exception { - final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); - final NetworkAgent testAgent = mock(NetworkAgent.class); + final Consumer unwantedCallback = setupNetworkAndGetUnwantedCallback(); + final VcnNetworkAgent testAgent = mock(VcnNetworkAgent.class); mGatewayConnection.setNetworkAgent(testAgent); unwantedCallback.accept(mNetworkAgent); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index c5ed8f6ddcc7c..b93347bbd9d25 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -18,6 +18,7 @@ package com.android.server.vcn; import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; +import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; import static org.junit.Assert.assertEquals; @@ -44,7 +45,6 @@ import android.net.IpSecTunnelInterfaceResponse; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; -import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.IkeSessionCallback; @@ -278,8 +278,8 @@ public class VcnGatewayConnectionTestBase { protected void verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent( @NonNull State expectedState) { - // Set a NetworkAgent, and expect it to be unregistered and cleared - final NetworkAgent mockNetworkAgent = mock(NetworkAgent.class); + // Set a VcnNetworkAgent, and expect it to be unregistered and cleared + final VcnNetworkAgent mockNetworkAgent = mock(VcnNetworkAgent.class); mGatewayConnection.setNetworkAgent(mockNetworkAgent); // SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial From 8fac2ea3f3b07d674cae3c6fb608f6d636cf25cd Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Tue, 6 Apr 2021 18:08:43 -0700 Subject: [PATCH 4/4] Dynamically set MTU based on proposed algorithms This change adds the relevant utilities and plumbing to ensure that MTUs are set dynamically based on the underlying network's MTU. Bug: 184697651 Test: atest FrameworksVcnTests Change-Id: I77e34a92eb4e81e83d20fe6019b38ea5f1af4765 --- .../server/vcn/VcnGatewayConnection.java | 96 ++++++++++- .../com/android/server/vcn/util/MtuUtils.java | 155 ++++++++++++++++++ ...cnGatewayConnectionConnectedStateTest.java | 19 ++- .../vcn/VcnGatewayConnectionTestBase.java | 10 ++ .../android/server/vcn/util/MtuUtilsTest.java | 92 +++++++++++ 5 files changed, 364 insertions(+), 8 deletions(-) create mode 100644 services/core/java/com/android/server/vcn/util/MtuUtils.java create mode 100644 tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 8d24c3ab0ca2d..83ac36f9e0f65 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -85,6 +85,7 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; +import com.android.server.vcn.util.MtuUtils; import java.io.IOException; import java.net.Inet4Address; @@ -449,6 +450,44 @@ public class VcnGatewayConnection extends StateMachine { */ private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10; + /** + * Sent when an IKE has completed migration, and created updated transforms for application. + * + *

Only relevant in the Connected state. + * + * @param arg1 The session token for the IKE Session that completed migration, used to prevent + * out-of-date signals from propagating. + * @param obj @NonNull An EventMigrationCompletedInfo instance with relevant data. + */ + private static final int EVENT_MIGRATION_COMPLETED = 11; + + private static class EventMigrationCompletedInfo implements EventInfo { + @NonNull public final IpSecTransform inTransform; + @NonNull public final IpSecTransform outTransform; + + EventMigrationCompletedInfo( + @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) { + this.inTransform = Objects.requireNonNull(inTransform); + this.outTransform = Objects.requireNonNull(outTransform); + } + + @Override + public int hashCode() { + return Objects.hash(inTransform, outTransform); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof EventMigrationCompletedInfo)) { + return false; + } + + final EventMigrationCompletedInfo rhs = (EventMigrationCompletedInfo) other; + return Objects.equals(inTransform, rhs.inTransform) + && Objects.equals(outTransform, rhs.outTransform); + } + } + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -1054,6 +1093,14 @@ public class VcnGatewayConnection extends StateMachine { sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token); } + private void migrationCompleted( + int token, @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) { + sendMessageAndAcquireWakeLock( + EVENT_MIGRATION_COMPLETED, + token, + new EventMigrationCompletedInfo(inTransform, outTransform)); + } + private void childTransformCreated( int token, @NonNull IpSecTransform transform, int direction) { sendMessageAndAcquireWakeLock( @@ -1149,7 +1196,9 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_SETUP_COMPLETED: // Fallthrough case EVENT_DISCONNECT_REQUESTED: // Fallthrough case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough - case EVENT_SUBSCRIPTIONS_CHANGED: + case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough + case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough + case EVENT_MIGRATION_COMPLETED: logUnexpectedEvent(msg.what); break; default: @@ -1446,7 +1495,8 @@ public class VcnGatewayConnection extends StateMachine { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); final LinkProperties lp = - buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); + buildConnectedLinkProperties( + mConnectionConfig, tunnelIface, childConfig, mUnderlying); agent.sendNetworkCapabilities(caps); agent.sendLinkProperties(lp); @@ -1458,7 +1508,8 @@ public class VcnGatewayConnection extends StateMachine { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); final LinkProperties lp = - buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); + buildConnectedLinkProperties( + mConnectionConfig, tunnelIface, childConfig, mUnderlying); final NetworkAgentConfig nac = new NetworkAgentConfig.Builder() .setLegacyType(ConnectivityManager.TYPE_MOBILE) @@ -1627,12 +1678,36 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: handleSafeModeTimeoutExceeded(); break; + case EVENT_MIGRATION_COMPLETED: + final EventMigrationCompletedInfo migrationCompletedInfo = + (EventMigrationCompletedInfo) msg.obj; + + handleMigrationCompleted(migrationCompletedInfo); + break; default: logUnhandledMessage(msg); break; } } + private void handleMigrationCompleted(EventMigrationCompletedInfo migrationCompletedInfo) { + applyTransform( + mCurrentToken, + mTunnelIface, + mUnderlying.network, + migrationCompletedInfo.inTransform, + IpSecManager.DIRECTION_IN); + + applyTransform( + mCurrentToken, + mTunnelIface, + mUnderlying.network, + migrationCompletedInfo.outTransform, + IpSecManager.DIRECTION_OUT); + + updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig); + } + private void handleUnderlyingNetworkChanged(@NonNull Message msg) { final UnderlyingNetworkRecord oldUnderlying = mUnderlying; mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; @@ -1822,7 +1897,10 @@ public class VcnGatewayConnection extends StateMachine { private static LinkProperties buildConnectedLinkProperties( @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, @NonNull IpSecTunnelInterface tunnelIface, - @NonNull VcnChildSessionConfiguration childConfig) { + @NonNull VcnChildSessionConfiguration childConfig, + @Nullable UnderlyingNetworkRecord underlying) { + final VcnControlPlaneIkeConfig controlPlaneConfig = + (VcnControlPlaneIkeConfig) gatewayConnectionConfig.getControlPlaneConfig(); final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(tunnelIface.getInterfaceName()); @@ -1838,7 +1916,12 @@ public class VcnGatewayConnection extends StateMachine { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/, null /*iface*/, RouteInfo.RTN_UNICAST)); - lp.setMtu(gatewayConnectionConfig.getMaxMtu()); + final int underlyingMtu = (underlying == null) ? 0 : underlying.linkProperties.getMtu(); + lp.setMtu( + MtuUtils.getMtu( + controlPlaneConfig.getChildSessionParams().getSaProposals(), + gatewayConnectionConfig.getMaxMtu(), + underlyingMtu)); return lp; } @@ -1919,8 +2002,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull IpSecTransform inIpSecTransform, @NonNull IpSecTransform outIpSecTransform) { Slog.v(TAG, "ChildTransformsMigrated; token " + mToken); - onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN); - onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT); + migrationCompleted(mToken, inIpSecTransform, outIpSecTransform); } @Override diff --git a/services/core/java/com/android/server/vcn/util/MtuUtils.java b/services/core/java/com/android/server/vcn/util/MtuUtils.java new file mode 100644 index 0000000000000..49c1a02215e30 --- /dev/null +++ b/services/core/java/com/android/server/vcn/util/MtuUtils.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 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.util; + +import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES; +import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC; +import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CTR; +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.ENCRYPTION_ALGORITHM_CHACHA20_POLY1305; +import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_CMAC_96; +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.INTEGRITY_ALGORITHM_NONE; + +import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; + +import static java.lang.Math.max; +import static java.util.Collections.unmodifiableMap; + +import android.annotation.NonNull; +import android.net.ipsec.ike.ChildSaProposal; +import android.util.ArrayMap; +import android.util.Pair; +import android.util.Slog; + +import java.util.List; +import java.util.Map; + +/** @hide */ +public class MtuUtils { + private static final String TAG = MtuUtils.class.getSimpleName(); + /** + * Max ESP overhead possible + * + *

60 (Outer IPv4 + options) + 8 (UDP encap) + 4 (SPI) + 4 (Seq) + 2 (Pad + NextHeader) + */ + private static final int GENERIC_ESP_OVERHEAD_MAX = 78; + + /** Maximum overheads of authentication algorithms, keyed on IANA-defined constants */ + private static final Map AUTH_ALGORITHM_OVERHEAD; + + static { + final Map map = new ArrayMap<>(); + map.put(INTEGRITY_ALGORITHM_NONE, 0); + map.put(INTEGRITY_ALGORITHM_HMAC_SHA1_96, 12); + map.put(INTEGRITY_ALGORITHM_AES_XCBC_96, 12); + map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, 32); + map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, 48); + map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, 64); + map.put(INTEGRITY_ALGORITHM_AES_CMAC_96, 12); + + AUTH_ALGORITHM_OVERHEAD = unmodifiableMap(map); + } + + /** Maximum overheads of encryption algorithms, keyed on IANA-defined constants */ + private static final Map CRYPT_ALGORITHM_OVERHEAD; + + static { + final Map map = new ArrayMap<>(); + map.put(ENCRYPTION_ALGORITHM_3DES, 15); // 8 (IV) + 7 (Max pad) + map.put(ENCRYPTION_ALGORITHM_AES_CBC, 31); // 16 (IV) + 15 (Max pad) + map.put(ENCRYPTION_ALGORITHM_AES_CTR, 11); // 8 (IV) + 3 (Max pad) + + CRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map); + } + + /** Maximum overheads of combined mode algorithms, keyed on IANA-defined constants */ + private static final Map AUTHCRYPT_ALGORITHM_OVERHEAD; + + static { + final Map map = new ArrayMap<>(); + map.put(ENCRYPTION_ALGORITHM_AES_GCM_8, 19); // 8 (IV) + 3 (Max pad) + 8 (ICV) + map.put(ENCRYPTION_ALGORITHM_AES_GCM_12, 23); // 8 (IV) + 3 (Max pad) + 12 (ICV) + map.put(ENCRYPTION_ALGORITHM_AES_GCM_16, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV) + map.put(ENCRYPTION_ALGORITHM_CHACHA20_POLY1305, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV) + + AUTHCRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map); + } + + /** + * Calculates the MTU of the inner interface based on the parameters provided + * + *

The MTU of the inner interface will be the minimum of the following: + * + *

+ */ + public static int getMtu( + @NonNull List childProposals, int maxMtu, int underlyingMtu) { + if (underlyingMtu <= 0) { + return IPV6_MIN_MTU; + } + + boolean hasUnknownAlgorithm = false; + int maxAuthOverhead = 0; + int maxCryptOverhead = 0; + int maxAuthCryptOverhead = 0; + + for (ChildSaProposal proposal : childProposals) { + for (Pair encryptionAlgoPair : proposal.getEncryptionAlgorithms()) { + final int algo = encryptionAlgoPair.first; + + if (AUTHCRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) { + maxAuthCryptOverhead = + max(maxAuthCryptOverhead, AUTHCRYPT_ALGORITHM_OVERHEAD.get(algo)); + continue; + } else if (CRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) { + maxCryptOverhead = max(maxCryptOverhead, CRYPT_ALGORITHM_OVERHEAD.get(algo)); + continue; + } + + Slog.wtf(TAG, "Unknown encryption algorithm requested: " + algo); + return IPV6_MIN_MTU; + } + + for (int algo : proposal.getIntegrityAlgorithms()) { + if (AUTH_ALGORITHM_OVERHEAD.containsKey(algo)) { + maxAuthOverhead = max(maxAuthOverhead, AUTH_ALGORITHM_OVERHEAD.get(algo)); + continue; + } + + Slog.wtf(TAG, "Unknown integrity algorithm requested: " + algo); + return IPV6_MIN_MTU; + } + } + + // Return minimum of maxMtu, and the adjusted MTUs based on algorithms. + final int combinedModeMtu = underlyingMtu - maxAuthCryptOverhead - GENERIC_ESP_OVERHEAD_MAX; + final int normalModeMtu = + underlyingMtu - maxCryptOverhead - maxAuthOverhead - GENERIC_ESP_OVERHEAD_MAX; + return Math.min(Math.min(maxMtu, combinedModeMtu), normalModeMtu); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index cb7c96a273dd7..bb67593d84ba1 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -47,15 +47,19 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkAgent; import android.net.NetworkCapabilities; +import android.net.ipsec.ike.ChildSaProposal; import android.net.ipsec.ike.exceptions.AuthenticationFailedException; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.ipsec.ike.exceptions.TemporaryFailureException; +import android.net.vcn.VcnControlPlaneIkeConfig; import android.net.vcn.VcnManager.VcnErrorCode; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.vcn.util.MtuUtils; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -153,7 +157,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test - public void testMigratedTransformsAreApplied() throws Exception { + public void testMigration() throws Exception { + triggerChildOpened(); + getChildSessionCallback() .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); mTestLooper.dispatchAll(); @@ -171,6 +177,17 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + + final List saProposals = + ((VcnControlPlaneIkeConfig) mConfig.getControlPlaneConfig()) + .getChildSessionParams() + .getSaProposals(); + final int expectedMtu = + MtuUtils.getMtu( + saProposals, + mConfig.getMaxMtu(), + TEST_UNDERLYING_NETWORK_RECORD_1.linkProperties.getMtu()); + verify(mNetworkAgent).sendLinkProperties(argThat(lp -> expectedMtu == lp.getMtu())); } private void triggerChildOpened() { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index b93347bbd9d25..dc73be25ffa3c 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -90,12 +90,18 @@ public class VcnGatewayConnectionTestBase { protected static final int TEST_SUB_ID = 5; protected static final long ELAPSED_REAL_TIME = 123456789L; protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; + protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = new UnderlyingNetworkRecord( new Network(0), new NetworkCapabilities(), new LinkProperties(), false /* blocked */); + + static { + TEST_UNDERLYING_NETWORK_RECORD_1.linkProperties.setMtu(1500); + } + protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 = new UnderlyingNetworkRecord( new Network(1), @@ -103,6 +109,10 @@ public class VcnGatewayConnectionTestBase { new LinkProperties(), false /* blocked */); + static { + TEST_UNDERLYING_NETWORK_RECORD_2.linkProperties.setMtu(1460); + } + protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT = new TelephonySubscriptionSnapshot( Collections.singletonMap(TEST_SUB_ID, TEST_SUB_GRP), Collections.EMPTY_MAP); diff --git a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java new file mode 100644 index 0000000000000..29511f780bf61 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java @@ -0,0 +1,92 @@ +/* + * 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.util; + +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_HMAC_SHA2_256_128; +import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU; +import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; +import static com.android.server.vcn.util.MtuUtils.getMtu; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import static java.util.Collections.emptyList; + +import android.net.ipsec.ike.ChildSaProposal; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MtuUtilsTest { + @Test + public void testUnderlyingMtuZero() { + assertEquals( + IPV6_MIN_MTU, getMtu(emptyList(), ETHER_MTU /* maxMtu */, 0 /* underlyingMtu */)); + } + + @Test + public void testClampsToMaxMtu() { + assertEquals(0, getMtu(emptyList(), 0 /* maxMtu */, IPV6_MIN_MTU /* underlyingMtu */)); + } + + @Test + public void testNormalModeAlgorithmLessThanUnderlyingMtu() { + final List saProposals = + Arrays.asList( + new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256) + .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) + .build()); + + final int actualMtu = + getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */); + assertTrue(ETHER_MTU > actualMtu); + } + + @Test + public void testCombinedModeAlgorithmLessThanUnderlyingMtu() { + final List saProposals = + Arrays.asList( + new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256) + .addEncryptionAlgorithm( + ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256) + .addEncryptionAlgorithm( + ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256) + .build()); + + final int actualMtu = + getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */); + assertTrue(ETHER_MTU > actualMtu); + } +}