Loading services/core/java/com/android/server/vcn/Vcn.java +2 −1 Original line number Diff line number Diff line Loading @@ -214,7 +214,8 @@ public class Vcn extends Handler { } /** Retrieves the network score for a VCN Network */ private int getNetworkScore() { // Package visibility for use in VcnGatewayConnection static int getNetworkScore() { // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in // subGrp" value return 52; Loading services/core/java/com/android/server/vcn/VcnGatewayConnection.java +249 −3 Original line number Diff line number Diff line Loading @@ -17,8 +17,11 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.server.VcnManagementService.VDBG; Loading @@ -34,8 +37,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.RouteInfo; import android.net.TelephonyNetworkSpecifier; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; Loading @@ -47,10 +52,13 @@ 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.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; Loading @@ -64,6 +72,7 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Arrays; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; Loading Loading @@ -113,6 +122,9 @@ import java.util.concurrent.TimeUnit; public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; Loading Loading @@ -537,6 +549,8 @@ public class VcnGatewayConnection extends StateMachine { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { // TODO(b/179091925): Move the delayed-message handling to BaseState // If underlying is null, all underlying networks have been lost. Disconnect VCN after a // timeout. if (underlying == null) { Loading Loading @@ -921,6 +935,8 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectingState); break; case EVENT_SESSION_CLOSED: // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this // message may not be posted again. Defer to ensure immediate shutdown. deferMessage(msg); transitionTo(mDisconnectingState); Loading @@ -941,7 +957,108 @@ public class VcnGatewayConnection extends StateMachine { } } private abstract class ConnectedStateBase extends ActiveBaseState {} private abstract class ConnectedStateBase extends ActiveBaseState { protected void updateNetworkAgent( @NonNull IpSecTunnelInterface tunnelIface, @NonNull NetworkAgent agent, @NonNull ChildSessionConfiguration childConfig) { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); final LinkProperties lp = buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); agent.sendNetworkCapabilities(caps); agent.sendLinkProperties(lp); } protected NetworkAgent buildNetworkAgent( @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig) { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); final LinkProperties lp = buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); final NetworkAgent agent = new NetworkAgent( mVcnContext.getContext(), mVcnContext.getLooper(), TAG, caps, lp, Vcn.getNetworkScore(), new NetworkAgentConfig(), mVcnContext.getVcnNetworkProvider()) { @Override public void unwanted() { teardownAsynchronously(); } }; agent.register(); agent.markConnected(); return agent; } protected void applyTransform( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull Network underlyingNetwork, @NonNull IpSecTransform transform, int direction) { try { // TODO: Set underlying network of tunnel interface // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); } catch (IOException e) { Slog.d(TAG, "Transform application failed for network " + token, e); sessionLost(token, e); } } protected void setupInterface( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig) { setupInterface(token, tunnelIface, childConfig, null); } protected void setupInterface( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig, @Nullable ChildSessionConfiguration oldChildConfig) { try { final Set<LinkAddress> newAddrs = new ArraySet<>(childConfig.getInternalAddresses()); final Set<LinkAddress> existingAddrs = new ArraySet<>(); if (oldChildConfig != null) { existingAddrs.addAll(oldChildConfig.getInternalAddresses()); } final Set<LinkAddress> toAdd = new ArraySet<>(); toAdd.addAll(newAddrs); toAdd.removeAll(existingAddrs); final Set<LinkAddress> toRemove = new ArraySet<>(); toRemove.addAll(existingAddrs); toRemove.removeAll(newAddrs); for (LinkAddress address : toAdd) { tunnelIface.addAddress(address.getAddress(), address.getPrefixLength()); } for (LinkAddress address : toRemove) { tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength()); } } catch (IOException e) { Slog.d(TAG, "Adding address to tunnel failed for token " + token, e); sessionLost(token, e); } } } /** * Stable state representing a VCN that has a functioning connection to the mobility anchor. Loading @@ -951,7 +1068,89 @@ public class VcnGatewayConnection extends StateMachine { */ class ConnectedState extends ConnectedStateBase { @Override protected void processStateMsg(Message msg) {} protected void enterState() throws Exception { // Successful connection, clear failed attempt counter mFailedAttempts = 0; } @Override protected void processStateMsg(Message msg) { switch (msg.what) { case EVENT_UNDERLYING_NETWORK_CHANGED: handleUnderlyingNetworkChanged(msg); break; case EVENT_SESSION_CLOSED: // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this // message may not be posted again. Defer to ensure immediate shutdown. deferMessage(msg); transitionTo(mDisconnectingState); break; case EVENT_SESSION_LOST: transitionTo(mDisconnectingState); break; case EVENT_TRANSFORM_CREATED: final EventTransformCreatedInfo transformCreatedInfo = (EventTransformCreatedInfo) msg.obj; applyTransform( mCurrentToken, mTunnelIface, mUnderlying.network, transformCreatedInfo.transform, transformCreatedInfo.direction); break; case EVENT_SETUP_COMPLETED: mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig; setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); break; case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; default: logUnhandledMessage(msg); break; } } private void handleUnderlyingNetworkChanged(@NonNull Message msg) { final UnderlyingNetworkRecord oldUnderlying = mUnderlying; mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; if (mUnderlying == null) { // Ignored for now; a new network may be coming up. If none does, the delayed // NETWORK_LOST disconnect will be fired, and tear down the session + network. return; } // mUnderlying assumed non-null, given check above. // If network changed, migrate. Otherwise, update any existing networkAgent. if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { mIkeSession.setNetwork(mUnderlying.network); } else { // oldUnderlying is non-null & underlying network itself has not changed // (only network properties were changed). // Network not yet set up, or child not yet connected. if (mNetworkAgent != null && mChildConfig != null) { // If only network properties changed and agent is active, update properties updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig); } } } protected void setupInterfaceAndNetworkAgent( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig) { setupInterface(token, tunnelIface, childConfig); if (mNetworkAgent == null) { mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); } else { updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); } } } /** Loading @@ -966,7 +1165,8 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static NetworkCapabilities buildNetworkCapabilities( @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, @Nullable UnderlyingNetworkRecord underlying) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); builder.addTransportType(TRANSPORT_CELLULAR); Loading @@ -978,6 +1178,52 @@ public class VcnGatewayConnection extends StateMachine { builder.addCapability(cap); } if (underlying != null) { final NetworkCapabilities underlyingCaps = underlying.networkCapabilities; // Mirror merged capabilities. for (int cap : MERGED_CAPABILITIES) { if (underlyingCaps.hasCapability(cap)) { builder.addCapability(cap); } } // Set admin UIDs for ConnectivityDiagnostics use. final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids(); Arrays.sort(underlyingAdminUids); // Sort to allow contains check below. final int[] adminUids; if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list. underlyingAdminUids, underlyingCaps.getOwnerUid())) { adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1); adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid(); Arrays.sort(adminUids); } else { adminUids = underlyingAdminUids; } builder.setAdministratorUids(adminUids); // Set TransportInfo for SysUI use (never parcelled out of SystemServer). if (underlyingCaps.hasTransport(TRANSPORT_WIFI) && underlyingCaps.getTransportInfo() instanceof WifiInfo) { final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo(); builder.setTransportInfo(new VcnTransportInfo(wifiInfo)); } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR) && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) { final TelephonyNetworkSpecifier telNetSpecifier = (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier(); builder.setTransportInfo(new VcnTransportInfo(telNetSpecifier.getSubscriptionId())); } else { Slog.wtf( TAG, "Unknown transport type or missing TransportInfo/NetworkSpecifier for" + " non-null underlying network"); } } // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs. return builder.build(); } Loading tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * 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 android.net.IpSecManager.DIRECTION_IN; import static android.net.IpSecManager.DIRECTION_OUT; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** Tests for VcnGatewayConnection.ConnectedState */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase { private VcnIkeSession mIkeSession; @Before public void setUp() throws Exception { super.setUp(); mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); mIkeSession = mGatewayConnection.buildIkeSession(); mGatewayConnection.setIkeSession(mIkeSession); mGatewayConnection.transitionTo(mGatewayConnection.mConnectedState); mTestLooper.dispatchAll(); } @Test public void testEnterStateCreatesNewIkeSession() throws Exception { verify(mDeps).newIkeSession(any(), any(), any(), any(), any()); } @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); verify(mIkeSession, never()).close(); } @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); verify(mIkeSession, never()).close(); verify(mIkeSession).setNetwork(TEST_UNDERLYING_NETWORK_RECORD_2.network); } @Test public void testSameNetworkDoesNotTriggerMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } @Test public void testCreatedTransformsAreApplied() throws Exception { for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction); mTestLooper.dispatchAll(); verify(mIpSecSvc) .applyTunnelModeTransform( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } @Test public void testChildSessionClosedTriggersDisconnect() throws Exception { getChildSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); } @Test public void testIkeSessionClosedTriggersDisconnect() throws Exception { getIkeSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); } // TODO: Add tests for childOpened() when ChildSessionConfiguration can be mocked or created } tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +59 −6 Original line number Diff line number Diff line Loading @@ -16,20 +16,35 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import android.annotation.NonNull; import android.content.Context; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.ParcelUuid; import android.os.Process; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -42,6 +57,7 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionTest { private static final int TEST_UID = Process.myUid(); 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; Loading @@ -61,22 +77,59 @@ public class VcnGatewayConnectionTest { @NonNull private final TestLooper mTestLooper; @NonNull private final VcnNetworkProvider mVcnNetworkProvider; @NonNull private final VcnGatewayConnection.Dependencies mDeps; @NonNull private final WifiInfo mWifiInfo; public VcnGatewayConnectionTest() { mContext = mock(Context.class); mTestLooper = new TestLooper(); mVcnNetworkProvider = mock(VcnNetworkProvider.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); mWifiInfo = mock(WifiInfo.class); } @Test public void testBuildNetworkCapabilities() throws Exception { final NetworkCapabilities caps = private void verifyBuildNetworkCapabilitiesCommon(int transportType) { final NetworkCapabilities underlyingCaps = new NetworkCapabilities(); underlyingCaps.addTransportType(transportType); underlyingCaps.addCapability(NET_CAPABILITY_NOT_METERED); underlyingCaps.addCapability(NET_CAPABILITY_NOT_ROAMING); if (transportType == TRANSPORT_WIFI) { underlyingCaps.setTransportInfo(mWifiInfo); underlyingCaps.setOwnerUid(TEST_UID); } else if (transportType == TRANSPORT_CELLULAR) { underlyingCaps.setAdministratorUids(new int[] {TEST_UID}); underlyingCaps.setNetworkSpecifier( new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_1)); } UnderlyingNetworkRecord record = new UnderlyingNetworkRecord( new Network(0), underlyingCaps, new LinkProperties(), false); final NetworkCapabilities vcnCaps = VcnGatewayConnection.buildNetworkCapabilities( VcnGatewayConnectionConfigTest.buildTestConfig()); VcnGatewayConnectionConfigTest.buildTestConfig(), record); assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertArrayEquals(new int[] {TEST_UID}, vcnCaps.getAdministratorUids()); assertTrue(vcnCaps.getTransportInfo() instanceof VcnTransportInfo); for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { assertTrue(caps.hasCapability(exposedCapability)); final VcnTransportInfo info = (VcnTransportInfo) vcnCaps.getTransportInfo(); if (transportType == TRANSPORT_WIFI) { assertEquals(mWifiInfo, info.getWifiInfo()); } else if (transportType == TRANSPORT_CELLULAR) { assertEquals(TEST_SUBSCRIPTION_ID_1, info.getSubId()); } } @Test public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception { verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI); } @Test public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception { verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR); } } tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +10 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/vcn/Vcn.java +2 −1 Original line number Diff line number Diff line Loading @@ -214,7 +214,8 @@ public class Vcn extends Handler { } /** Retrieves the network score for a VCN Network */ private int getNetworkScore() { // Package visibility for use in VcnGatewayConnection static int getNetworkScore() { // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in // subGrp" value return 52; Loading
services/core/java/com/android/server/vcn/VcnGatewayConnection.java +249 −3 Original line number Diff line number Diff line Loading @@ -17,8 +17,11 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.server.VcnManagementService.VDBG; Loading @@ -34,8 +37,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.RouteInfo; import android.net.TelephonyNetworkSpecifier; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; Loading @@ -47,10 +52,13 @@ 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.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; Loading @@ -64,6 +72,7 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Arrays; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; Loading Loading @@ -113,6 +122,9 @@ import java.util.concurrent.TimeUnit; public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; Loading Loading @@ -537,6 +549,8 @@ public class VcnGatewayConnection extends StateMachine { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { // TODO(b/179091925): Move the delayed-message handling to BaseState // If underlying is null, all underlying networks have been lost. Disconnect VCN after a // timeout. if (underlying == null) { Loading Loading @@ -921,6 +935,8 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectingState); break; case EVENT_SESSION_CLOSED: // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this // message may not be posted again. Defer to ensure immediate shutdown. deferMessage(msg); transitionTo(mDisconnectingState); Loading @@ -941,7 +957,108 @@ public class VcnGatewayConnection extends StateMachine { } } private abstract class ConnectedStateBase extends ActiveBaseState {} private abstract class ConnectedStateBase extends ActiveBaseState { protected void updateNetworkAgent( @NonNull IpSecTunnelInterface tunnelIface, @NonNull NetworkAgent agent, @NonNull ChildSessionConfiguration childConfig) { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); final LinkProperties lp = buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); agent.sendNetworkCapabilities(caps); agent.sendLinkProperties(lp); } protected NetworkAgent buildNetworkAgent( @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig) { final NetworkCapabilities caps = buildNetworkCapabilities(mConnectionConfig, mUnderlying); final LinkProperties lp = buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); final NetworkAgent agent = new NetworkAgent( mVcnContext.getContext(), mVcnContext.getLooper(), TAG, caps, lp, Vcn.getNetworkScore(), new NetworkAgentConfig(), mVcnContext.getVcnNetworkProvider()) { @Override public void unwanted() { teardownAsynchronously(); } }; agent.register(); agent.markConnected(); return agent; } protected void applyTransform( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull Network underlyingNetwork, @NonNull IpSecTransform transform, int direction) { try { // TODO: Set underlying network of tunnel interface // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); } catch (IOException e) { Slog.d(TAG, "Transform application failed for network " + token, e); sessionLost(token, e); } } protected void setupInterface( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig) { setupInterface(token, tunnelIface, childConfig, null); } protected void setupInterface( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig, @Nullable ChildSessionConfiguration oldChildConfig) { try { final Set<LinkAddress> newAddrs = new ArraySet<>(childConfig.getInternalAddresses()); final Set<LinkAddress> existingAddrs = new ArraySet<>(); if (oldChildConfig != null) { existingAddrs.addAll(oldChildConfig.getInternalAddresses()); } final Set<LinkAddress> toAdd = new ArraySet<>(); toAdd.addAll(newAddrs); toAdd.removeAll(existingAddrs); final Set<LinkAddress> toRemove = new ArraySet<>(); toRemove.addAll(existingAddrs); toRemove.removeAll(newAddrs); for (LinkAddress address : toAdd) { tunnelIface.addAddress(address.getAddress(), address.getPrefixLength()); } for (LinkAddress address : toRemove) { tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength()); } } catch (IOException e) { Slog.d(TAG, "Adding address to tunnel failed for token " + token, e); sessionLost(token, e); } } } /** * Stable state representing a VCN that has a functioning connection to the mobility anchor. Loading @@ -951,7 +1068,89 @@ public class VcnGatewayConnection extends StateMachine { */ class ConnectedState extends ConnectedStateBase { @Override protected void processStateMsg(Message msg) {} protected void enterState() throws Exception { // Successful connection, clear failed attempt counter mFailedAttempts = 0; } @Override protected void processStateMsg(Message msg) { switch (msg.what) { case EVENT_UNDERLYING_NETWORK_CHANGED: handleUnderlyingNetworkChanged(msg); break; case EVENT_SESSION_CLOSED: // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this // message may not be posted again. Defer to ensure immediate shutdown. deferMessage(msg); transitionTo(mDisconnectingState); break; case EVENT_SESSION_LOST: transitionTo(mDisconnectingState); break; case EVENT_TRANSFORM_CREATED: final EventTransformCreatedInfo transformCreatedInfo = (EventTransformCreatedInfo) msg.obj; applyTransform( mCurrentToken, mTunnelIface, mUnderlying.network, transformCreatedInfo.transform, transformCreatedInfo.direction); break; case EVENT_SETUP_COMPLETED: mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig; setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); break; case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; default: logUnhandledMessage(msg); break; } } private void handleUnderlyingNetworkChanged(@NonNull Message msg) { final UnderlyingNetworkRecord oldUnderlying = mUnderlying; mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; if (mUnderlying == null) { // Ignored for now; a new network may be coming up. If none does, the delayed // NETWORK_LOST disconnect will be fired, and tear down the session + network. return; } // mUnderlying assumed non-null, given check above. // If network changed, migrate. Otherwise, update any existing networkAgent. if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { mIkeSession.setNetwork(mUnderlying.network); } else { // oldUnderlying is non-null & underlying network itself has not changed // (only network properties were changed). // Network not yet set up, or child not yet connected. if (mNetworkAgent != null && mChildConfig != null) { // If only network properties changed and agent is active, update properties updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig); } } } protected void setupInterfaceAndNetworkAgent( int token, @NonNull IpSecTunnelInterface tunnelIface, @NonNull ChildSessionConfiguration childConfig) { setupInterface(token, tunnelIface, childConfig); if (mNetworkAgent == null) { mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); } else { updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); } } } /** Loading @@ -966,7 +1165,8 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static NetworkCapabilities buildNetworkCapabilities( @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, @Nullable UnderlyingNetworkRecord underlying) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); builder.addTransportType(TRANSPORT_CELLULAR); Loading @@ -978,6 +1178,52 @@ public class VcnGatewayConnection extends StateMachine { builder.addCapability(cap); } if (underlying != null) { final NetworkCapabilities underlyingCaps = underlying.networkCapabilities; // Mirror merged capabilities. for (int cap : MERGED_CAPABILITIES) { if (underlyingCaps.hasCapability(cap)) { builder.addCapability(cap); } } // Set admin UIDs for ConnectivityDiagnostics use. final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids(); Arrays.sort(underlyingAdminUids); // Sort to allow contains check below. final int[] adminUids; if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list. underlyingAdminUids, underlyingCaps.getOwnerUid())) { adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1); adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid(); Arrays.sort(adminUids); } else { adminUids = underlyingAdminUids; } builder.setAdministratorUids(adminUids); // Set TransportInfo for SysUI use (never parcelled out of SystemServer). if (underlyingCaps.hasTransport(TRANSPORT_WIFI) && underlyingCaps.getTransportInfo() instanceof WifiInfo) { final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo(); builder.setTransportInfo(new VcnTransportInfo(wifiInfo)); } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR) && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) { final TelephonyNetworkSpecifier telNetSpecifier = (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier(); builder.setTransportInfo(new VcnTransportInfo(telNetSpecifier.getSubscriptionId())); } else { Slog.wtf( TAG, "Unknown transport type or missing TransportInfo/NetworkSpecifier for" + " non-null underlying network"); } } // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs. return builder.build(); } Loading
tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * 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 android.net.IpSecManager.DIRECTION_IN; import static android.net.IpSecManager.DIRECTION_OUT; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** Tests for VcnGatewayConnection.ConnectedState */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase { private VcnIkeSession mIkeSession; @Before public void setUp() throws Exception { super.setUp(); mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); mIkeSession = mGatewayConnection.buildIkeSession(); mGatewayConnection.setIkeSession(mIkeSession); mGatewayConnection.transitionTo(mGatewayConnection.mConnectedState); mTestLooper.dispatchAll(); } @Test public void testEnterStateCreatesNewIkeSession() throws Exception { verify(mDeps).newIkeSession(any(), any(), any(), any(), any()); } @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); verify(mIkeSession, never()).close(); } @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); verify(mIkeSession, never()).close(); verify(mIkeSession).setNetwork(TEST_UNDERLYING_NETWORK_RECORD_2.network); } @Test public void testSameNetworkDoesNotTriggerMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } @Test public void testCreatedTransformsAreApplied() throws Exception { for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction); mTestLooper.dispatchAll(); verify(mIpSecSvc) .applyTunnelModeTransform( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } @Test public void testChildSessionClosedTriggersDisconnect() throws Exception { getChildSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); } @Test public void testIkeSessionClosedTriggersDisconnect() throws Exception { getIkeSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); } // TODO: Add tests for childOpened() when ChildSessionConfiguration can be mocked or created }
tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +59 −6 Original line number Diff line number Diff line Loading @@ -16,20 +16,35 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import android.annotation.NonNull; import android.content.Context; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.ParcelUuid; import android.os.Process; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -42,6 +57,7 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionTest { private static final int TEST_UID = Process.myUid(); 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; Loading @@ -61,22 +77,59 @@ public class VcnGatewayConnectionTest { @NonNull private final TestLooper mTestLooper; @NonNull private final VcnNetworkProvider mVcnNetworkProvider; @NonNull private final VcnGatewayConnection.Dependencies mDeps; @NonNull private final WifiInfo mWifiInfo; public VcnGatewayConnectionTest() { mContext = mock(Context.class); mTestLooper = new TestLooper(); mVcnNetworkProvider = mock(VcnNetworkProvider.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); mWifiInfo = mock(WifiInfo.class); } @Test public void testBuildNetworkCapabilities() throws Exception { final NetworkCapabilities caps = private void verifyBuildNetworkCapabilitiesCommon(int transportType) { final NetworkCapabilities underlyingCaps = new NetworkCapabilities(); underlyingCaps.addTransportType(transportType); underlyingCaps.addCapability(NET_CAPABILITY_NOT_METERED); underlyingCaps.addCapability(NET_CAPABILITY_NOT_ROAMING); if (transportType == TRANSPORT_WIFI) { underlyingCaps.setTransportInfo(mWifiInfo); underlyingCaps.setOwnerUid(TEST_UID); } else if (transportType == TRANSPORT_CELLULAR) { underlyingCaps.setAdministratorUids(new int[] {TEST_UID}); underlyingCaps.setNetworkSpecifier( new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_1)); } UnderlyingNetworkRecord record = new UnderlyingNetworkRecord( new Network(0), underlyingCaps, new LinkProperties(), false); final NetworkCapabilities vcnCaps = VcnGatewayConnection.buildNetworkCapabilities( VcnGatewayConnectionConfigTest.buildTestConfig()); VcnGatewayConnectionConfigTest.buildTestConfig(), record); assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertArrayEquals(new int[] {TEST_UID}, vcnCaps.getAdministratorUids()); assertTrue(vcnCaps.getTransportInfo() instanceof VcnTransportInfo); for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { assertTrue(caps.hasCapability(exposedCapability)); final VcnTransportInfo info = (VcnTransportInfo) vcnCaps.getTransportInfo(); if (transportType == TRANSPORT_WIFI) { assertEquals(mWifiInfo, info.getWifiInfo()); } else if (transportType == TRANSPORT_CELLULAR) { assertEquals(TEST_SUBSCRIPTION_ID_1, info.getSubId()); } } @Test public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception { verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI); } @Test public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception { verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR); } }
tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +10 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes