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

Commit 73dccb6f authored by Paul Hu's avatar Paul Hu
Browse files

Send connection metrics when status changes

VPN metrics should send disconnected and connected events when
the underlying network or IP protocol changes.

Bug: 306313287
Test: atest FrameworksVpnTests
Test: Verify some scenarios manually:
  - Switch underlying network between Wi-Fi and cellular.
  - Switch between platform VPN and 3rd-party VPN.
  - Disable MOBIKE feature.
Flag: android.net.platform.flags.collect_vpn_metrics
Change-Id: I228fa4f6c6b6c6f217a58a1e6acf3bc295cb0d3f
parent cf1ce84f
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -2314,7 +2314,7 @@ public class Vpn {
        synchronized (Vpn.this) {
            mConfig.underlyingNetworks = networks;
            if (mVpnConnectivityMetrics != null) {
                mVpnConnectivityMetrics.setUnderlyingNetwork(mConfig.underlyingNetworks);
                mVpnConnectivityMetrics.updateUnderlyingNetworkTypes(mConfig.underlyingNetworks);
            }
        }
    }
@@ -3005,7 +3005,8 @@ public class Vpn {
            // in onChildMigrated
            mIkeConnectionInfo = ikeConnectionInfo;
            if (mVpnConnectivityMetrics != null) {
                mVpnConnectivityMetrics.setServerIpProtocol(ikeConnectionInfo.getRemoteAddress());
                mVpnConnectivityMetrics.updateServerIpProtocol(
                        ikeConnectionInfo.getRemoteAddress());
            }
        }

@@ -3084,7 +3085,7 @@ public class Vpn {
                    mConfig.addresses.clear();
                    mConfig.addresses.addAll(internalAddresses);
                    if (mVpnConnectivityMetrics != null) {
                        mVpnConnectivityMetrics.setVpnNetworkIpProtocol(mConfig.addresses);
                        mVpnConnectivityMetrics.updateVpnNetworkIpProtocol(mConfig.addresses);
                    }

                    mConfig.routes.clear();
+104 −25
Original line number Diff line number Diff line
@@ -232,53 +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 if (address instanceof Inet6Address) {
            mServerIpProtocol = IP_PROTOCOL_IPv6;
        } else {
            mServerIpProtocol = IP_PROTOCOL_UNKNOWN;
        }
        return (address instanceof Inet4Address) ? IP_PROTOCOL_IPv4 :
                (address instanceof Inet6Address) ? IP_PROTOCOL_IPv6 : IP_PROTOCOL_UNKNOWN;
    }

    /**
+219 −16
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ import static com.android.server.connectivity.VpnConnectivityMetrics.checkIpProt
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.net.ConnectivityManager;
@@ -123,8 +125,7 @@ public class VpnConnectivityMetricsTest {
        assertEquals(IP_PROTOCOL_IPv4v6, checkIpProtocol(List.of(vpnClientIpv4, vpnClientIpv6)));
    }

    @Test
    public void testNotifyVpnConnected() {
    private Network verifyNotifyVpnConnected() {
        final Network cellNetwork = new Network(1234);
        final NetworkCapabilities cellCap = new NetworkCapabilities();
        cellCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
@@ -135,14 +136,14 @@ public class VpnConnectivityMetricsTest {
        mMetrics.setMtu(1327);
        mMetrics.setVpnProfileType(VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS);
        mMetrics.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
        mMetrics.setUnderlyingNetwork(new Network[] { cellNetwork });
        mMetrics.setVpnNetworkIpProtocol(
        mMetrics.updateUnderlyingNetworkTypes(new Network[] { cellNetwork });
        mMetrics.updateVpnNetworkIpProtocol(
                List.of(new LinkAddress(VPN_CLIENT_IP_V4), new LinkAddress(VPN_CLIENT_IP_V6)));
        mMetrics.setServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V4));
        mMetrics.updateServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V4));

        // Verify a vpn connected event with the filled in data.
        mMetrics.notifyVpnConnected();
        verify(mDeps).statsWrite(
        verify(mDeps, times(1)).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
@@ -152,6 +153,13 @@ public class VpnConnectivityMetricsTest {
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                true /* connected */,
                USER_ID);

        return cellNetwork;
    }

    @Test
    public void testNotifyVpnConnected() {
        verifyNotifyVpnConnected();
    }

    @Test
@@ -166,9 +174,9 @@ public class VpnConnectivityMetricsTest {
        mMetrics.setMtu(1280);
        mMetrics.setVpnProfileType(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS);
        mMetrics.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
        mMetrics.setUnderlyingNetwork(new Network[] { wifiNetwork });
        mMetrics.setVpnNetworkIpProtocol(List.of(new LinkAddress(VPN_CLIENT_IP_V6)));
        mMetrics.setServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V6));
        mMetrics.updateUnderlyingNetworkTypes(new Network[] { wifiNetwork });
        mMetrics.updateVpnNetworkIpProtocol(List.of(new LinkAddress(VPN_CLIENT_IP_V6)));
        mMetrics.updateServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V6));

        // Verify a vpn disconnected event with the filled in data.
        mMetrics.notifyVpnDisconnected();
@@ -194,8 +202,8 @@ public class VpnConnectivityMetricsTest {
            mMetrics.setMtu(1280);
            mMetrics.setVpnProfileType(-1);
            mMetrics.setAllowedAlgorithms(List.of("unknown"));
            mMetrics.setVpnNetworkIpProtocol(List.of());
            mMetrics.setServerIpProtocol(null);
            mMetrics.updateVpnNetworkIpProtocol(List.of());
            mMetrics.updateServerIpProtocol(null);

            // Verify a vpn connected event with the filled in correct data.
            mMetrics.notifyVpnConnected();
@@ -218,8 +226,8 @@ public class VpnConnectivityMetricsTest {
            mMetrics.setAllowedAlgorithms(allowedAlgorithms);
            final List<LinkAddress> addresses = new ArrayList<>();
            addresses.add(null);
            mMetrics.setVpnNetworkIpProtocol(addresses);
            mMetrics.setServerIpProtocol(null);
            mMetrics.updateVpnNetworkIpProtocol(addresses);
            mMetrics.updateServerIpProtocol(null);

            // Verify a vpn disconnected event with the filled in correct data.
            mMetrics.notifyVpnDisconnected();
@@ -248,10 +256,10 @@ public class VpnConnectivityMetricsTest {
        mMetrics.setMtu(1327);
        mMetrics.setVpnProfileType(VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS);
        mMetrics.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
        mMetrics.setUnderlyingNetwork(new Network[] { cellNetwork });
        mMetrics.setVpnNetworkIpProtocol(
        mMetrics.updateUnderlyingNetworkTypes(new Network[] { cellNetwork });
        mMetrics.updateVpnNetworkIpProtocol(
                List.of(new LinkAddress(VPN_CLIENT_IP_V4), new LinkAddress(VPN_CLIENT_IP_V6)));
        mMetrics.setServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V4));
        mMetrics.updateServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V4));

        // Verify a vpn connected event with the filled in data.
        mMetrics.notifyVpnConnected();
@@ -280,4 +288,199 @@ public class VpnConnectivityMetricsTest {
                true /* connected */,
                USER_ID);
    }

    @Test
    public void testUpdateServerIpProtocol() {
        verifyNotifyVpnConnected();

        // Update server IP to IPv6, which should trigger both disconnected and connected
        // notifications.
        mMetrics.updateServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V6));

        // Verify a vpn disconnected event.
        verify(mDeps).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                false /* connected */,
                USER_ID);

        // Verify a vpn connected event with the new server IP protocol.
        verify(mDeps).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv6,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                true /* connected */,
                USER_ID);
    }

    @Test
    public void testUpdateServerIpProtocol_NoIpProtocolChange() {
        verifyNotifyVpnConnected();

        // Update with the same server IP, which should not trigger any notifications.
        mMetrics.updateServerIpProtocol(InetAddresses.parseNumericAddress(VPN_SERVER_IP_V4));

        verify(mDeps, never()).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                false /* connected */,
                USER_ID);

        verify(mDeps, times(1)).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                true /* connected */,
                USER_ID);
    }

    @Test
    public void testUpdateVpnNetworkIpProtocol() {
        verifyNotifyVpnConnected();

        // Update vpn network IP to IPv4 only, which should trigger both disconnected and connected
        // notifications.
        mMetrics.updateVpnNetworkIpProtocol(List.of(new LinkAddress(VPN_CLIENT_IP_V4)));

        // Verify a vpn disconnected event.
        verify(mDeps).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                false /* connected */,
                USER_ID);

        // Verify a vpn connected event with the new server IP protocol.
        verify(mDeps).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                true /* connected */,
                USER_ID);
    }

    @Test
    public void testUpdateVpnNetworkIpProtocol_NoIpProtocolChange() {
        verifyNotifyVpnConnected();

        // Update with the same vpn network IP, which should not trigger any notifications.
        mMetrics.updateVpnNetworkIpProtocol(
                List.of(new LinkAddress(VPN_CLIENT_IP_V4), new LinkAddress(VPN_CLIENT_IP_V6)));

        verify(mDeps, never()).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                false /* connected */,
                USER_ID);

        verify(mDeps, times(1)).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                true /* connected */,
                USER_ID);
    }

    @Test
    public void testUpdateUnderlyingNetworkTypes() {
        verifyNotifyVpnConnected();

        final Network wifiNetwork = new Network(4321);
        final NetworkCapabilities wifiCap = new NetworkCapabilities();
        wifiCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
        doReturn(wifiCap).when(mCm).getNetworkCapabilities(wifiNetwork);

        // Update underlying network to Wi-Fi, which should trigger both disconnected and connected
        // notifications.
        mMetrics.updateUnderlyingNetworkTypes(new Network[] { wifiNetwork });

        // Verify a vpn disconnected event.
        verify(mDeps).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                false /* connected */,
                USER_ID);

        // Verify a vpn connected event with the new underlying network.
        verify(mDeps).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_WIFI },
                true /* connected */,
                USER_ID);
    }

    @Test
    public void testUpdateUnderlyingNetworkTypes_NoNetworkTypeChange() {
        final Network cellnetwork = verifyNotifyVpnConnected();

        // Update with thhe same underlying network, which should not trigger any notifications.
        mMetrics.updateUnderlyingNetworkTypes(new Network[] { cellnetwork });

        verify(mDeps, never()).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                false /* connected */,
                USER_ID);

        verify(mDeps, times(1)).statsWrite(
                VpnManager.TYPE_VPN_PLATFORM,
                IP_PROTOCOL_IPv4v6,
                IP_PROTOCOL_IPv4,
                VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS + 1,
                1999 /* allowedAlgorithms */,
                1327 /* mtu */,
                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR },
                true /* connected */,
                USER_ID);
    }
}
+15 −5
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 =
@@ -2220,13 +2220,16 @@ public class VpnTest extends VpnTestBase {
        }
        verify(mVpnConnectivityMetrics).setVpnType(VpnManager.TYPE_VPN_PLATFORM);
        verify(mVpnConnectivityMetrics).setVpnProfileType(vpnProfile.type);
        verify(mVpnConnectivityMetrics).setUnderlyingNetwork(any());
        verify(mVpnConnectivityMetrics).setVpnNetworkIpProtocol(argThat(addresses ->
        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
@@ -2804,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());
@@ -2843,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 =
@@ -2879,7 +2890,6 @@ public class VpnTest extends VpnTestBase {
        }

        assertEquals(newMtu, lp.getMtu());

        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
    }