How do some apps block others from reaching WiFi/mobile-networks?

550 Views Asked by At

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?
2

There are 2 best solutions below

1
Deepjyoty nath On

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.

2
Zahirul Islam On
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
                        }
                    }
                }
            }
        )
    }
}
}