Skip to content

Commit 69ed62e

Browse files
committed
Handle refused VPN service setup
1 parent ab7695f commit 69ed62e

File tree

1 file changed

+74
-59
lines changed

1 file changed

+74
-59
lines changed

app/src/main/java/tech/httptoolkit/android/ProxyVpnService.kt

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,17 @@ class ProxyVpnService : VpnService(), IProtectSocket {
7676

7777
if (intent.action == START_VPN_ACTION) {
7878
val proxyConfig = intent.getParcelableExtra<ProxyConfig>(PROXY_CONFIG_EXTRA)
79-
startVpn(proxyConfig)
8079

81-
// If the system briefly kills us for some reason (memory, the user, whatever) whilst
82-
// running the VPN, it should redeliver the VPN setup intent ASAP.
83-
return Service.START_REDELIVER_INTENT
80+
val vpnStarted = startVpn(proxyConfig)
81+
82+
if (vpnStarted) {
83+
// If the system briefly kills us for some reason (memory, the user, whatever) whilst
84+
// running the VPN, it should redeliver the VPN setup intent ASAP.
85+
return Service.START_REDELIVER_INTENT
86+
} else {
87+
// We failed to start somehow - cleanup
88+
stopVpn()
89+
}
8490
} else if (intent.action == STOP_VPN_ACTION) {
8591
stopVpn()
8692
}
@@ -132,7 +138,7 @@ class ProxyVpnService : VpnService(), IProtectSocket {
132138

133139
}
134140

135-
private fun startVpn(proxyConfig: ProxyConfig) {
141+
private fun startVpn(proxyConfig: ProxyConfig): Boolean {
136142
this.proxyConfig = proxyConfig
137143
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
138144

@@ -143,67 +149,76 @@ class ProxyVpnService : VpnService(), IProtectSocket {
143149
name -> name.startsWith("com.genymotion")
144150
}
145151

146-
if (vpnInterface == null) {
147-
app!!.pauseEvents() // Try not to send events while the VPN is active, it's unnecessary noise
148-
app!!.trackEvent("VPN", "vpn-started")
149-
vpnInterface = Builder()
150-
.addAddress(VPN_IP_ADDRESS, 32)
151-
.addRoute(ALL_ROUTES, 0)
152-
.apply {
153-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
154-
// Where possible, we want to explicitly set the proxy in addition to
155-
// manually redirecting traffic. This is useful because it captures HTTP sent
156-
// to non-default ports. We still need to do both though, as not all clients
157-
// will use the proxy settings.
158-
setHttpProxy(ProxyInfo.buildDirectProxy(proxyConfig.ip, proxyConfig.port))
159-
}
152+
if (this.vpnInterface != null) return false // The VPN is already running, somehow? Do nothing
153+
154+
app!!.pauseEvents() // Try not to send events while the VPN is active, it's unnecessary noise
155+
app!!.trackEvent("VPN", "vpn-started")
156+
val vpnInterface = Builder()
157+
.addAddress(VPN_IP_ADDRESS, 32)
158+
.addRoute(ALL_ROUTES, 0)
159+
.apply {
160+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
161+
// Where possible, we want to explicitly set the proxy in addition to
162+
// manually redirecting traffic. This is useful because it captures HTTP sent
163+
// to non-default ports. We still need to do both though, as not all clients
164+
// will use the proxy settings.
165+
setHttpProxy(ProxyInfo.buildDirectProxy(proxyConfig.ip, proxyConfig.port))
160166
}
167+
}
168+
169+
.setMtu(MAX_PACKET_LEN) // Limit the packet size to the buffer used by ProxyVpnRunnable
170+
.setBlocking(true) // We use a blocking loop to read in ProxyVpnRunnable
161171

162-
.setMtu(MAX_PACKET_LEN) // Limit the packet size to the buffer used by ProxyVpnRunnable
163-
.setBlocking(true) // We use a blocking loop to read in ProxyVpnRunnable
164-
165-
.apply {
166-
// We exclude ourselves from interception, so we can still make network requests
167-
// separately, primarily because otherwise pinging with isReachable is recursive.
168-
val httpToolkitPackage = packageName
169-
170-
// For some reason, with Genymotion the whole device crashes if we intercept
171-
// blindly, but intercepting every single application explicitly is fine.
172-
if (isGenymotion) {
173-
packageNames.forEach { name ->
174-
if (name != httpToolkitPackage) addAllowedApplication(name)
175-
}
176-
} else {
177-
addDisallowedApplication(httpToolkitPackage)
172+
.apply {
173+
// We exclude ourselves from interception, so we can still make network requests
174+
// separately, primarily because otherwise pinging with isReachable is recursive.
175+
val httpToolkitPackage = packageName
176+
177+
// For some reason, with Genymotion the whole device crashes if we intercept
178+
// blindly, but intercepting every single application explicitly is fine.
179+
if (isGenymotion) {
180+
packageNames.forEach { name ->
181+
if (name != httpToolkitPackage) addAllowedApplication(name)
178182
}
183+
} else {
184+
addDisallowedApplication(httpToolkitPackage)
179185
}
180-
.setSession(getString(R.string.app_name))
181-
.establish()
182-
183-
app.lastProxy = proxyConfig
184-
showServiceNotification()
185-
localBroadcastManager!!.sendBroadcast(
186-
Intent(VPN_STARTED_BROADCAST).apply {
187-
putExtra(PROXY_CONFIG_EXTRA, proxyConfig)
188-
}
189-
)
186+
}
187+
.setSession(getString(R.string.app_name))
188+
.establish()
190189

191-
SocketProtector.getInstance().setProtector(this)
192-
193-
vpnRunnable = ProxyVpnRunnable(
194-
vpnInterface!!,
195-
proxyConfig.ip,
196-
proxyConfig.port,
197-
intArrayOf(
198-
80, // HTTP
199-
443, // HTTPS
200-
8000, 8001, 8080, 8888, 9000 // Common local dev ports
201-
)
190+
// establish() returns null if we no longer have permissions to establish the VPN somehow
191+
// In that case, we give up. The UI
192+
if (vpnInterface == null) {
193+
return false
194+
} else {
195+
this.vpnInterface = vpnInterface
196+
}
197+
198+
app.lastProxy = proxyConfig
199+
showServiceNotification()
200+
localBroadcastManager!!.sendBroadcast(
201+
Intent(VPN_STARTED_BROADCAST).apply {
202+
putExtra(PROXY_CONFIG_EXTRA, proxyConfig)
203+
}
204+
)
205+
206+
SocketProtector.getInstance().setProtector(this)
207+
208+
vpnRunnable = ProxyVpnRunnable(
209+
vpnInterface,
210+
proxyConfig.ip,
211+
proxyConfig.port,
212+
intArrayOf(
213+
80, // HTTP
214+
443, // HTTPS
215+
8000, 8001, 8080, 8888, 9000 // Common local dev ports
202216
)
203-
Thread(vpnRunnable, "Vpn thread").start()
217+
)
218+
Thread(vpnRunnable, "Vpn thread").start()
204219

205-
app.vpnShouldBeRunning = true
206-
}
220+
app.vpnShouldBeRunning = true
221+
return true
207222
}
208223

209224
private fun stopVpn() {

0 commit comments

Comments
 (0)