DnsVpnService.kt 45.8 KB
Newer Older
Daniel Wolf's avatar
Daniel Wolf committed
1
2
package com.frostnerd.smokescreen.service

3
import android.app.NotificationManager
4
import android.app.PendingIntent
Daniel Wolf's avatar
Daniel Wolf committed
5
6
7
import android.app.Service
import android.content.Context
import android.content.Intent
8
import android.content.SharedPreferences
Daniel Wolf's avatar
Daniel Wolf committed
9
import android.content.pm.PackageManager
10
11
import android.net.*
import android.os.*
Daniel Wolf's avatar
Daniel Wolf committed
12
import android.system.OsConstants
13
import android.util.Base64
Daniel Wolf's avatar
Daniel Wolf committed
14
15
import androidx.core.app.NotificationCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
16
import com.frostnerd.dnstunnelproxy.*
Daniel Wolf's avatar
Daniel Wolf committed
17
import com.frostnerd.encrypteddnstunnelproxy.HttpsDnsServerInformation
Daniel Wolf's avatar
Daniel Wolf committed
18
import com.frostnerd.encrypteddnstunnelproxy.ServerConfiguration
Daniel Wolf's avatar
Daniel Wolf committed
19
import com.frostnerd.encrypteddnstunnelproxy.tls.TLSUpstreamAddress
20
import com.frostnerd.general.CombinedIterator
21
import com.frostnerd.general.service.isServiceRunning
22
import com.frostnerd.preferenceskt.typedpreferences.TypedPreferences
23
import com.frostnerd.smokescreen.*
24
25
import com.frostnerd.smokescreen.BuildConfig
import com.frostnerd.smokescreen.R
26
import com.frostnerd.smokescreen.activity.BackgroundVpnConfigureActivity
27
28
import com.frostnerd.smokescreen.activity.PinActivity
import com.frostnerd.smokescreen.activity.PinType
29
30
import com.frostnerd.smokescreen.database.entities.CachedResponse
import com.frostnerd.smokescreen.database.getDatabase
Daniel Wolf's avatar
Daniel Wolf committed
31
import com.frostnerd.smokescreen.util.Notifications
32
import com.frostnerd.smokescreen.util.proxy.ProxyBypassHandler
Daniel Wolf's avatar
Daniel Wolf committed
33
34
import com.frostnerd.smokescreen.util.proxy.ProxyHttpsHandler
import com.frostnerd.smokescreen.util.proxy.ProxyTlsHandler
Daniel Wolf's avatar
Daniel Wolf committed
35
import com.frostnerd.smokescreen.util.proxy.SmokeProxy
36
import com.frostnerd.vpntunnelproxy.TrafficStats
37
import com.frostnerd.vpntunnelproxy.VPNTunnelProxy
38
import kotlinx.coroutines.*
Daniel Wolf's avatar
Daniel Wolf committed
39
import org.minidns.dnsmessage.DnsMessage
40
41
import org.minidns.dnsmessage.Question
import org.minidns.dnsname.DnsName
42
43
import org.minidns.record.A
import org.minidns.record.AAAA
44
import org.minidns.record.Record
45
46
import java.io.ByteArrayInputStream
import java.io.DataInputStream
Daniel Wolf's avatar
Daniel Wolf committed
47
import java.io.Serializable
48
import java.lang.IllegalStateException
49
import java.net.Inet4Address
50
import java.net.Inet6Address
51
import java.net.InetAddress
52
import java.util.concurrent.TimeoutException
53
import java.util.logging.Level
Daniel Wolf's avatar
Daniel Wolf committed
54
55


Daniel Wolf's avatar
Daniel Wolf committed
56
57
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
Daniel Wolf's avatar
Daniel Wolf committed
58
 *
Daniel Wolf's avatar
Daniel Wolf committed
59
60
61
62
63
64
65
66
67
68
69
70
71
72
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You can contact the developer at daniel.wolf@frostnerd.com.
Daniel Wolf's avatar
Daniel Wolf committed
73
74
75
 */
class DnsVpnService : VpnService(), Runnable {
    private var fileDescriptor: ParcelFileDescriptor? = null
Daniel Wolf's avatar
Daniel Wolf committed
76
    private var handle: ProxyHttpsHandler? = null
Daniel Wolf's avatar
Daniel Wolf committed
77
    private var dnsProxy: SmokeProxy? = null
78
    private var vpnProxy: VPNTunnelProxy? = null
Daniel Wolf's avatar
Daniel Wolf committed
79
80
    private var destroyed = false
    private lateinit var notificationBuilder: NotificationCompat.Builder
81
82
    private lateinit var noConnectionNotificationBuilder: NotificationCompat.Builder
    private var noConnectionNotificationShown = false
Daniel Wolf's avatar
Daniel Wolf committed
83
    private lateinit var serverConfig:DnsServerConfiguration
84
    private lateinit var settingsSubscription: TypedPreferences<SharedPreferences>.OnPreferenceChangeListener
85
    private var networkCallback:ConnectivityManager.NetworkCallback? = null
86
    private var pauseNotificationAction:NotificationCompat.Action? = null
87
    private var queryCountOffset: Long = 0
88
    private var packageBypassAmount = 0
89
    private var connectedToANetwork:Boolean? = null
90
91
92
93
94

    /*
        URLs passed to the Service, which haven't been retrieved from the settings.
        Null if the current servers are from the settings
     */
95
    private var userServerConfig:DnsServerInformation<*>? = null
Daniel Wolf's avatar
Daniel Wolf committed
96
97
98
99

    companion object {
        const val BROADCAST_VPN_ACTIVE = BuildConfig.APPLICATION_ID + ".VPN_ACTIVE"
        const val BROADCAST_VPN_INACTIVE = BuildConfig.APPLICATION_ID + ".VPN_INACTIVE"
100
101
        var currentTrafficStats: TrafficStats? = null
            private set
Daniel Wolf's avatar
Daniel Wolf committed
102

103
        fun startVpn(context: Context, serverInfo:DnsServerInformation<*>? = null) {
104
            val intent = Intent(context, DnsVpnService::class.java)
105
106
107
            if (serverInfo != null) {
                BackgroundVpnConfigureActivity.writeServerInfoToIntent(serverInfo, intent)
            }
108
109
110
            context.startForegroundServiceCompat(intent)
        }

111
        fun restartVpn(context: Context, fetchServersFromSettings: Boolean) {
112
113
114
115
116
            if(context.isServiceRunning(DnsVpnService::class.java)) {
                val bundle = Bundle()
                bundle.putBoolean("fetch_servers", fetchServersFromSettings)
                sendCommand(context, Command.RESTART, bundle)
            } else startVpn(context)
117
118
        }

119
        fun restartVpn(context: Context, serverInfo:DnsServerInformation<*>?) {
120
121
            if(context.isServiceRunning(DnsVpnService::class.java)) {
                val bundle = Bundle()
122
123
                if(serverInfo != null) {
                    BackgroundVpnConfigureActivity.writeServerInfoToIntent(serverInfo, bundle)
124
125
                    bundle.putBoolean("fetch_servers", true)
                }
126
                sendCommand(context, Command.RESTART, bundle)
127
            } else startVpn(context, serverInfo)
128
129
130
131
132
133
        }

        fun sendCommand(context: Context, command: Command, extras: Bundle? = null) {
            val intent = Intent(context, DnsVpnService::class.java).putExtra("command", command)
            if (extras != null) intent.putExtras(extras)
            context.startService(intent)
Daniel Wolf's avatar
Daniel Wolf committed
134
        }
135
136
137
138
139
140

        fun commandIntent(context: Context, command: Command, extras: Bundle? = null): Intent {
            val intent = Intent(context, DnsVpnService::class.java).putExtra("command", command)
            if (extras != null) intent.putExtras(extras)
            return intent
        }
Daniel Wolf's avatar
Daniel Wolf committed
141
142
143
144
145
    }


    override fun onCreate() {
        super.onCreate()
146
        Thread.setDefaultUncaughtExceptionHandler { t, e ->
147
            log("Encountered an uncaught exception.")
148
149
150
            destroy()
            stopForeground(true)
            stopSelf()
151
            (application as SmokeScreen).customUncaughtExceptionHandler.uncaughtException(t, e)
152
        }
153
        log("Service onCreate()")
154
        createNotification()
155
        updateServiceTile()
156
        subscribeToSettings()
157
        addNetworkChangeListener()
158
159
        log("Service created.")
    }
160

161
162
163
164
165
    private fun addNetworkChangeListener() {
        val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        networkCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onLost(network: Network?) {
                super.onLost(network)
Daniel Wolf's avatar
Daniel Wolf committed
166
167
168
                val activeNetwork = mgr.activeNetworkInfo
                val networkInfo = mgr.getNetworkInfo(network)
                log("Network lost: $network, info: $networkInfo, current active network: $activeNetwork")
169
170
171
172
173
                handleChange()
            }

            override fun onAvailable(network: Network?) {
                super.onAvailable(network)
Daniel Wolf's avatar
Daniel Wolf committed
174
175
176
                val activeNetwork = mgr.activeNetworkInfo
                val networkInfo = mgr.getNetworkInfo(network)
                log("Network became available: $network, info: $networkInfo, current active network: $activeNetwork")
177
178
179
180
                handleChange()
            }

            private fun handleChange() {
181
                if(this@DnsVpnService::serverConfig.isInitialized) serverConfig.forEachAddress { _, upstreamAddress ->
182
183
184
                    upstreamAddress.addressCreator.reset()
                    upstreamAddress.addressCreator.resolve(force = true, runResolveNow = true)
                }
185
186
187
188
189
190
191
192
                if (fileDescriptor != null) recreateVpn(false, null)
            }
        }
        val builder = NetworkRequest.Builder()
        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
        mgr.registerNetworkCallback(builder.build(), networkCallback)
    }

193
194
195
196
197
198
199
200
201
202
203
204
    private fun subscribeToSettings() {
        log("Subscribing to settings for automated restart")
        val relevantSettings = mutableSetOf(
            "ipv4_enabled",
            "ipv6_enabled",
            "force_ipv6",
            "force_ipv4",
            "catch_known_servers",
            "dnscache_enabled",
            "dnscache_maxsize",
            "dnscache_use_default_time",
            "dnscache_custom_time",
205
            "user_bypass_packages",
206
            "dnscache_keepacrosslaunches",
207
            "bypass_searchdomains",
208
            "user_bypass_blacklist",
Daniel Wolf's avatar
Daniel Wolf committed
209
            "log_dns_queries",
210
            "show_notification_on_lockscreen",
211
            "hide_notification_icon",
212
            "pause_on_captive_portal",
213
214
            "null_terminate_keweon",
            "allow_ipv6_traffic",
Daniel Wolf's avatar
Daniel Wolf committed
215
            "allow_ipv4_traffic",
216
217
            "dns_server_config",
            "notification_allow_stop",
218
219
            "notification_allow_pause",
            "dns_rules_enabled"
220
        )
Daniel Wolf's avatar
Daniel Wolf committed
221
222
        settingsSubscription = getPreferences().listenForChanges(relevantSettings, getPreferences().preferenceChangeListener {
                changes ->
223
            log("The Preference(s) ${changes.keys} have changed, restarting the VPN.")
224
            log("Detailed changes: $changes")
225
            if("hide_notification_icon" in changes || "hide_notification_icon" in changes) {
226
227
228
                log("Recreating the notification because of the change in preferences")
                createNotification()
                setNotificationText()
229
230
231
232
            } else if("notification_allow_pause" in changes || "notification_allow_stop" in changes) {
                log("Recreating the notification because of the change in preferences")
                createNotification()
                setNotificationText()
233
            }
Daniel Wolf's avatar
Daniel Wolf committed
234
            val reload = "dns_server_config" in changes
235
            recreateVpn(reload, null)
Daniel Wolf's avatar
Daniel Wolf committed
236
        })
237
238
239
        log("Subscribed.")
    }

240
241
    private fun createNotification() {
        log("Creating notification")
242
        notificationBuilder = NotificationCompat.Builder(this, Notifications.servicePersistentNotificationChannel(this))
243
244
245
246
        if(getPreferences().hideNotificationIcon)
            notificationBuilder.priority = NotificationCompat.PRIORITY_MIN
        if(!getPreferences().showNotificationOnLockscreen)
            notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_SECRET)
Daniel Wolf's avatar
Daniel Wolf committed
247
        notificationBuilder.setContentTitle(getString(R.string.app_name))
248
        notificationBuilder.setSmallIcon(R.drawable.ic_mainnotification)
249
250
        notificationBuilder.setOngoing(true)
        notificationBuilder.setAutoCancel(false)
251
        notificationBuilder.setSound(null)
Daniel Wolf's avatar
Daniel Wolf committed
252
        notificationBuilder.setOnlyAlertOnce(true)
253
        notificationBuilder.setUsesChronometer(true)
254
255
256
        notificationBuilder.setContentIntent(
            PendingIntent.getActivity(
                this, 1,
257
                Intent(this, PinActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT
258
259
            )
        )
260
261
262
263
264
265
266
267
268
269
270
271
        if(getPreferences().allowStopInNotification) {
            val stopPendingIntent =
                PendingIntent.getService(this, 1, commandIntent(this, Command.STOP), PendingIntent.FLAG_CANCEL_CURRENT)
            val stopAction = NotificationCompat.Action(R.drawable.ic_stop, getString(R.string.all_stop), stopPendingIntent)
            notificationBuilder.addAction(stopAction)
        }
        if(getPreferences().allowPauseInNotification) {
            val pausePendingIntent =
                PendingIntent.getService(this, 2, commandIntent(this, Command.PAUSE_RESUME), PendingIntent.FLAG_CANCEL_CURRENT)
            pauseNotificationAction = NotificationCompat.Action(R.drawable.ic_stat_pause, getString(R.string.all_pause), pausePendingIntent)
            notificationBuilder.addAction(pauseNotificationAction)
        }
Daniel Wolf's avatar
Daniel Wolf committed
272
        updateNotification(0)
273
        log("Notification created and posted.")
Daniel Wolf's avatar
Daniel Wolf committed
274
275
    }

276
    private fun updateNotification(queryCount: Int? = null) {
277
278
279
280
281
282
        if (queryCount != null) notificationBuilder.setSubText(
            getString(
                R.string.notification_main_subtext,
                queryCount + queryCountOffset
            )
        )
Daniel Wolf's avatar
Daniel Wolf committed
283
284
285
        startForeground(1, notificationBuilder.build())
    }

286
    private fun showNoConnectionNotification() {
287
        if(!getPreferences().showNoConnectionNotification) return
288
289
290
        if(!this::noConnectionNotificationBuilder.isInitialized) {
            noConnectionNotificationBuilder = NotificationCompat.Builder(this, Notifications.noConnectionNotificationChannelId(this))
            noConnectionNotificationBuilder.priority = NotificationCompat.PRIORITY_HIGH
291
            noConnectionNotificationBuilder.setOngoing(false)
292
            noConnectionNotificationBuilder.setSmallIcon(R.drawable.ic_cloud_strikethrough)
293
294
295
296
297
298
299
300
301
302
303
304
305
            noConnectionNotificationBuilder.setContentTitle(getString(R.string.notification_noconnection_title))
            noConnectionNotificationBuilder.setContentText(getString(R.string.notification_noconnection_text))
            noConnectionNotificationBuilder.setStyle(NotificationCompat.BigTextStyle(notificationBuilder).bigText(getString(R.string.notification_noconnection_text)))
        }
        if(!noConnectionNotificationShown) (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(2, noConnectionNotificationBuilder.build())
        noConnectionNotificationShown = true
    }

    private fun hideNoConnectionNotification() {
        if(noConnectionNotificationShown) (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(2)
        noConnectionNotificationShown = false
    }

Daniel Wolf's avatar
Daniel Wolf committed
306
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
307
        log("Service onStartCommand", intent = intent)
Daniel Wolf's avatar
Daniel Wolf committed
308
309
310
311
312
        if (intent != null && intent.hasExtra("command")) {
            val command = intent.getSerializableExtra("command") as Command

            when (command) {
                Command.STOP -> {
313
314
315
316
317
318
319
320
321
322
                    log("Received STOP command.")
                    if(PinActivity.shouldValidatePin(this, intent)) {
                        log("The pin has to be validated before actually stopping.")
                        PinActivity.askForPin(this, PinType.STOP_SERVICE)
                    } else {
                        log("No need to ask for pin, stopping.")
                        destroy()
                        stopForeground(true)
                        stopSelf()
                    }
Daniel Wolf's avatar
Daniel Wolf committed
323
                }
324
                Command.RESTART -> {
325
                    log("Received RESTART command, restarting vpn.")
326
                    recreateVpn(intent.getBooleanExtra("fetch_servers", false), intent)
327
                    setNotificationText()
328
                }
329
330
331
332
333
                Command.PAUSE_RESUME -> {
                    if(vpnProxy != null) {
                        destroy(false)
                        pauseNotificationAction?.title = getString(R.string.all_resume)
                        pauseNotificationAction?.icon = R.drawable.ic_stat_resume
334
                        notificationBuilder.setSmallIcon(R.drawable.ic_notification_paused)
335
336
337
338
                    } else {
                        recreateVpn(false, null)
                        pauseNotificationAction?.title = getString(R.string.all_pause)
                        pauseNotificationAction?.icon = R.drawable.ic_stat_pause
339
                        notificationBuilder.setSmallIcon(R.drawable.ic_mainnotification)
340
341
342
                    }
                    updateNotification()
                }
Daniel Wolf's avatar
Daniel Wolf committed
343
            }
344
        } else {
345
            log("No command passed, fetching servers and establishing connection if needed")
346
            log("Checking whether The VPN is prepared")
347
            if (VpnService.prepare(this) != null) {
348
                log("The VPN isn't prepared, stopping self and starting Background configure")
349
                updateNotification(0)
350
351
352
                stopForeground(true)
                destroy()
                stopSelf()
353
354
                BackgroundVpnConfigureActivity.prepareVpn(
                    this,
355
                    userServerConfig
356
                )
357
358
359
            } else {
                log("The VPN is prepared, proceeding.")
                if (!destroyed) {
Daniel Wolf's avatar
Daniel Wolf committed
360
                    if (!this::serverConfig.isInitialized) {
361
362
363
364
365
366
                        setServerConfiguration(intent)
                        setNotificationText()
                    }
                    updateNotification(0)
                    establishVpn()
                }
367
            }
368
369
370
371
372
        }
        return if (destroyed) Service.START_NOT_STICKY else Service.START_STICKY
    }

    private fun setServerConfiguration(intent: Intent?) {
373
        log("Updating server configuration..")
374
        userServerConfig = BackgroundVpnConfigureActivity.readServerInfoFromIntent(intent)
Daniel Wolf's avatar
Daniel Wolf committed
375
        serverConfig = getServerConfig()
376
        serverConfig.forEachAddress { _, address ->
377
            if(!address.addressCreator.isCurrentlyResolving()) address.addressCreator.resolveOrGetResultOrNull(true)
378
            address.addressCreator.whenResolveFailed {
379
                showNoConnectionNotification()
380
381
382
383
384
385
                if(it is TimeoutException) {
                    address.addressCreator.resolveOrGetResultOrNull(true)
                    address.addressCreator.whenResolveFinishedSuccessfully {
                        hideNoConnectionNotification()
                    }
                }
386
            }
387
            address.addressCreator.whenResolveFinishedSuccessfully {
388
389
390
                hideNoConnectionNotification()
            }
        }
Daniel Wolf's avatar
Daniel Wolf committed
391
        log("Server configuration updated to $serverConfig")
392
393
394
    }

    private fun setNotificationText() {
Daniel Wolf's avatar
Daniel Wolf committed
395
396
397
398
        val primaryServer:String
        val secondaryServer:String?
        if(serverConfig.httpsConfiguration != null) {
            notificationBuilder.setContentTitle(getString(R.string.notification_main_title_https))
399
400
            primaryServer = serverConfig.httpsConfiguration!![0].urlCreator.address.getUrl(true)
            secondaryServer = serverConfig.httpsConfiguration!!.getOrNull(1)?.urlCreator?.address?.getUrl(true)
Daniel Wolf's avatar
Daniel Wolf committed
401
402
        } else {
            notificationBuilder.setContentTitle(getString(R.string.notification_main_title_tls))
403
404
            primaryServer = serverConfig.tlsConfiguration!![0].formatToString()
            secondaryServer = serverConfig.tlsConfiguration!!.getOrNull(1)?.formatToString()
Daniel Wolf's avatar
Daniel Wolf committed
405
        }
406
407
        val text = if (secondaryServer != null) {
            getString(
408
                if(getPreferences().isBypassBlacklist) R.string.notification_main_text_with_secondary else R.string.notification_main_text_with_secondary_whitelist,
Daniel Wolf's avatar
Daniel Wolf committed
409
410
                primaryServer,
                secondaryServer,
411
                packageBypassAmount,
412
                dnsProxy?.cache?.livingCachedEntries() ?: 0
413
            )
414
        } else {
415
            getString(
416
                if(getPreferences().isBypassBlacklist) R.string.notification_main_text else R.string.notification_main_text_whitelist,
Daniel Wolf's avatar
Daniel Wolf committed
417
                primaryServer,
418
                packageBypassAmount,
419
                dnsProxy?.cache?.livingCachedEntries() ?: 0
420
            )
421
        }
422
        notificationBuilder.setStyle(NotificationCompat.BigTextStyle(notificationBuilder).bigText(text))
Daniel Wolf's avatar
Daniel Wolf committed
423
424
    }

425
    private fun establishVpn() {
426
        log("Establishing VPN")
427
428
        if (fileDescriptor == null) {
            fileDescriptor = createBuilder().establish()
429
            run()
430
            setNotificationText()
431
            updateNotification()
432
        } else log("Connection already running, no need to establish.")
433
434
    }

435
    private fun recreateVpn(reloadServerConfiguration: Boolean, intent: Intent?) {
436
        log("Recreating the VPN (destroying & establishing)")
437
        destroy(false)
438
439
440
        if (VpnService.prepare(this) == null) {
            log("VpnService is still prepared, establishing VPN.")
            destroyed = false
Daniel Wolf's avatar
Daniel Wolf committed
441
            if (reloadServerConfiguration || !this::serverConfig.isInitialized) {
442
443
                log("Re-fetching the servers (from intent or settings)")
                setServerConfiguration(intent)
444
445
            } else serverConfig.forEachAddress { _, address ->
                address.addressCreator.resolveOrGetResultOrNull(true)
446
447
            }
            establishVpn()
448
            setNotificationText()
449
            updateNotification(0)
450
451
452
453
        } else {
            log("VpnService isn't prepared, launching BackgroundVpnConfigureActivity.")
            BackgroundVpnConfigureActivity.prepareVpn(
                this,
454
                userServerConfig
455
456
457
458
459
            )
            log("BackgroundVpnConfigureActivity launched, stopping service.")
            stopForeground(true)
            stopSelf()
        }
460
461
    }

462
    private fun destroy(isStoppingCompletely:Boolean = true) {
463
        log("Destroying the VPN")
464
        if(isStoppingCompletely || connectedToANetwork == true) hideNoConnectionNotification()
Daniel Wolf's avatar
Daniel Wolf committed
465
        if (!destroyed) {
466
            queryCountOffset += currentTrafficStats?.packetsReceivedFromDevice ?: 0
Daniel Wolf's avatar
Daniel Wolf committed
467
468
            vpnProxy?.stop()
            fileDescriptor?.close()
469
470
471
472
            if(isStoppingCompletely && networkCallback != null) {
                (getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).unregisterNetworkCallback(networkCallback)
                networkCallback = null
            }
473
474
            vpnProxy = null
            fileDescriptor = null
475
            destroyed = true
476
477
            log("VPN destroyed.")
        } else log("VPN is already destroyed.")
478
        currentTrafficStats = null
Daniel Wolf's avatar
Daniel Wolf committed
479
480
481
482
    }

    override fun onDestroy() {
        super.onDestroy()
Daniel Wolf's avatar
Daniel Wolf committed
483
        log("onDestroy() called (Was destroyed from within: $destroyed)")
484
485
486
487
        log("Unregistering settings listener")
        getPreferences().unregisterOnChangeListener(settingsSubscription)
        log("Unregistered.")

488
        if (!destroyed && resources.getBoolean(R.bool.keep_service_alive)) {
489
            log("The service wasn't destroyed from within and keep_service_alive is true, restarting VPN.")
490
            val restartIntent = Intent(this, VpnRestartService::class.java)
491
            if(userServerConfig != null) BackgroundVpnConfigureActivity.writeServerInfoToIntent(userServerConfig!!, restartIntent)
492
            startForegroundServiceCompat(restartIntent)
493
494
495
        } else {
            destroy()
            LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(BROADCAST_VPN_INACTIVE))
496
        }
497
        updateServiceTile()
Daniel Wolf's avatar
Daniel Wolf committed
498
        log("onDestroy() done.")
Daniel Wolf's avatar
Daniel Wolf committed
499
500
    }

501
    override fun onRevoke() {
502
        log("onRevoke() called")
503
504
505
506
        destroy()
        stopForeground(true)
        stopSelf()
        if (getPreferences().disallowOtherVpns) {
Daniel Wolf's avatar
Daniel Wolf committed
507
            log("Disallow other VPNs is true, restarting in 250ms")
508
            Handler(Looper.getMainLooper()).postDelayed({
509
                BackgroundVpnConfigureActivity.prepareVpn(this, userServerConfig)
510
511
512
513
            }, 250)
        }
    }

Daniel Wolf's avatar
Daniel Wolf committed
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
    private fun randomLocalIPv6Address(): String {
        val prefix = StringBuilder(randomIPv6LocalPrefix())
        for (i in 0..4) prefix.append(":").append(randomIPv6Block(16, false))
        return prefix.toString()
    }

    private fun randomIPv6LocalPrefix(): String {
        return "fd" + randomIPv6Block(8, true) + ":" + randomIPv6Block(16, false) + ":" + randomIPv6Block(16, false)
    }

    private fun randomIPv6Block(bits: Int, leading_zeros: Boolean): String {
        var hex = java.lang.Long.toHexString(Math.floor(Math.random() * Math.pow(2.0, bits.toDouble())).toLong())
        if (!leading_zeros || hex.length == bits / 4);
        hex = "0000".substring(0, bits / 4 - hex.length) + hex
        return hex
    }

Daniel Wolf's avatar
Daniel Wolf committed
531
    private fun createBuilder(): Builder {
532
        log("Creating the VpnBuilder.")
533
534
535
536
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val activeNetwork = (getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetwork
            log("Current active network: $activeNetwork")
        }
Daniel Wolf's avatar
Daniel Wolf committed
537
        val builder = Builder()
538

539
540
541
        val deviceHasIpv6 = hasDeviceIpv6Address()
        val deviceHasIpv4 = hasDeviceIpv4Address()

542
543
        val dhcpDnsServer = getDhcpDnsServers()

544
        val useIpv6 = getPreferences().enableIpv6 && (getPreferences().forceIpv6 || deviceHasIpv6)
545
        val useIpv4 =
546
            !useIpv6 || (getPreferences().enableIpv4 && (getPreferences().forceIpv4 || deviceHasIpv4))
Daniel Wolf's avatar
Daniel Wolf committed
547

548
549
        val allowIpv4Traffic = useIpv4 || getPreferences().allowIpv4Traffic
        val allowIpv6Traffic = useIpv6 || getPreferences().allowIpv6Traffic
Daniel Wolf's avatar
Daniel Wolf committed
550

551
552
        val dummyServerIpv4 = getPreferences().dummyDnsAddressIpv4
        val dummyServerIpv6 = getPreferences().dummyDnsAddressIpv6
553
554
        log("Dummy address for Ipv4: $dummyServerIpv4")
        log("Dummy address for Ipv6: $dummyServerIpv6")
555
556
        log("Using IPv4: $useIpv4 (device has IPv4: $deviceHasIpv4), Ipv4 Traffic allowed: $allowIpv4Traffic")
        log("Using IPv6: $useIpv6 (device has IPv6: $deviceHasIpv6), Ipv6 Traffic allowed: $allowIpv6Traffic")
557
        log("DHCP Dns servers: $dhcpDnsServer")
558

559
        var couldSetAddress = false
560
        if (useIpv4) {
561
562
563
564
565
566
567
568
569
570
571
572
573
574
            for (address in resources.getStringArray(R.array.interface_address_prefixes)) {
                val prefix = address.split("/")[0]
                val mask = address.split("/")[1].toInt()
                try {
                    builder.addAddress("$prefix.134", mask)
                    couldSetAddress = true
                    log("Ipv4-Address set to $prefix.134./$mask")
                    break
                } catch (ignored: IllegalArgumentException) {
                    log("Couldn't set Ipv4-Address $prefix.134/$mask")
                }
            }
            if (!couldSetAddress) {
                log("Couldn't set any IPv4 dynamic address, trying 192.168.0.10...")
Daniel Wolf's avatar
Daniel Wolf committed
575
                builder.addAddress("192.168.0.10", 24)
576
577
578
579
580
            }
        }
        couldSetAddress = false

        var tries = 0
581
        if (useIpv6) {
582
            do {
Daniel Wolf's avatar
Daniel Wolf committed
583
                val addr = randomLocalIPv6Address()
584
585
586
587
588
589
                try {
                    builder.addAddress(addr, 48)
                    couldSetAddress = true
                    log("Ipv6-Address set to $addr")
                    break
                } catch (e: IllegalArgumentException) {
590
                    if (tries >= 5) throw e
591
592
                    log("Couldn't set Ipv6-Address $addr, try $tries")
                }
593
            } while (!couldSetAddress && ++tries < 5)
594
        }
Daniel Wolf's avatar
Daniel Wolf committed
595

596
        if (getPreferences().catchKnownDnsServers) {
597
            log("Interception of requests towards known DNS servers is enabled, adding routes.")
598
            for (server in DnsServerInformation.waitUntilKnownServersArePopulated(-1)!!.values) {
599
                log("Adding all routes for ${server.name}")
600
                server.servers.forEach {
601
602
603
604
                    it.address.addressCreator.whenResolveFinishedSuccessfully { addresses ->
                        addresses.forEach { address ->
                            if (address is Inet6Address && useIpv6) {
                                log("Adding route for Ipv6 $address")
605
                                builder.addRoute(address, 128)
606
607
                            } else if (address is Inet4Address && useIpv4) {
                                log("Adding route for Ipv4 $address")
608
                                builder.addRoute(address, 32)
609
                            }
610
611
                        }
                    }
612
                    if(!it.address.addressCreator.startedResolve && !it.address.addressCreator.isCurrentlyResolving()) it.address.addressCreator.resolveOrGetResultOrNull(true, true)
613
                }
Daniel Wolf's avatar
Daniel Wolf committed
614
            }
615
        } else log("Not intercepting traffic towards known DNS servers.")
Daniel Wolf's avatar
Daniel Wolf committed
616
        builder.setSession(getString(R.string.app_name))
617
        if (useIpv4) {
618
619
            builder.addDnsServer(dummyServerIpv4)
            builder.addRoute(dummyServerIpv4, 32)
620
            dhcpDnsServer.forEach {
621
                if (it is Inet4Address) {
622
623
                    builder.addRoute(it, 32)
                }
624
625
            }
        } else if (deviceHasIpv4 && allowIpv4Traffic) builder.allowFamily(OsConstants.AF_INET) // If not allowing no IPv4 connections work anymore.
626

627
        if (useIpv6) {
628
629
            builder.addDnsServer(dummyServerIpv6)
            builder.addRoute(dummyServerIpv6, 128)
630
631
632
633
634
            dhcpDnsServer.forEach {
                if(it is Inet6Address) {
                    builder.addRoute(it, 128)
                }
            }
635
        } else if(deviceHasIpv6 && allowIpv6Traffic) builder.allowFamily(OsConstants.AF_INET6)
Daniel Wolf's avatar
Daniel Wolf committed
636
637
        builder.setBlocking(true)

638
639
640
641
642
643
644
645
646
647
        log("Applying disallowed packages.")
        val userBypass = getPreferences().userBypassPackages
        val defaultBypass = getPreferences().defaultBypassPackages
        if(getPreferences().isBypassBlacklist || userBypass.size == 0) {
            log("Mode is set to blacklist, bypassing ${userBypass.size + defaultBypass.size} packages.")
            for (defaultBypassPackage in CombinedIterator(userBypass.iterator(), defaultBypass.iterator())) {
                if (isPackageInstalled(defaultBypassPackage)) { //TODO Check what is faster: catching the exception, or checking ourselves
                    builder.addDisallowedApplication(defaultBypassPackage)
                } else log("Package $defaultBypassPackage not installed, thus not bypassing")
            }
648
            packageBypassAmount = userBypass.size
649
650
651
652
653
654
655
656
657
658
        } else {
            log("Mode is set to whitelist, whitelisting ${userBypass.size} packages.")
            for (pkg in userBypass) {
                if(!defaultBypass.contains(pkg)) {
                    if (isPackageInstalled(pkg)) { //TODO Check what is faster: catching the exception, or checking ourselves
                        builder.addAllowedApplication(pkg)
                        packageBypassAmount++
                    } else log("Package $pkg not installed, thus not bypassing")
                } else log("Not whitelisting $pkg, it is blacklisted by default.")
            }
Daniel Wolf's avatar
Daniel Wolf committed
659
660
661
662
        }
        return builder
    }

663
664
665
    private fun hasDeviceIpv4Address(): Boolean {
        val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        for (network in mgr.allNetworks) {
666
            if(network == null) continue
Daniel Wolf's avatar
Daniel Wolf committed
667
668
669
            val info = mgr.getNetworkInfo(network) ?: continue
            val capabilities = mgr.getNetworkCapabilities(network) ?: continue
            if (info.isConnected && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
670
                val linkProperties = mgr.getLinkProperties(network) ?: continue
671
                log("Checking for IPv4 address in connected non-VPN network ${info.typeName}")
672
673
                for (linkAddress in linkProperties.linkAddresses) {
                    if (linkAddress.address is Inet4Address && !linkAddress.address.isLoopbackAddress) {
674
                        log("IPv4 address found.")
675
676
677
678
679
                        return true
                    }
                }
            }
        }
680
        log("No IPv4 addresses found.")
681
682
683
        return false
    }

684
685
686
    private fun hasDeviceIpv6Address(): Boolean {
        val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        for (network in mgr.allNetworks) {
687
            if(network == null) continue
Daniel Wolf's avatar
Daniel Wolf committed
688
689
690
            val info = mgr.getNetworkInfo(network) ?: continue
            val capabilities = mgr.getNetworkCapabilities(network) ?: continue
            if (info.isConnected && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
691
                val linkProperties = mgr.getLinkProperties(network) ?: continue
692
                log("Checking for IPv6 address in connected non-VPN network ${info.typeName}")
693
694
                for (linkAddress in linkProperties.linkAddresses) {
                    if (linkAddress.address is Inet6Address && !linkAddress.address.isLoopbackAddress) {
695
                        log("IPv6 address found.")
696
697
698
699
700
                        return true
                    }
                }
            }
        }
701
        log("No IPv6 addresses found.")
702
703
704
        return false
    }

705
706
707
    private fun getDhcpDnsServers():List<InetAddress> {
        val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        for (network in mgr.allNetworks) {
708
            if(network == null) continue
Daniel Wolf's avatar
Daniel Wolf committed
709
710
711
            val info = mgr.getNetworkInfo(network) ?: continue
            val capabilities = mgr.getNetworkCapabilities(network) ?: continue
            if (info.isConnected && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
712
                val linkProperties = mgr.getLinkProperties(network) ?: continue
713
714
715
716
717
718
                return linkProperties.dnsServers
            }
        }
        return emptyList()
    }

Daniel Wolf's avatar
Daniel Wolf committed
719
720
    private fun getServerConfig(): DnsServerConfiguration {
        TLSUpstreamAddress
721
722
723
724
725
726
727
728
729
        return if(userServerConfig != null) {
            userServerConfig!!.let {config ->
                return if(config is HttpsDnsServerInformation) DnsServerConfiguration(config.serverConfigurations.values.toList(), null)
                else {
                    DnsServerConfiguration(null, config.servers.map {
                        it.address as TLSUpstreamAddress
                    })
                }
            }
Daniel Wolf's avatar
Daniel Wolf committed
730
731
732
733
734
735
736
737
738
739
740
741
742
        }
        else {
            val config = getPreferences().dnsServerConfig
            if(config.hasTlsServer()) {
                DnsServerConfiguration(null, config.servers.map {
                    it.address as TLSUpstreamAddress
                })
            } else {
                DnsServerConfiguration((config as HttpsDnsServerInformation).serverConfigurations.map {
                    it.value
                }, null)
            }
        }
743
744
    }

Daniel Wolf's avatar
Daniel Wolf committed
745
    override fun run() {
746
        log("run() called")
Daniel Wolf's avatar
Daniel Wolf committed
747
        log("Starting with config: $serverConfig")
Daniel Wolf's avatar
Daniel Wolf committed
748

749
        log("Creating handle.")
Daniel Wolf's avatar
Daniel Wolf committed
750
751
752
753
        val handle:DnsHandle
        if(serverConfig.httpsConfiguration != null) {
            handle = ProxyHttpsHandler(
                serverConfig.httpsConfiguration!!,
Daniel Wolf's avatar
Daniel Wolf committed
754
                connectTimeout = 5000,
Daniel Wolf's avatar
Daniel Wolf committed
755
756
757
758
759
760
761
762
                queryCountCallback = {
                    setNotificationText()
                    updateNotification(it)
                },
                nullRouteKeweon = getPreferences().isUsingKeweon() && getPreferences().nullTerminateKeweon
            )
        } else {
            handle = ProxyTlsHandler(serverConfig.tlsConfiguration!!,
Daniel Wolf's avatar
Daniel Wolf committed
763
                connectTimeout = 2000,
Daniel Wolf's avatar
Daniel Wolf committed
764
765
766
767
768
769
                queryCountCallback = {
                    setNotificationText()
                    updateNotification(it)
                },
                nullRouteKeweon = getPreferences().isUsingKeweon() && getPreferences().nullTerminateKeweon)
        }
770
        log("Handle created, creating DNS proxy")
771
772
773
        handle.ipv6Enabled = getPreferences().enableIpv6 && (getPreferences().forceIpv6 || hasDeviceIpv6Address())
        handle.ipv4Enabled =
            !handle.ipv6Enabled || (getPreferences().enableIpv4 && (getPreferences().forceIpv4 || hasDeviceIpv4Address()))
774

775
        dnsProxy = SmokeProxy(handle, createProxyBypassHandlers(), createDnsCache(), createQueryLogger(), createLocalResolver())
776
        log("DnsProxy created, creating VPN proxy")
777
        vpnProxy = VPNTunnelProxy(dnsProxy!!, vpnService = this, coroutineScope = CoroutineScope(
Daniel Wolf's avatar
Daniel Wolf committed
778
            newFixedThreadPoolContext(1, "proxy-pool")), logger = object:com.frostnerd.vpntunnelproxy.Logger() {
Daniel Wolf's avatar
Daniel Wolf committed
779
            override fun logException(ex: Exception, terminal: Boolean, level: Level) {
780
                if(terminal) log(ex)
Daniel Wolf's avatar
Daniel Wolf committed
781
                else log(Logger.stacktraceToString(ex), "VPN-LIBRARY, $level")
782
783
784
            }

            override fun logMessage(message: String, level: Level) {
Daniel Wolf's avatar
Daniel Wolf committed
785
                if(level >= Level.INFO || (BuildConfig.DEBUG && level >= Level.FINE)) {
786
787
788
789
                    log(message, "VPN-LIBRARY, $level")
                }
            }
        })
790
791

        log("VPN proxy creating, trying to run...")
792
793
        fileDescriptor?.let {
            vpnProxy?.run(it)
Daniel Wolf's avatar
Daniel Wolf committed
794
795
796
797
        } ?: kotlin.run {
            recreateVpn(false, null)
            return
        }
798
        log("VPN proxy started.")
799
        currentTrafficStats = vpnProxy?.trafficStats
800
801
802
        LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(BROADCAST_VPN_ACTIVE))
    }

803
804
805
806
807
808
809
    private fun createQueryLogger(): QueryListener? {
        return if(getPreferences().loggingEnabled || getPreferences().queryLoggingEnabled) {
            com.frostnerd.smokescreen.util.proxy.QueryListener(this)
        } else null
    }


810
811
812
813
    /**
     * Creates bypass handlers for each network and its associated search domains
     * Requests for .*SEARCHDOMAIN won't use doh and are sent to the DNS servers of the network they originated from.
     */
814
815
    private fun createProxyBypassHandlers(): MutableList<DnsHandle> {
        val bypassHandlers = mutableListOf<DnsHandle>()
816
817
818
819
        if(getPreferences().bypassSearchdomains) {
            log("Creating bypass handlers for search domains of connected networks.")
            val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            for (network in mgr.allNetworks) {
820
                if(network == null) continue
Daniel Wolf's avatar
Daniel Wolf committed
821
822
823
824
                val networkInfo = mgr.getNetworkInfo(network) ?: continue
                if (networkInfo.isConnected && !mgr.isVpnNetwork(network)) {
                    val linkProperties = mgr.getLinkProperties(network) ?: continue
                    if (!linkProperties.domains.isNullOrBlank()) {
825
826
827
828
829
830
831
                        log("Bypassing domains ${linkProperties.domains} for network of type ${networkInfo.typeName}")
                        val domains = linkProperties.domains.split(",").toList()
                        bypassHandlers.add(
                            ProxyBypassHandler(
                                domains,
                                linkProperties.dnsServers[0]!!
                            )
832
                        )
833
                    }
834
835
                }
            }
836
837
            log("${bypassHandlers.size} bypass handlers created.")
        } else log("Not creating bypass handlers for search domains, bypass is disabled.")
838
839
840
841
        if(getPreferences().pauseOnCaptivePortal) {
            val dhcpServers = getDhcpDnsServers()
            if(!dhcpServers.isEmpty()) bypassHandlers.add(CaptivePortalUdpDnsHandle(targetDnsServer = { dhcpServers.first() }))
        }
842
        bypassHandlers.add(NoConnectionDnsHandle(getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager, NoConnectionDnsHandle.Behavior.DROP_PACKETS) {
Daniel Wolf's avatar
Daniel Wolf committed
843
            log("Connection changed to connected=$it", "NoConnectionDnsHandle-Listener")
844
845
            connectedToANetwork = it
            if(!it) showNoConnectionNotification()
846
            else hideNoConnectionNotification()
Daniel Wolf's avatar
Daniel Wolf committed
847
        })
848
849
850
        return bypassHandlers
    }

851
    private fun createDnsCache():SimpleDnsCache? {
852
853
        val dnsCache: SimpleDnsCache?
        dnsCache = if (getPreferences().useDnsCache) {
854
            log("Creating DNS Cache.")
855
            val cacheControl: CacheControl = if (!getPreferences().useDefaultDnsCacheTime) {
856
                val cacheTime = getPreferences().customDnsCacheTime.toLong()
857
                val nxDomainCacheTime = getPreferences().nxDomainCacheTime.toLong()
858
                object : CacheControl {
Daniel Wolf's avatar
Daniel Wolf committed
859
860
861
862
863
                    override suspend fun getTtl(
                        answerMessage: DnsMessage,
                        dnsName: DnsName,
                        type: Record.TYPE,
                        record: Record<*>
864
865
866
                    ): Long {
                        return if(answerMessage.responseCode == DnsMessage.RESPONSE_CODE.NX_DOMAIN) nxDomainCacheTime else cacheTime
                    }
Daniel Wolf's avatar
Daniel Wolf committed
867

868
                    override suspend fun getTtl(question: Question, record: Record<*>): Long = cacheTime
Daniel Wolf's avatar
Daniel Wolf committed
869