Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 561cf967 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "Send vpn metrics" into main

* changes:
  Send connection metrics when status changes
  Reset VpnConnectivityMetrics on VPN disconnect
  Adjust the timing of VPN type/profile/algorithms metrics collection
  Validate and correct the VPN metrics
  Send VPN connection state metrics
parents 508e1180 73dccb6f
Loading
Loading
Loading
Loading
+26 −11
Original line number Diff line number Diff line
@@ -711,6 +711,10 @@ public class Vpn {
        return mEnableTeardown;
    }

    private boolean isVpnMetricsLoggable() {
        return mVpnConnectivityMetrics != null && mVpnConnectivityMetrics.isPlatformVpn();
    }

    /**
     * Update current state, dispatching event to listeners.
     */
@@ -731,6 +735,9 @@ public class Vpn {
            case CONNECTED:
                if (null != mNetworkAgent) {
                    mNetworkAgent.markConnected();
                    if (isVpnMetricsLoggable()) {
                        mVpnConnectivityMetrics.notifyVpnConnected();
                    }
                }
                break;
            case DISCONNECTED:
@@ -738,6 +745,11 @@ public class Vpn {
                if (null != mNetworkAgent) {
                    mNetworkAgent.unregister();
                    mNetworkAgent = null;
                    if (isVpnMetricsLoggable()) {
                        mVpnConnectivityMetrics.notifyVpnDisconnected();
                        // Clear the metrics since the NetworkAgent is disconnected.
                        mVpnConnectivityMetrics.resetMetrics();
                    }
                }
                break;
            case CONNECTING:
@@ -2289,7 +2301,7 @@ public class Vpn {
        return success;
    }

    private void setMtu(int mtu) {
    private void setMtuAndMetrics(int mtu) {
        synchronized (Vpn.this) {
            mConfig.mtu = mtu;
            if (mVpnConnectivityMetrics != null) {
@@ -2302,7 +2314,7 @@ public class Vpn {
        synchronized (Vpn.this) {
            mConfig.underlyingNetworks = networks;
            if (mVpnConnectivityMetrics != null) {
                mVpnConnectivityMetrics.setUnderlyingNetwork(mConfig.underlyingNetworks);
                mVpnConnectivityMetrics.updateUnderlyingNetworkTypes(mConfig.underlyingNetworks);
            }
        }
    }
@@ -2993,7 +3005,8 @@ public class Vpn {
            // in onChildMigrated
            mIkeConnectionInfo = ikeConnectionInfo;
            if (mVpnConnectivityMetrics != null) {
                mVpnConnectivityMetrics.setServerIpProtocol(ikeConnectionInfo.getRemoteAddress());
                mVpnConnectivityMetrics.updateServerIpProtocol(
                        ikeConnectionInfo.getRemoteAddress());
            }
        }

@@ -3058,14 +3071,21 @@ public class Vpn {
                    // Ignore stale runner.
                    if (mVpnRunner != this) return;

                    if (mVpnConnectivityMetrics != null) {
                        mVpnConnectivityMetrics.setVpnType(VpnManager.TYPE_VPN_PLATFORM);
                        mVpnConnectivityMetrics.setVpnProfileType(mProfile.toVpnProfile().type);
                        mVpnConnectivityMetrics.setAllowedAlgorithms(
                                mProfile.getAllowedAlgorithms());
                    }

                    mInterface = interfaceName;
                    setMtu(vpnMtu);
                    setMtuAndMetrics(vpnMtu);
                    mConfig.interfaze = mInterface;

                    mConfig.addresses.clear();
                    mConfig.addresses.addAll(internalAddresses);
                    if (mVpnConnectivityMetrics != null) {
                        mVpnConnectivityMetrics.setVpnNetworkIpProtocol(mConfig.addresses);
                        mVpnConnectivityMetrics.updateVpnNetworkIpProtocol(mConfig.addresses);
                    }

                    mConfig.routes.clear();
@@ -3169,7 +3189,7 @@ public class Vpn {
                    final LinkProperties oldLp = makeLinkProperties();

                    setUnderlyingNetworksAndMetrics(new Network[] {network});
                    setMtu(calculateVpnMtu());
                    setMtuAndMetrics(calculateVpnMtu());
                    final LinkProperties newLp = makeLinkProperties();

                    // If MTU is < 1280, IPv6 addresses will be removed. If there are no addresses
@@ -4274,11 +4294,6 @@ public class Vpn {
            config.allowBypass = profile.isBypassable;
            config.disallowedApplications = getAppExclusionList(mPackage);
            mConfig = config;
            if (mVpnConnectivityMetrics != null) {
                mVpnConnectivityMetrics.setVpnType(VpnManager.TYPE_VPN_PLATFORM);
                mVpnConnectivityMetrics.setVpnProfileType(profile.type);
                mVpnConnectivityMetrics.setAllowedAlgorithms(profile.getAllowedAlgorithms());
            }

            switch (profile.type) {
                case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+248 −27
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
import static android.net.IpSecAlgorithm.CRYPT_AES_CTR;
import static android.net.VpnManager.TYPE_VPN_OEM;
import static android.net.VpnManager.TYPE_VPN_PLATFORM;

import android.net.ConnectivityManager;
import android.net.LinkAddress;
@@ -39,7 +41,10 @@ import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.util.FrameworkStatsLog;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
@@ -50,11 +55,14 @@ import java.util.List;
 */
public class VpnConnectivityMetrics {
    private static final String TAG = VpnConnectivityMetrics.class.getSimpleName();
    public static final int VPN_TYPE_UNKNOWN = 0;
    public static final int VPN_PROFILE_TYPE_UNKNOWN = 0;
    private static final int UNKNOWN_UNDERLYING_NETWORK_TYPE = -1;
    // Copied from corenetworking platform vpn enum
    @VisibleForTesting
    static final int VPN_TYPE_UNKNOWN = 0;
    @VisibleForTesting
    static final int VPN_PROFILE_TYPE_UNKNOWN = 0;
    private static final int VPN_PROFILE_TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS = 10;
    private static final int UNKNOWN_UNDERLYING_NETWORK_TYPE = -1;
    @VisibleForTesting
    static final int IP_PROTOCOL_UNKNOWN = 0;
    @VisibleForTesting
    static final int IP_PROTOCOL_IPv4 = 1;
@@ -66,6 +74,8 @@ public class VpnConnectivityMetrics {
    private final int mUserId;
    @NonNull
    private final ConnectivityManager mConnectivityManager;
    @NonNull
    private final Dependencies mDependencies;
    private int mVpnType = VPN_TYPE_UNKNOWN;
    private int mVpnProfileType = VPN_PROFILE_TYPE_UNKNOWN;
    private int mMtu = 0;
@@ -76,6 +86,14 @@ public class VpnConnectivityMetrics {
     * index in {@code sAlgorithms} is considered allowed.
     */
    private int mAllowedAlgorithms = 0;
    /**
     * The maximum value that {@code mAllowedAlgorithms} can take.
     * It's calculated based on the number of algorithms defined in {@code sAlgorithms}.
     * Each algorithm corresponds to a bit in the bitmask, so the maximum value is
     * 2^numberOfAlgorithms - 1.
     * This value should be updated if {@code sAlgorithms} is modified.
     */
    private static final int MAX_ALLOWED_ALGORITHMS_VALUE = (1 << 11) - 1;
    /**
     * An array representing the transport types of the underlying networks for the VPN.
     * Each element in this array corresponds to a specific underlying network.
@@ -86,7 +104,7 @@ public class VpnConnectivityMetrics {
     * {@code UNKNOWN_UNDERLYING_NETWORK_TYPE}.
     */
    @NonNull
    private int[] mUnderlyingNetworkTypes;
    private int[] mUnderlyingNetworkTypes = new int[0];
    private int mVpnNetworkIpProtocol = IP_PROTOCOL_UNKNOWN;
    private int mServerIpProtocol = IP_PROTOCOL_UNKNOWN;

@@ -107,9 +125,33 @@ public class VpnConnectivityMetrics {
        sAlgorithms.put(10, CRYPT_AES_CTR);
    }

    /**
     * Dependencies of VpnConnectivityMetrics, for injection in tests.
     */
    public static class Dependencies {

        /**
         * @see FrameworkStatsLog
         */
        public void statsWrite(int vpnType, int vpnNetworkIpProtocol, int serverIpProtocol,
                int vpnProfileType, int allowedAlgorithms, int mtu, int[] underlyingNetworkType,
                boolean connected, int userId) {
            FrameworkStatsLog.write(FrameworkStatsLog.VPN_CONNECTION_REPORTED, vpnType,
                    vpnNetworkIpProtocol, serverIpProtocol, vpnProfileType, allowedAlgorithms, mtu,
                    underlyingNetworkType, connected, userId);
        }
    }

    public VpnConnectivityMetrics(int userId, ConnectivityManager connectivityManager) {
        this(userId, connectivityManager, new Dependencies());
    }

    @VisibleForTesting
    VpnConnectivityMetrics(int userId, ConnectivityManager connectivityManager,
            Dependencies dependencies) {
        mUserId = userId;
        mConnectivityManager = connectivityManager;
        mDependencies = dependencies;
    }

    /**
@@ -190,51 +232,132 @@ public class VpnConnectivityMetrics {
     * for a specific network, a predefined {@code UNKNOWN_UNDERLYING_NETWORK_TYPE} is
     * used for that entry.
     *
     * <p>
     * Note: This method contains a synchronized call to ConnectivityManager to query the network
     * capability, so this method should not be called from the Network Callback.
     *
     * <p>
     * If the underlying network types have changed are different from current, a sequence of
     * notifications is triggered: first, a disconnection notification is sent with the old value,
     * then {@code mUnderlyingNetworkTypes} is updated with the new value, and finally, a connection
     * notification is issued with the new value.
     *
     * @param networks An array of {@link android.net.Network} objects representing the underlying
     *                 networks currently in use.
     */
    public void setUnderlyingNetwork(@NonNull Network[] networks) {
        if (networks.length != 0) {
            int[] types = new int[networks.length];
    public void updateUnderlyingNetworkTypes(@NonNull Network[] networks) {
        // Note: If the underlying network is lost, an empty underlying network won't be set.
        // Instead, only a countdown timer will be activated. After a timeout, the NetworkAgent
        // disconnects, and the disconnection is then notified from there. Therefore, the recorded
        // time may not be accurate because there may be a gap between the NetworkAgent disconnect
        // and the loss of the underlying network.
        if (networks.length == 0) {
            return; // Return if no networks.
        }

        int[] newTypes = new int[networks.length];
        for (int i = 0; i < networks.length; i++) {
            final NetworkCapabilities capabilities =
                    mConnectivityManager.getNetworkCapabilities(networks[i]);
            if (capabilities != null) {
                // Get the primary transport type of the network.
                    types[i] = capabilities.getTransportTypes()[0];
                newTypes[i] = capabilities.getTransportTypes()[0];
            } else {
                    types[i] = UNKNOWN_UNDERLYING_NETWORK_TYPE;
                newTypes[i] = UNKNOWN_UNDERLYING_NETWORK_TYPE;
            }
        }
            mUnderlyingNetworkTypes = Arrays.copyOf(types, types.length);
        } else {
            mUnderlyingNetworkTypes = new int[0];
        // Set the underlying network types directly if it's the default value, skipping the
        // connection status notification. Those notifications will be sent only when the VPN
        // connection is established and the underlying network types change.
        if (mUnderlyingNetworkTypes.length == 0) {
            mUnderlyingNetworkTypes = newTypes;
            return;
        }
        // Return if no type change.
        if (Arrays.equals(mUnderlyingNetworkTypes, newTypes)) {
            return;
        }
        // Notify the ip protocol change and set the new ip protocol.
        notifyVpnDisconnected();
        mUnderlyingNetworkTypes = newTypes;
        notifyVpnConnected();
    }

    /**
     * Sets the IP protocol for the vpn network based on a list of {@link LinkAddress} objects.
     *
     * <p>
     * If the vpn network ip protocol has changed is different from current, a sequence of
     * notifications is triggered: first, a disconnection notification is sent with the old value,
     * then {@code mUnderlyingNetworkTypes} is updated with the new value, and finally, a connection
     * notification is issued with the new value.
     *
     * @param addresses A list of {@link LinkAddress} objects representing the IP addresses
     *                  configured on the VPN network.
     */
    public void setVpnNetworkIpProtocol(@NonNull List<LinkAddress> addresses) {
        mVpnNetworkIpProtocol = checkIpProtocol(addresses);
    public void updateVpnNetworkIpProtocol(@NonNull List<LinkAddress> addresses) {
        final int newVpnNetworkIpProtocol = checkIpProtocol(addresses);
        // Set the vpn network ip protocol directly if it's the default value, skipping the
        // connection status notification. Those notifications will be sent only when the VPN
        // connection is established and the vpn network ip protocol changes.
        if (mVpnNetworkIpProtocol == IP_PROTOCOL_UNKNOWN) {
            mVpnNetworkIpProtocol = newVpnNetworkIpProtocol;
            return;
        }
        // Return if no ip protocol change.
        if (mVpnNetworkIpProtocol == newVpnNetworkIpProtocol) {
            return;
        }
        // Notify the ip protocol change and set the new ip protocol.
        notifyVpnDisconnected();
        mVpnNetworkIpProtocol = newVpnNetworkIpProtocol;
        notifyVpnConnected();
    }

    /**
     * Sets the IP protocol for the server based on its {@link InetAddress}.
     *
     * <p>
     * If the server ip protocol has changed is different from current, a sequence of notifications
     * is triggered: first, a disconnection notification is sent with the old value, then
     * {@code mUnderlyingNetworkTypes} is updated with the new value, and finally, a connection
     * notification is issued with the new value.
     *
     * @param address The {@link InetAddress} of the server.
     */
    public void setServerIpProtocol(@NonNull InetAddress address) {
    public void updateServerIpProtocol(@NonNull InetAddress address) {
        final int newServerIpProtocol = getIpProtocolVersion(address);
        // Set the server ip protocol directly if it's the default value, skipping the connection
        // status notification. Those notifications will be sent only when the VPN connection is
        // established and the server ip protocol changes.
        if (mServerIpProtocol == IP_PROTOCOL_UNKNOWN) {
            mServerIpProtocol = newServerIpProtocol;
            return;
        }
        // Return if no ip protocol change.
        if (mServerIpProtocol == newServerIpProtocol) {
            return;
        }
        // Notify the ip protocol change and set the new ip protocol.
        notifyVpnDisconnected();
        mServerIpProtocol = newServerIpProtocol;
        notifyVpnConnected();
    }

    /**
     * Determines the IP protocol version of a given {@link InetAddress}.
     *
     * @param address The {@link InetAddress} for which to determine the IP protocol version.
     * @return An integer representing the IP protocol version:
     * {@link #IP_PROTOCOL_IPv4} for IPv4 addresses,
     * {@link #IP_PROTOCOL_IPv6} for IPv6 addresses,
     * or {@link #IP_PROTOCOL_UNKNOWN} for any other address types.
     */
    private static int getIpProtocolVersion(@NonNull InetAddress address) {
        // Assume that if the address is not IPv4, it is IPv6. It does not consider other cases like
        // IPv4-mapped IPv6 addresses.
        if (address instanceof Inet4Address) {
            mServerIpProtocol = IP_PROTOCOL_IPv4;
        } else {
            mServerIpProtocol = IP_PROTOCOL_IPv6;
        }
        return (address instanceof Inet4Address) ? IP_PROTOCOL_IPv4 :
                (address instanceof Inet6Address) ? IP_PROTOCOL_IPv6 : IP_PROTOCOL_UNKNOWN;
    }

    /**
@@ -250,6 +373,7 @@ public class VpnConnectivityMetrics {
        boolean hasIpv6 = false;
        int ipProtocol = IP_PROTOCOL_UNKNOWN;
        for (LinkAddress address : addresses) {
            if (address == null) continue;
            if (address.isIpv4()) {
                hasIpv4 = true;
            } else if (address.isIpv6()) {
@@ -265,4 +389,101 @@ public class VpnConnectivityMetrics {
        }
        return ipProtocol;
    }

    /**
     * Checks if the VPN associated with these metrics is a platform-managed VPN.
     * The determination is based on the internal {@code mVpnType} field, which
     * should be set during the VPN's configuration.
     *
     * @return {@code true} if the VPN type matches {@code TYPE_VPN_PLATFORM};
     *         {@code false} otherwise.
     */
    public boolean isPlatformVpn() {
        return mVpnType == TYPE_VPN_PLATFORM;
    }

    /**
     * Validates and corrects the internal VPN metrics to ensure the collected data fall within
     * acceptable ranges.
     * <p>
     * This method checks the values of {@code mVpnType}, {@code mVpnNetworkIpProtocol},
     * {@code mServerIpProtocol}, {@code mVpnProfileType}, and {@code mAllowedAlgorithms}.
     * If any value is found to be outside its expected bounds, an error is logged, and the metric
     * is reset to default state.
     * </p>
     */
    private void validateAndCorrectMetrics() {
        if (mVpnType < VPN_TYPE_UNKNOWN || mVpnType > TYPE_VPN_OEM) {
            Log.e(TAG, "Invalid vpnType: " + mVpnType);
            mVpnType = VPN_TYPE_UNKNOWN;
        }
        if (mVpnNetworkIpProtocol < IP_PROTOCOL_UNKNOWN
                || mVpnNetworkIpProtocol > IP_PROTOCOL_IPv4v6) {
            Log.e(TAG, "Invalid vpnNetworkIpProtocol: " + mVpnNetworkIpProtocol);
            mVpnNetworkIpProtocol = IP_PROTOCOL_UNKNOWN;
        }
        if (mServerIpProtocol < IP_PROTOCOL_UNKNOWN || mServerIpProtocol > IP_PROTOCOL_IPv6) {
            Log.e(TAG, "Invalid serverIpProtocol: " + mServerIpProtocol);
            mServerIpProtocol = IP_PROTOCOL_UNKNOWN;
        }
        if (mVpnProfileType < VPN_PROFILE_TYPE_UNKNOWN
                || mVpnProfileType > VPN_PROFILE_TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS) {
            Log.e(TAG, "Invalid vpnProfileType: " + mVpnProfileType);
            mVpnProfileType = VPN_PROFILE_TYPE_UNKNOWN;
        }
        if (mAllowedAlgorithms < 0 || mAllowedAlgorithms > MAX_ALLOWED_ALGORITHMS_VALUE) {
            Log.e(TAG, "Invalid allowedAlgorithms: " + mAllowedAlgorithms);
            mAllowedAlgorithms = 0;
        }
    }

    private void validateAndReportVpnConnectionEvent(boolean connected) {
        validateAndCorrectMetrics();
        mDependencies.statsWrite(
                mVpnType,
                mVpnNetworkIpProtocol,
                mServerIpProtocol,
                mVpnProfileType,
                mAllowedAlgorithms,
                mMtu,
                mUnderlyingNetworkTypes,
                connected,
                mUserId);
    }

    /**
     * Notifies that a VPN connected event has occurred.
     *
     * This method gathers the current VPN state information from internal fields and reports it to
     * the system's statistics logging service.
     */
    public void notifyVpnConnected() {
        validateAndReportVpnConnectionEvent(true /* connected */);
    }

    /**
     * Notifies that a VPN disconnected event has occurred.
     *
     * This method gathers the current VPN state information from internal fields and reports it to
     * the system's statistics logging service.
     */
    public void notifyVpnDisconnected() {
        validateAndReportVpnConnectionEvent(false /* connected */);
    }

    /**
     * Resets all internal VPN metrics to their default states.
     * <p>
     * This method should be called to ensure a clean state.
     * </p>
     */
    public void resetMetrics() {
        mVpnType = VPN_TYPE_UNKNOWN;
        mVpnNetworkIpProtocol = IP_PROTOCOL_UNKNOWN;
        mServerIpProtocol = IP_PROTOCOL_UNKNOWN;
        mVpnProfileType = VPN_PROFILE_TYPE_UNKNOWN;
        mAllowedAlgorithms = 0;
        mMtu = 0;
        mUnderlyingNetworkTypes = new int[0];
    }
}
+397 −2

File changed.

Preview size limit exceeded, changes collapsed.

+28 −12
Original line number Diff line number Diff line
@@ -253,7 +253,7 @@ public class VpnTest extends VpnTestBase {
    private static final InetAddress TEST_VPN_CLIENT_IP_2 =
            InetAddresses.parseNumericAddress("192.0.2.200");
    private static final InetAddress TEST_VPN_SERVER_IP_2 =
            InetAddresses.parseNumericAddress("192.0.2.201");
            InetAddresses.parseNumericAddress("2001:db8::2");
    private static final InetAddress TEST_VPN_INTERNAL_IP =
            InetAddresses.parseNumericAddress("198.51.100.10");
    private static final InetAddress TEST_VPN_INTERNAL_IP6 =
@@ -2063,13 +2063,6 @@ public class VpnTest extends VpnTestBase {
    private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
        setMockedUsers(PRIMARY_USER);
        vpn.startLegacyVpn(vpnProfile);
        if (vpnProfile.type == VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS
                || vpnProfile.type == VpnProfile.TYPE_IKEV2_IPSEC_PSK) {
            verify(mVpnConnectivityMetrics).setAllowedAlgorithms(
                    Ikev2VpnProfile.DEFAULT_ALGORITHMS);
        }
        verify(mVpnConnectivityMetrics).setVpnType(VpnManager.TYPE_VPN_PLATFORM);
        verify(mVpnConnectivityMetrics).setVpnProfileType(vpnProfile.type);
        return vpn;
    }

@@ -2220,13 +2213,25 @@ public class VpnTest extends VpnTestBase {
                any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
                any(), nacCaptor.capture(), any(), any());
        verify(mIkeSessionWrapper).setUnderpinnedNetwork(TEST_NETWORK);
        verify(mVpnConnectivityMetrics).setUnderlyingNetwork(any());
        verify(mVpnConnectivityMetrics).setVpnNetworkIpProtocol(argThat(addresses ->
        if (vpnProfile.type == VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS
                || vpnProfile.type == VpnProfile.TYPE_IKEV2_IPSEC_PSK) {
            verify(mVpnConnectivityMetrics).setAllowedAlgorithms(
                    Ikev2VpnProfile.DEFAULT_ALGORITHMS);
        }
        verify(mVpnConnectivityMetrics).setVpnType(VpnManager.TYPE_VPN_PLATFORM);
        verify(mVpnConnectivityMetrics).setVpnProfileType(vpnProfile.type);
        verify(mVpnConnectivityMetrics).updateUnderlyingNetworkTypes(
                argThat(networks -> Arrays.asList(networks).contains(TEST_NETWORK)));
        verify(mVpnConnectivityMetrics).updateVpnNetworkIpProtocol(argThat(addresses ->
                CollectionUtils.all(List.of(
                                new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN),
                                new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN)),
                        address -> addresses.contains(address))));
        verify(mVpnConnectivityMetrics).setServerIpProtocol(TEST_VPN_SERVER_IP);
        verify(mVpnConnectivityMetrics).updateServerIpProtocol(TEST_VPN_SERVER_IP);
        verify(mVpnConnectivityMetrics).setMtu(
                !mtuSupportsIpv6 ? IPV6_MIN_MTU - 1 : IPV6_MIN_MTU);
        // Verify connection metrics notification.
        verify(mVpnConnectivityMetrics).notifyVpnConnected();
        // Check LinkProperties
        final LinkProperties lp = lpCaptor.getValue();
        final List<RouteInfo> expectedRoutes =
@@ -2802,8 +2807,12 @@ public class VpnTest extends VpnTestBase {

        // Mock the MOBIKE procedure
        vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
        verify(mVpnConnectivityMetrics).updateServerIpProtocol(TEST_VPN_SERVER_IP_2);
        vpnSnapShot.childCb.onIpSecTransformsMigrated(
                createIpSecTransform(), createIpSecTransform());
        verify(mVpnConnectivityMetrics).updateUnderlyingNetworkTypes(
                argThat(networks -> Arrays.asList(networks).contains(TEST_NETWORK_2)));
        verify(mVpnConnectivityMetrics).setMtu(newMtu);

        verify(mIpSecService).setNetworkForTunnelInterface(
                eq(TEST_TUNNEL_RESOURCE_ID), eq(TEST_NETWORK_2), anyString());
@@ -2841,8 +2850,12 @@ public class VpnTest extends VpnTestBase {
                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());

        vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
        verify(mVpnConnectivityMetrics).updateServerIpProtocol(TEST_VPN_SERVER_IP_2);
        vpnSnapShot.childCb.onIpSecTransformsMigrated(
                createIpSecTransform(), createIpSecTransform());
        verify(mVpnConnectivityMetrics).updateUnderlyingNetworkTypes(
                argThat(networks -> Arrays.asList(networks).contains(TEST_NETWORK_2)));
        verify(mVpnConnectivityMetrics).setMtu(newMtu);

        // Verify removal of IPv6 addresses and routes triggers a network agent restart
        final ArgumentCaptor<LinkProperties> lpCaptor =
@@ -2877,7 +2890,6 @@ public class VpnTest extends VpnTestBase {
        }

        assertEquals(newMtu, lp.getMtu());

        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
    }

@@ -3027,6 +3039,9 @@ public class VpnTest extends VpnTestBase {
        assertEquals(expectedRoutes, lp.getRoutes());

        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)).unregister();
        // Verify disconnection metrics notification.
        verify(mVpnConnectivityMetrics).notifyVpnDisconnected();
        verify(mVpnConnectivityMetrics).resetMetrics();
    }

    @Test
@@ -3321,6 +3336,7 @@ public class VpnTest extends VpnTestBase {
        verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
                provider -> provider.getName().contains("VpnNetworkProvider")
        ));
        doReturn(true).when(mVpnConnectivityMetrics).isPlatformVpn();
        return vpn;
    }