Search code examples
androidandroid-vpn-service

VpnService always-on "not supported by this app"


I'm trying to write a VPN application for android, and no matter what I do it seems that the "Always On" setting for my application is grayed out.

I cannot find any documentation anywhere in the android docs that states WHY a VpnService might not be allowed to use the "Always On" feature.

Just to make sure I'm clear, my VpnService works just fine if i manually enable it. I'm able to connect, use it, etc without any issues. I'm just confused as to why in the Android settings it's showing as "not supported for this device" under the "Always-On" toggle for my Vpn Service.

Note: I am using a custom protocol, and on some forum somewhere I did see once that "Google doesn't allow Always-On for VPNs that don't use protocols that have been whitelisted by google", however no where have I found documentation to support this claim, so I'm doubtful that it is the cause.

What I have found from reading the Android Documentation:

  1. If you explicitly supply the SERVICE_META_DATA_SUPPORTS_ALWAYS_ON flag to the <service> definition in your Android Manafest with a value of false it will tell the system that your application does not support the Always On feature. However, if you leave it out, it defaults to true. So there should be no need to supply this.

  2. According to the Android Souce in AppManagementFragment.java#updateRestrictedViews, it uses a function mConnectivityManager.isAlwaysOnVpnPackageSupportedForUser(mUserId, mPackageName), however I'm having difficulty following the logic of this function. When looking at ConnectivityManager.java#isAlwaysOnVpnPackageSupportedForUser, it seems to end up in ConnectivityService.java#isAlwaysOnVpnPackageSupported and ultimately points to Vpn.java#isAlwaysOnPackageSupported which can result in your VPN being considered to NOT support always on if one of the following is met:

    1. The package name passed to the function is null.
    2. The system is unable to locate the package using PackageManager. getApplicationInfoAsUser().
    3. The target for the application is less than VERSION_CODES.N
    4. The package does not contain any Vpn Services in its AndroidManifest.xml.
    5. The package defines ANY VpnServices with the SERVICE_META_DATA_SUPPORTS_ALWAYS_ON meta data set to false.

Note: My application targets API 29.

And that's about it.

Since my application meets all of the requirements stated in Vpn.java#isAlwaysOnPackageSupported, i'm left very confused as to why it will not work.

I've created an android studio project that demonstrates this happening on my Samsung Galaxy S10. You can find it here on github.

My code for reference:

My service is configured as is documented in the Android Documentation.

<service
    android:name=".provider.VpnProvider"
    android:enabled="true"
    android:permission="android.permission.BIND_VPN_SERVICE"
    android:description="@string/service_description"
    android:exported="false">
    <intent-filter>
        <action android:name="android.net.VpnSerivce" />
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</service>

In my Dashboard activity, I use this to handle the Connect button being tapped.

public static final int REQ_START_VPN = 40;

public boolean connect() {
    Intent intent;

    try {
        intent = VpnProvider.prepare(this.getApplicationContext());
    } catch (IllegalStateException e) {
        return false;
    }

    if (intent != null) {
        try {
            this.startActivityForResult(intent, REQ_START_VPN);
            return true;
        } catch (ActivityNotFoundException e) {
            new BasicDialog(
                this, R.string.not_supported,
                R.string.not_supported_message
            ).show();
        }
    } else {
        this.onActivityResult(REQ_START_VPN, AppCompatActivity.RESULT_OK, null);
        return true;
    }
}

I then handle the result using the following:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == AppCompatActivity.RESULT_OK) {
        if (requestCode == REQ_START_VPN) {
            try {
                this.startService(new Intent(this, VpnProvider.class));
            } catch (IllegalStateException ignored) {}
        }
    }
}

My VpnProvider class extends VpnService and has the following for Building the interface:

VpnService.Builder builder = this.new Builder();
builder.setMtu(mtu);
builder.addAddress("1.2.3.4", 32);
builder.addDnsServer("8.8.8.8");
builder.addDnsServer("8.8.4.4");
builder.addRoute("0.0.0.0", 0);
vpnInterface = builder.establish();

Solution

  • I've found the problem.

    In my AndroidManifest.xml when I declared the intent-filters for my service I had a typo.

    <action android:name="android.net.VpnSerivce" />
    

    instead of

    <action android:name="android.net.VpnService" />
    

    This made my application not inform the operating system that my application had a VpnService as documented in the android documentation here.

    This typo took three days of my life that I will never get back.