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

Commit 642ef43d authored by Paul Hu's avatar Paul Hu
Browse files

Validate and correct the VPN metrics

If there are invalid values in VPN metrics data, and this data is
uploaded to StatSD, then this garbage data will impact the
overall metrics in the system. Therefore, it's crucial to validate
and correct the VPN metrics before uploading.

Bug: 306313287
Test: atest FrameworksVpnTests
Flag: android.net.platform.flags.collect_vpn_metrics
Change-Id: I609d46dac77f7f5a3b760c69a3548b95ba8073ed
parent f087ec64
Loading
Loading
Loading
Loading
+68 −23
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
import static android.net.IpSecAlgorithm.CRYPT_AES_CTR;
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 static android.net.VpnManager.TYPE_VPN_PLATFORM;


import android.net.ConnectivityManager;
import android.net.ConnectivityManager;
@@ -43,6 +44,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FrameworkStatsLog;


import java.net.Inet4Address;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Arrays;
import java.util.List;
import java.util.List;
@@ -53,11 +55,14 @@ import java.util.List;
 */
 */
public class VpnConnectivityMetrics {
public class VpnConnectivityMetrics {
    private static final String TAG = VpnConnectivityMetrics.class.getSimpleName();
    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
    // Copied from corenetworking platform vpn enum
    @VisibleForTesting
    @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;
    static final int IP_PROTOCOL_UNKNOWN = 0;
    @VisibleForTesting
    @VisibleForTesting
    static final int IP_PROTOCOL_IPv4 = 1;
    static final int IP_PROTOCOL_IPv4 = 1;
@@ -81,6 +86,14 @@ public class VpnConnectivityMetrics {
     * index in {@code sAlgorithms} is considered allowed.
     * index in {@code sAlgorithms} is considered allowed.
     */
     */
    private int mAllowedAlgorithms = 0;
    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.
     * An array representing the transport types of the underlying networks for the VPN.
     * Each element in this array corresponds to a specific underlying network.
     * Each element in this array corresponds to a specific underlying network.
@@ -91,7 +104,7 @@ public class VpnConnectivityMetrics {
     * {@code UNKNOWN_UNDERLYING_NETWORK_TYPE}.
     * {@code UNKNOWN_UNDERLYING_NETWORK_TYPE}.
     */
     */
    @NonNull
    @NonNull
    private int[] mUnderlyingNetworkTypes;
    private int[] mUnderlyingNetworkTypes = new int[0];
    private int mVpnNetworkIpProtocol = IP_PROTOCOL_UNKNOWN;
    private int mVpnNetworkIpProtocol = IP_PROTOCOL_UNKNOWN;
    private int mServerIpProtocol = IP_PROTOCOL_UNKNOWN;
    private int mServerIpProtocol = IP_PROTOCOL_UNKNOWN;


@@ -261,8 +274,10 @@ public class VpnConnectivityMetrics {
        // IPv4-mapped IPv6 addresses.
        // IPv4-mapped IPv6 addresses.
        if (address instanceof Inet4Address) {
        if (address instanceof Inet4Address) {
            mServerIpProtocol = IP_PROTOCOL_IPv4;
            mServerIpProtocol = IP_PROTOCOL_IPv4;
        } else {
        } else if (address instanceof Inet6Address) {
            mServerIpProtocol = IP_PROTOCOL_IPv6;
            mServerIpProtocol = IP_PROTOCOL_IPv6;
        } else {
            mServerIpProtocol = IP_PROTOCOL_UNKNOWN;
        }
        }
    }
    }


@@ -279,6 +294,7 @@ public class VpnConnectivityMetrics {
        boolean hasIpv6 = false;
        boolean hasIpv6 = false;
        int ipProtocol = IP_PROTOCOL_UNKNOWN;
        int ipProtocol = IP_PROTOCOL_UNKNOWN;
        for (LinkAddress address : addresses) {
        for (LinkAddress address : addresses) {
            if (address == null) continue;
            if (address.isIpv4()) {
            if (address.isIpv4()) {
                hasIpv4 = true;
                hasIpv4 = true;
            } else if (address.isIpv6()) {
            } else if (address.isIpv6()) {
@@ -307,15 +323,43 @@ public class VpnConnectivityMetrics {
        return mVpnType == TYPE_VPN_PLATFORM;
        return mVpnType == TYPE_VPN_PLATFORM;
    }
    }




    /**
    /**
     * Notifies that a VPN connected event has occurred.
     * Validates and corrects the internal VPN metrics to ensure the collected data fall within
     *
     * acceptable ranges.
     * This method gathers the current VPN state information from internal fields and reports it to
     * <p>
     * the system's statistics logging service.
     * 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>
     */
     */
    public void notifyVpnConnected() {
    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(
        mDependencies.statsWrite(
                mVpnType,
                mVpnType,
                mVpnNetworkIpProtocol,
                mVpnNetworkIpProtocol,
@@ -324,10 +368,20 @@ public class VpnConnectivityMetrics {
                mAllowedAlgorithms,
                mAllowedAlgorithms,
                mMtu,
                mMtu,
                mUnderlyingNetworkTypes,
                mUnderlyingNetworkTypes,
                true /* connected */,
                connected,
                mUserId);
                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.
     * Notifies that a VPN disconnected event has occurred.
     *
     *
@@ -335,15 +389,6 @@ public class VpnConnectivityMetrics {
     * the system's statistics logging service.
     * the system's statistics logging service.
     */
     */
    public void notifyVpnDisconnected() {
    public void notifyVpnDisconnected() {
        mDependencies.statsWrite(
        validateAndReportVpnConnectionEvent(false /* connected */);
                mVpnType,
                mVpnNetworkIpProtocol,
                mServerIpProtocol,
                mVpnProfileType,
                mAllowedAlgorithms,
                mMtu,
                mUnderlyingNetworkTypes,
                false /* connected */,
                mUserId);
    }
    }
}
}
+55 −0
Original line number Original line Diff line number Diff line
@@ -32,6 +32,8 @@ import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL
import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL_IPv4v6;
import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL_IPv4v6;
import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL_IPv6;
import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL_IPv6;
import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL_UNKNOWN;
import static com.android.server.connectivity.VpnConnectivityMetrics.IP_PROTOCOL_UNKNOWN;
import static com.android.server.connectivity.VpnConnectivityMetrics.VPN_PROFILE_TYPE_UNKNOWN;
import static com.android.server.connectivity.VpnConnectivityMetrics.VPN_TYPE_UNKNOWN;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.server.connectivity.VpnConnectivityMetrics.buildAllowedAlgorithmsBitmask;
import static com.android.server.connectivity.VpnConnectivityMetrics.buildAllowedAlgorithmsBitmask;
import static com.android.server.connectivity.VpnConnectivityMetrics.checkIpProtocol;
import static com.android.server.connectivity.VpnConnectivityMetrics.checkIpProtocol;
@@ -61,6 +63,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations;


import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicBoolean;


@@ -180,4 +183,56 @@ public class VpnConnectivityMetricsTest {
                false /* connected */,
                false /* connected */,
                USER_ID);
                USER_ID);
    }
    }

    @Test
    public void testInvalidMetrics() {
        final Log.TerribleFailureHandler originalHandler =
                Log.setWtfHandler((tag, what, system) -> {});
        testAndCleanup(() -> {
            // Fill in invalid metrics data
            mMetrics.setVpnType(VpnManager.TYPE_VPN_NONE);
            mMetrics.setMtu(1280);
            mMetrics.setVpnProfileType(-1);
            mMetrics.setAllowedAlgorithms(List.of("unknown"));
            mMetrics.setVpnNetworkIpProtocol(List.of());
            mMetrics.setServerIpProtocol(null);

            // Verify a vpn connected event with the filled in correct data.
            mMetrics.notifyVpnConnected();
            verify(mDeps).statsWrite(
                    VPN_TYPE_UNKNOWN,
                    IP_PROTOCOL_UNKNOWN,
                    IP_PROTOCOL_UNKNOWN,
                    VPN_PROFILE_TYPE_UNKNOWN,
                    0 /* allowedAlgorithms */,
                    1280 /* mtu */,
                    new int[0],
                    true /* connected */,
                    USER_ID);

            // Fill in invalid metrics data again
            mMetrics.setVpnType(VpnManager.TYPE_VPN_OEM_SERVICE);
            mMetrics.setVpnProfileType(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS + 1);
            final List<String> allowedAlgorithms = new ArrayList<>();
            allowedAlgorithms.add(null);
            mMetrics.setAllowedAlgorithms(allowedAlgorithms);
            final List<LinkAddress> addresses = new ArrayList<>();
            addresses.add(null);
            mMetrics.setVpnNetworkIpProtocol(addresses);
            mMetrics.setServerIpProtocol(null);

            // Verify a vpn disconnected event with the filled in correct data.
            mMetrics.notifyVpnDisconnected();
            verify(mDeps).statsWrite(
                    VPN_TYPE_UNKNOWN,
                    IP_PROTOCOL_UNKNOWN,
                    IP_PROTOCOL_UNKNOWN,
                    VPN_PROFILE_TYPE_UNKNOWN,
                    0 /* allowedAlgorithms */,
                    1280 /* mtu */,
                    new int[0],
                    false /* connected */,
                    USER_ID);
        }, () -> Log.setWtfHandler(originalHandler));
    }
}
}