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
- Is this the right way to create and maintain a VPN service?
- 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.
- 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?
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.