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?