6

Background

I've noticed various firewall apps on the Play Store being able to let the user choose which apps are allowed to use Wi-Fi and/or mobile-network for Internet connection. These app use VPN to have the control they need.

An example for this is Xproguard Firewall:

https://play.google.com/store/apps/details?id=com.xproguard.firewall

The problem

I want to try to make something similar. For this, I sadly can't find much material on the web, and actually found various unanswered questions even here on StackOverflow.

What I've found and tried

I've found a sample called "ToyVpn" (here), but it seems much more complex than what I wanted, and I'm not even sure it can do what I'm trying to do.

So I tried something from scratch. I've succeeded to have a working VPN (or at least so it seems). I took Google Chrome as the app to block, because it's easy to test it and because I can see that the firewall app also succeeded to block it. This is what I got:

manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission
        android:name="android.permission.QUERY_ALL_PACKAGES"
        tools:ignore="QueryAllPackagesPermission" />

    <application
        android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true" android:theme="@style/Theme.Firewall" tools:targetApi="31">
        <activity
            android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyVpnService" android:exported="true"
            android:permission="android.permission.BIND_VPN_SERVICE">
            <intent-filter>
                <action android:name="android.net.VpnService" />
            </intent-filter>
        </service>
    </application>

</manifest>

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private val requestVpn =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == RESULT_OK)
                startService(Intent(this, MyVpnService::class.java))
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val vpnIntent = VpnService.prepare(this@MainActivity)
        if (vpnIntent != null)
            requestVpn.launch(vpnIntent)
        else
            startService(Intent(this, MyVpnService::class.java))
    }
}

MyVpnService.kt

class MyVpnService : VpnService() {
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        val builder = Builder()
        builder.setSession("MyVPNService")
            .addAddress("192.168.0.1", 24)
            .addDnsServer("8.8.8.8")
            .addRoute("0.0.0.0", 0)
            .addDisallowedApplication("com.android.chrome")
            .establish()
        return START_STICKY
    }
}

Another alternative I've found requires root, and can be done even in a batch file:

set package_name="com.android.browser"
for /f "delims=" %%A in ('adb shell dumpsys package %package_name% ^| findstr "userId="') do (
  for /f "tokens=2 delims== " %%B in ("%%A") do (
    echo Extracted UID: %%B
    adb shell "su -c 'iptables -A OUTPUT -m owner --uid-owner %%B -j DROP'"
  )
)

For un-blocking, replace the "-A OUTPUT" with "-D OUTPUT".

To check the status, you can use this:

set package_name="com.android.browser"
for /f "delims=" %%A in ('adb shell dumpsys package %package_name% ^| findstr "userId="') do (
  for /f "tokens=2 delims== " %%B in ("%%A") do (
    set uid=%%B
    echo Extracted UID: %%B
    adb shell "su -c 'iptables -L OUTPUT -v -n --line-numbers | grep \"owner UID match %%B\"'"
  )
)

pause

This works well, but I want to know how it's done in apps that don't require root.

The questions

  1. Is this the right way to create and maintain a VPN service?
  2. The current code doesn't really block apps. What would it take to block other apps? It seems I need to do something with the builder's generated instance, but I couldn't find what.
  3. How would I be able to set the type of communication to block: Wi-Fi and/or mobile-data, just like on other firewall apps?
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Just making sure you are aware of/checked code of existing open-source apps: https://search.f-droid.org/?q=firewall&lang=en – Morrison Chang May 07 '23 at 03:27
  • @MorrisonChang Could be useful. Still, I prefer to learn from minimal code and not a full app, as it's harder to understand what's going on this way. I already tried one and it was very hard. Do you know what's wrong in the code I've tried? – android developer May 07 '23 at 09:01
  • I'm not sure if Chrome is a good test case as it may protect itself from VPN. https://github.com/M66B/NetGuard/blob/master/FAQ.md#user-content-faq19 From what I can tell the 'app blocker' firewall apps are using uid packet inspection to allow/block traffic, [Acccess to /proc/net/tcp in Android Q](https://stackoverflow.com/q/58497492/295004) with some level of traffic logging?. As docs were lacking, look at source: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/net/VpnService.java – Morrison Chang May 07 '23 at 13:05
  • `Denied applications will use networking as if the VPN wasn't running.` from https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/net/VpnService.java;drc=fe5a57e8c47855f73589a8de04e74d9f82718db9;l=823 Seems like https://github.com/StarGW-net/karma-firewall might the simplest one to review. – Morrison Chang May 07 '23 at 13:19
  • @MorrisonChang I already wrote about Chrome : "because I can see that the firewall app also succeeded to block it", meaning it's a good candidate. – android developer May 07 '23 at 15:12
  • I stand corrected, I misread your post. – Morrison Chang May 07 '23 at 16:17
  • [@androiddeveloper](https://stackoverflow.com/users/878126/android-developer) check this file https://github.com/M66B/NetGuard/blob/0.8/app/src/main/java/eu/faircode/netguard/SinkholeService.java it creates a VPN and foreward selected traffic to a 'black hole" – Yogev Neumann Jun 04 '23 at 21:08
  • I believe there is two way to achieve it. 1-using linux firewall(iptables) which requires root 2- dump every network request from ports that are used by blocked application(requires your application's vpn to control traffic and act as a firewall) – Ebrahim Karimi Jun 06 '23 at 22:04
  • @EbrahimKarimi That's what I wrote in the question. I already presented a start for both ways... I ask about VPN solution as the root option seems to work (at least on the basic level) – android developer Jun 06 '23 at 23:26

2 Answers2

-1

You can review the source code for the the given app, https://github.com/M66B/NetGuard. This app does not require a rooted device, also what you are trying to achieve is a complicated topic, you will need to learn proper networking concepts along with how android communicates with the network in order to block them. The source code may give you some insights on how to achieve the functionality.

-1
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

private lateinit var connectivityManager: ConnectivityManager

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    // Block other apps from network access
    blockNetworkAccess()
}

private fun blockNetworkAccess() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Get the currently active network
        val activeNetwork = connectivityManager.activeNetwork

        // Create a NetworkRequest and set the necessary capabilities
        val networkRequest = NetworkCapabilities.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()

        // Register a network callback to monitor the active network
        connectivityManager.registerNetworkCallback(
            networkRequest,
            object : ConnectivityManager.NetworkCallback() {
                override fun onCapabilitiesChanged(
                    network: Network,
                    networkCapabilities: NetworkCapabilities
                ) {
                    // Block network access for the active network
                    if (network == activeNetwork) {
                        if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
                            // App blocked from network access
                            // Perform necessary actions
                        }
                    }
                }
            }
        )
    }
}
}
Zahirul Islam
  • 155
  • 2
  • 12
  • I didn't ask how to check network access. I asked how to block it, using the VpnService class. You didn't even mention VpnService in your code... – android developer May 26 '23 at 10:31
  • 1
    [@Zahirul Islam](https://stackoverflow.com/users/3030358/zahirul-islam) While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – Yogev Neumann Jun 04 '23 at 20:54