DnsVpnService.kt 77 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
import android.app.Service
6
import android.content.*
Daniel Wolf's avatar
Daniel Wolf committed
7
import android.content.pm.PackageManager
8
9
import android.net.*
import android.os.*
Daniel Wolf's avatar
Daniel Wolf committed
10
import android.system.OsConstants
11
import android.util.Base64
Daniel Wolf's avatar
Daniel Wolf committed
12
13
import androidx.core.app.NotificationCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
14
import com.frostnerd.dnstunnelproxy.*
Daniel Wolf's avatar
Daniel Wolf committed
15
import com.frostnerd.encrypteddnstunnelproxy.HttpsDnsServerInformation
Daniel Wolf's avatar
Daniel Wolf committed
16
import com.frostnerd.encrypteddnstunnelproxy.ServerConfiguration
Daniel Wolf's avatar
Daniel Wolf committed
17
import com.frostnerd.encrypteddnstunnelproxy.quic.QuicUpstreamAddress
Daniel Wolf's avatar
Daniel Wolf committed
18
import com.frostnerd.encrypteddnstunnelproxy.tls.TLSUpstreamAddress
19
import com.frostnerd.general.CombinedIterator
20
import com.frostnerd.general.service.isServiceRunning
21
import com.frostnerd.preferenceskt.typedpreferences.TypedPreferences
22
import com.frostnerd.smokescreen.*
23
import com.frostnerd.smokescreen.R
24
import com.frostnerd.smokescreen.activity.BackgroundVpnConfigureActivity
25
26
import com.frostnerd.smokescreen.activity.PinActivity
import com.frostnerd.smokescreen.activity.PinType
27
28
import com.frostnerd.smokescreen.database.entities.CachedResponse
import com.frostnerd.smokescreen.database.getDatabase
Daniel Wolf's avatar
Daniel Wolf committed
29
import com.frostnerd.smokescreen.util.*
30
import com.frostnerd.smokescreen.util.preferences.VpnServiceState
Daniel Wolf's avatar
Daniel Wolf committed
31
import com.frostnerd.smokescreen.util.proxy.*
32
import com.frostnerd.vpntunnelproxy.Proxy
Daniel Wolf's avatar
Daniel Wolf committed
33
import com.frostnerd.vpntunnelproxy.RetryingVPNTunnelProxy
34
import com.frostnerd.vpntunnelproxy.TrafficStats
35
import com.frostnerd.vpntunnelproxy.VPNTunnelProxy
36
import kotlinx.coroutines.*
37
38
import org.minidns.dnsname.DnsName
import org.minidns.record.Record
39
40
import java.io.ByteArrayInputStream
import java.io.DataInputStream
Daniel Wolf's avatar
Daniel Wolf committed
41
import java.io.Serializable
42
import java.net.*
43
import java.util.*
44
import java.util.concurrent.TimeoutException
Daniel Wolf's avatar
Daniel Wolf committed
45
import java.util.logging.Level
46
import kotlin.coroutines.CoroutineContext
Daniel Wolf's avatar
Daniel Wolf committed
47
import kotlin.math.floor
48
import kotlin.math.min
49
import kotlin.math.pow
50
51
import com.frostnerd.smokescreen.BuildConfig
import com.frostnerd.smokescreen.util.proxy.QueryListener
Daniel Wolf's avatar
Daniel Wolf committed
52

Daniel Wolf's avatar
Daniel Wolf committed
53
54
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
Daniel Wolf's avatar
Daniel Wolf committed
55
 *
Daniel Wolf's avatar
Daniel Wolf committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 * 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
70
 */
71
class DnsVpnService : VpnService(), Runnable, CoroutineScope {
Daniel Wolf's avatar
Daniel Wolf committed
72
    private var fileDescriptor: ParcelFileDescriptor? = null
73
    private var dnsProxy: DnsPacketProxy? = null
Daniel Wolf's avatar
Daniel Wolf committed
74
    private var vpnProxy: RetryingVPNTunnelProxy? = null
75
    private var dnsServerProxy:DnsServerPacketProxy? = null
76
    private var ipTablesRedirector: IpTablesPacketRedirector? = null
Daniel Wolf's avatar
Daniel Wolf committed
77
    private var destroyed = false
78
    private var stopping = false
Daniel Wolf's avatar
Daniel Wolf committed
79
    private lateinit var notificationBuilder: NotificationCompat.Builder
80
81
    private lateinit var noConnectionNotificationBuilder: NotificationCompat.Builder
    private var noConnectionNotificationShown = false
82
    private lateinit var serverConfig: DnsServerConfiguration
83
    private var settingsSubscription: TypedPreferences<SharedPreferences>.OnPreferenceChangeListener? = null
84
85
    private var networkCallback: ConnectivityManager.NetworkCallback? = null
    private var pauseNotificationAction: NotificationCompat.Action? = null
86
    private var packageBypassAmount = 0
87
88
    private var connectedToANetwork: Boolean? = null
    private var lastScreenOff: Long? = null
89
    private var screenStateReceiver: BroadcastReceiver? = null
90
    private var dnsRuleRefreshReceiver:BroadcastReceiver? = null
91
    private var simpleNotification = getPreferences().simpleNotification
92
    private var lastVPNStopTime:Long? = null
Daniel Wolf's avatar
Daniel Wolf committed
93
    private var queryCount = 0
94
95
    private var dnsCache:SimpleDnsCache? = null
    private var localResolver:LocalResolver? = null
96
    private var runInNonVpnMode:Boolean = getPreferences().runWithoutVpn
97
    private var connectionWatchDog:ConnectionWatchdog? = null
98
    private var vpnWatchdog:VpnWatchDog? = null
99
    private var watchdogDisabledForSession = false
100
    private val coroutineSupervisor = SupervisorJob()
Daniel Wolf's avatar
Daniel Wolf committed
101
    @Suppress("EXPERIMENTAL_API_USAGE")
102
103
104
    private val addressResolveScope:CoroutineScope by lazy {
        CoroutineScope(newSingleThreadContext("service-resolve-retry"))
    }
105
    override val coroutineContext: CoroutineContext = coroutineSupervisor
106
107
108
109
110

    /*
        URLs passed to the Service, which haven't been retrieved from the settings.
        Null if the current servers are from the settings
     */
111
    private var userServerConfig: DnsServerInformation<*>? = null
Daniel Wolf's avatar
Daniel Wolf committed
112
113
114
115

    companion object {
        const val BROADCAST_VPN_ACTIVE = BuildConfig.APPLICATION_ID + ".VPN_ACTIVE"
        const val BROADCAST_VPN_INACTIVE = BuildConfig.APPLICATION_ID + ".VPN_INACTIVE"
116
117
        const val BROADCAST_VPN_PAUSED = BuildConfig.APPLICATION_ID + ".VPN_PAUSED"
        const val BROADCAST_VPN_RESUMED = BuildConfig.APPLICATION_ID + ".VPN_RESUME"
118
        const val BROADCAST_DNSRULES_REFRESHED = BuildConfig.APPLICATION_ID + ".DNSRULE_REFRESH"
119

120
121
        var currentTrafficStats: TrafficStats? = null
            private set
122
123
        var paused:Boolean = false
            private set
Daniel Wolf's avatar
Daniel Wolf committed
124

125
        fun startVpn(context: Context, serverInfo: DnsServerInformation<*>? = null) {
126
            val intent = Intent(context, DnsVpnService::class.java)
127
128
129
            if (serverInfo != null) {
                BackgroundVpnConfigureActivity.writeServerInfoToIntent(serverInfo, intent)
            }
130
131
132
            context.startForegroundServiceCompat(intent)
        }

133
        fun restartVpn(context: Context, fetchServersFromSettings: Boolean) {
134
            if (context.isServiceRunning(DnsVpnService::class.java)) {
135
136
137
138
                val bundle = Bundle()
                bundle.putBoolean("fetch_servers", fetchServersFromSettings)
                sendCommand(context, Command.RESTART, bundle)
            } else startVpn(context)
139
140
        }

141
142
143
144
145
146
        fun invalidateDNSCache(context: Context) {
            if(context.isServiceRunning(DnsVpnService::class.java)) {
                sendCommand(context, Command.INVALIDATE_DNS_CACHE)
            }
        }

147
148
        fun restartVpn(context: Context, serverInfo: DnsServerInformation<*>?) {
            if (context.isServiceRunning(DnsVpnService::class.java)) {
149
                val bundle = Bundle()
150
                if (serverInfo != null) {
151
                    BackgroundVpnConfigureActivity.writeServerInfoToIntent(serverInfo, bundle)
152
153
                    bundle.putBoolean("fetch_servers", true)
                }
154
                sendCommand(context, Command.RESTART, bundle)
155
            } else startVpn(context, serverInfo)
156
157
158
159
160
161
        }

        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
162
        }
163
164
165
166
167
168

        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
169
170
    }

171
172
173
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LanguageContextWrapper.attachFromSettings(this, newBase))
    }
Daniel Wolf's avatar
Daniel Wolf committed
174
175
176

    override fun onCreate() {
        super.onCreate()
177
        if (getPreferences().vpnServiceState == VpnServiceState.STARTED &&
178
            !getPreferences().ignoreServiceKilled &&
179
180
            getPreferences().vpnLaunchLastVersion == BuildConfig.VERSION_CODE
        ) { // The app didn't stop properly
181
182
183
184
            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M|| !(getSystemService(POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations(packageName)) {
                val ignoreIntent = Intent(this, DnsVpnService::class.java).putExtra(
                    "command",
                    Command.IGNORE_SERVICE_KILLED
185
                )
186
187
188
189
190
191
                val ignorePendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    PendingIntent.getForegroundService(
                        this@DnsVpnService,
                        RequestCodes.REQUEST_CODE_IGNORE_SERVICE_KILLED,
                        ignoreIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT
192
                    )
193
194
195
196
197
198
                } else {
                    PendingIntent.getService(
                        this@DnsVpnService,
                        RequestCodes.REQUEST_CODE_IGNORE_SERVICE_KILLED,
                        ignoreIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT
199
200
                    )
                }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
                NotificationCompat.Builder(this, Notifications.getDefaultNotificationChannelId(this))
                    .apply {
                        setContentTitle(getString(R.string.notification_service_killed_title))
                        setStyle(NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_service_killed_message)))
                        setSmallIcon(R.drawable.ic_cloud_warn)
                        setAutoCancel(true)
                        setOngoing(false)
                        setContentIntent(
                            DeepActionState.BATTERY_OPTIMIZATION_DIALOG.pendingIntentTo(
                                this@DnsVpnService
                            )
                        )
                        addAction(
                            R.drawable.ic_eye,
                            getString(R.string.notification_service_killed_ignore),
                            ignorePendingIntent
                        )
                    }.build().also {
                        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(
                            Notifications.ID_SERVICE_KILLED,
                            it
                        )
                    }
            }
225
        }
226
        getPreferences().vpnServiceState = VpnServiceState.STARTED
227
        getPreferences().vpnLaunchLastVersion = BuildConfig.VERSION_CODE
228
        Thread.setDefaultUncaughtExceptionHandler { t, e ->
229
            log("Encountered an uncaught exception.")
230
231
232
            destroy()
            stopForeground(true)
            stopSelf()
233
234

            (application as SmokeScreen).apply {
Daniel Wolf's avatar
Daniel Wolf committed
235
                (dnsProxy?.queryListener as QueryListener?)?.apply {
236
237
238
239
240
241
242
243
244
245
                    if (lastDnsResponse != null) {
                        customUncaughtExceptionHandler.addExtra(
                            "dns_packet",
                            lastDnsResponse.toString()
                        )
                        customUncaughtExceptionHandler.addExtra(
                            "dns_packet_bytes",
                            lastDnsResponse!!.toArray().joinToString(separator = "") {
                                it.toInt().toUByte().toString(16)
                            })
246
247
248
                    }
                }
            }.customUncaughtExceptionHandler.uncaughtException(t, e)
249
        }
250
        log("Service onCreate()")
251
        createNotification()
252
        hideMultipleUserNotification()
253
254
        if(isPrivateDnsActive) {
            showPrivateDnsNotification()
255
256
            stopForeground(true)
            destroy()
257
258
259
260
261
            stopSelf()
        } else {
            updateServiceTile()
            subscribeToSettings()
            addNetworkChangeListener()
262
            hidePrivateDnsNotification()
263
264
265
266
267
268
269
270
            screenStateReceiver =
                registerReceiver(listOf(Intent.ACTION_SCREEN_OFF, Intent.ACTION_SCREEN_ON)) {
                    if (it?.action == Intent.ACTION_SCREEN_OFF) {
                        lastScreenOff = System.currentTimeMillis()
                    } else {
                        if (lastScreenOff != null && System.currentTimeMillis() - lastScreenOff!! >= 60000) {
                            if (fileDescriptor != null && getPreferences().restartVpnOnNetworkChange) recreateVpn(false, null)
                        }
271
                    }
272
                }
273
274
275
276
277
            dnsRuleRefreshReceiver = registerLocalReceiver(listOf(BROADCAST_DNSRULES_REFRESHED)) {
                vpnProxy?.apply {
                    ((packetProxy as DnsPacketProxy).localResolver)?.apply {
                        (this as DnsRuleResolver).refreshRuleCount()
                    }
278
279
                }
            }
280
281
            clearPreviousIptablesRedirect(true)
            log("Service created.")
282
        }
283
    }
284

285
    private fun addNetworkChangeListener() {
286
        if(runInNonVpnMode) return
287
288
        val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        networkCallback = object : ConnectivityManager.NetworkCallback() {
Daniel Wolf's avatar
Daniel Wolf committed
289
            override fun onLost(network: Network) {
290
                super.onLost(network)
Daniel Wolf's avatar
Daniel Wolf committed
291
292
293
                val activeNetwork = mgr.activeNetworkInfo
                val networkInfo = mgr.getNetworkInfo(network)
                log("Network lost: $network, info: $networkInfo, current active network: $activeNetwork")
294
295
296
                handleChange()
            }

Daniel Wolf's avatar
Daniel Wolf committed
297
            override fun onAvailable(network: Network) {
298
                super.onAvailable(network)
Daniel Wolf's avatar
Daniel Wolf committed
299
300
301
                val activeNetwork = mgr.activeNetworkInfo
                val networkInfo = mgr.getNetworkInfo(network)
                log("Network became available: $network, info: $networkInfo, current active network: $activeNetwork")
302
303
304
305
                handleChange()
            }

            private fun handleChange() {
306
                if (this@DnsVpnService::serverConfig.isInitialized) serverConfig.forEachAddress { _, upstreamAddress ->
307
                    upstreamAddress.addressCreator.reset()
308
                    upstreamAddress.addressCreator.resetListeners()
309
                    resolveAllServerAddresses()
310
                }
311
                if (fileDescriptor != null && getPreferences().restartVpnOnNetworkChange) recreateVpn(false, null)
312
            }
313
314
315
316
317
318
319
320
321
322

            override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
                super.onLinkPropertiesChanged(network, linkProperties)
                if(isPrivateDnsActive) {
                    showPrivateDnsNotification()
                    destroy()
                    stopForeground(true)
                    stopSelf()
                }
            }
323
324
325
        }
        val builder = NetworkRequest.Builder()
        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
Daniel Wolf's avatar
Daniel Wolf committed
326
        mgr.registerNetworkCallback(builder.build(), networkCallback!!)
327
328
    }

329
330
331
332
333
334
335
336
337
338
339
340
    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",
341
            "user_bypass_packages",
342
            "dnscache_keepacrosslaunches",
343
            "bypass_searchdomains",
344
            "user_bypass_blacklist",
Daniel Wolf's avatar
Daniel Wolf committed
345
            "log_dns_queries",
346
            "show_notification_on_lockscreen",
347
            "hide_notification_icon",
348
            "pause_on_captive_portal",
349
            "allow_ipv6_traffic",
Daniel Wolf's avatar
Daniel Wolf committed
350
            "allow_ipv4_traffic",
351
352
            "dns_server_config",
            "notification_allow_stop",
353
            "notification_allow_pause",
354
            "dns_rules_enabled",
355
            "simple_notification",
356
            "pin",
357
            "nonvpn_use_iptables",
358
            "nonvpn_iptables_disable_ipv6",
359
360
            "connection_watchdog",
            "nonvpn_use_lanip"
361
        )
362
363
364
365
366
        settingsSubscription = getPreferences().listenForChanges(
            relevantSettings,
            getPreferences().preferenceChangeListener { changes ->
                log("The Preference(s) ${changes.keys} have changed, restarting the VPN.")
                log("Detailed changes: $changes")
367
                if ("hide_notification_icon" in changes || "hide_notification_icon" in changes || "simple_notification" in changes || "pin" in changes) {
368
                    simpleNotification = getPreferences().simpleNotification
369
370
371
372
373
374
375
376
377
378
379
                    log("Recreating the notification because of the change in preferences")
                    createNotification()
                    setNotificationText()
                } else if ("notification_allow_pause" in changes || "notification_allow_stop" in changes) {
                    log("Recreating the notification because of the change in preferences")
                    createNotification()
                    setNotificationText()
                }
                val reload = "dns_server_config" in changes
                recreateVpn(reload, null)
            })
380
381
382
        log("Subscribed.")
    }

383
384
    private fun createNotification() {
        log("Creating notification")
385
386
387
388
389
        notificationBuilder = NotificationCompat.Builder(
            this,
            Notifications.servicePersistentNotificationChannel(this)
        )
        if (getPreferences().hideNotificationIcon)
390
            notificationBuilder.priority = NotificationCompat.PRIORITY_MIN
391
        if (!getPreferences().showNotificationOnLockscreen)
392
            notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_SECRET)
Daniel Wolf's avatar
Daniel Wolf committed
393
        notificationBuilder.setContentTitle(getString(R.string.app_name))
394
        notificationBuilder.setSmallIcon(R.drawable.ic_mainnotification)
395
396
        notificationBuilder.setOngoing(true)
        notificationBuilder.setAutoCancel(false)
397
        notificationBuilder.setSound(null)
Daniel Wolf's avatar
Daniel Wolf committed
398
        notificationBuilder.setOnlyAlertOnce(true)
399
        notificationBuilder.setUsesChronometer(!getPreferences().simpleNotification)
400
401
        notificationBuilder.setContentIntent(
            PendingIntent.getActivity(
402
                this, RequestCodes.MAIN_NOTIFICATION,
403
                PinActivity.openAppIntent(this), PendingIntent.FLAG_UPDATE_CURRENT
404
405
            )
        )
406
        if (getPreferences().allowStopInNotification) {
407
            val stopPendingIntent =
408
409
                PendingIntent.getService(
                    this,
410
                    RequestCodes.MAIN_NOTIFICATION_STOP,
411
412
413
414
415
416
417
418
                    commandIntent(this, Command.STOP),
                    PendingIntent.FLAG_CANCEL_CURRENT
                )
            val stopAction = NotificationCompat.Action(
                R.drawable.ic_stop,
                getString(R.string.all_stop),
                stopPendingIntent
            )
419
420
            notificationBuilder.addAction(stopAction)
        }
421
        if (getPreferences().allowPauseInNotification) {
422
            val pausePendingIntent =
423
424
                PendingIntent.getService(
                    this,
425
                    RequestCodes.MAIN_NOTIFICATION_PAUSE,
426
427
428
429
430
431
432
433
                    commandIntent(this, Command.PAUSE_RESUME),
                    PendingIntent.FLAG_CANCEL_CURRENT
                )
            pauseNotificationAction = NotificationCompat.Action(
                R.drawable.ic_stat_pause,
                getString(R.string.all_pause),
                pausePendingIntent
            )
434
435
            notificationBuilder.addAction(pauseNotificationAction)
        }
Daniel Wolf's avatar
Daniel Wolf committed
436
        updateNotification()
437
        log("Notification created and posted.")
Daniel Wolf's avatar
Daniel Wolf committed
438
439
    }

Daniel Wolf's avatar
Daniel Wolf committed
440
    private fun updateNotification() {
441
        if(!simpleNotification) {
Daniel Wolf's avatar
Daniel Wolf committed
442
            notificationBuilder.setSubText(
443
444
                getString(
                    R.string.notification_main_subtext,
Daniel Wolf's avatar
Daniel Wolf committed
445
                    queryCount
446
                )
447
            )
448
        }
Daniel Wolf's avatar
Daniel Wolf committed
449
        startForeground(Notifications.ID_VPN_SERVICE, notificationBuilder.build())
Daniel Wolf's avatar
Daniel Wolf committed
450
451
    }

452
    private fun showNoConnectionNotification() {
453
        if (!getPreferences().enableConnectionWatchDog) return
Daniel Wolf's avatar
Daniel Wolf committed
454
        log("Showing no connection notification. Is already shown: $noConnectionNotificationShown")
455
456
457
458
459
        if (!this::noConnectionNotificationBuilder.isInitialized) {
            noConnectionNotificationBuilder = NotificationCompat.Builder(
                this,
                Notifications.noConnectionNotificationChannelId(this)
            )
460
            noConnectionNotificationBuilder.priority = NotificationCompat.PRIORITY_HIGH
461
            noConnectionNotificationBuilder.setOngoing(false)
462
            noConnectionNotificationBuilder.setSmallIcon(R.drawable.ic_cloud_strikethrough)
463
464
            noConnectionNotificationBuilder.setContentTitle(getString(R.string.notification_noconnection_title))
            noConnectionNotificationBuilder.setContentText(getString(R.string.notification_noconnection_text))
465
466
467
468
469
            noConnectionNotificationBuilder.setContentIntent(PendingIntent.getActivity(
                    this, RequestCodes.NO_CONNECTION_NOTIFICATION,
                    PinActivity.openAppIntent(this), PendingIntent.FLAG_UPDATE_CURRENT
                )
            )
470
            noConnectionNotificationBuilder.setStyle(
471
                NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_noconnection_text))
472
            )
473
        }
474
        noConnectionNotificationBuilder.setWhen(System.currentTimeMillis())
475
476
477
478
479
480
481
482
483
484
485
        if (!noConnectionNotificationShown) {
            try {
                (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(
                    Notifications.ID_NO_CONNECTION,
                    noConnectionNotificationBuilder.build()
                )
            } catch (ex:java.lang.NullPointerException) {
                // AIDL bug causing NPE in  android.app.ApplicationPackageManager.getUserIfProfile(ApplicationPackageManager.java:2813)
                // Ignore it.
            }
        }
486
487
488
        noConnectionNotificationShown = true
    }

489
    private fun showDnsServerModeNotification(address:String, port:Int, originalPort:Int, iptablesMode: IpTablesPacketRedirector.IpTablesMode) {
490
        val portDiffersFromConfig = port != originalPort
491
        val isNotificationImportant = portDiffersFromConfig || iptablesMode == IpTablesPacketRedirector.IpTablesMode.FAILED || iptablesMode == IpTablesPacketRedirector.IpTablesMode.SUCCEEDED_NO_IPV6
492
493
494
        val channel = if(isNotificationImportant) Notifications.getHighPriorityChannelId(this) else Notifications.getDefaultNotificationChannelId(this)
        val icon = if(isNotificationImportant) R.drawable.ic_cloud_warn else R.drawable.ic_mainnotification
        var contentText = when (iptablesMode) {
495
496
497
498
            IpTablesPacketRedirector.IpTablesMode.DISABLED -> getString(R.string.notification_dnsserver_message, address, port)
            IpTablesPacketRedirector.IpTablesMode.FAILED -> getString(R.string.notification_dnsserver_message_iptables_failed, address, port)
            IpTablesPacketRedirector.IpTablesMode.SUCCEEDED_NO_IPV6 -> getString(R.string.notification_dnsserver_message_iptables_active_no_ipv6, address, port)
            else -> getString(R.string.notification_dnsserver_message_iptables_active, address, port)
499
        }
500
501
502
503
504
505
        if(portDiffersFromConfig) contentText += "\n" + getString(R.string.notification_dnsserver_portdiffers, originalPort)
        NotificationCompat.Builder(this, channel)
            .setContentTitle(getString(R.string.notification_dnsserver_title))
            .setSmallIcon(icon)
            .setContentText(contentText)
            .setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
506
            .setOngoing(false)
507
508
509
510
511
512
513
            .setAutoCancel(true)
            .setContentIntent(DeepActionState.DNSSERVERMODE_SETTINGS.pendingIntentTo(this))
            .build().also {
                (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(Notifications.ID_DNSSERVER_MODE, it)
            }
    }

514
    private fun hideNoConnectionNotification() {
515
516
517
        if (noConnectionNotificationShown) (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(
            Notifications.ID_NO_CONNECTION
        )
518
519
520
        noConnectionNotificationShown = false
    }

521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
    private fun showPrivateDnsNotification() {
        val notificationBuilder = NotificationCompat.Builder(
            this,
            Notifications.getHighPriorityChannelId(this)
        )
        notificationBuilder.priority = NotificationCompat.PRIORITY_HIGH
        notificationBuilder.setOngoing(false)
        notificationBuilder.setSmallIcon(R.drawable.ic_cloud_strikethrough)
        notificationBuilder.setContentTitle(getString(R.string.notification_privatednswarning_title))
        notificationBuilder.setContentText(getString(R.string.notification_privatednswarning_text))
        notificationBuilder.setStyle(
            NotificationCompat.BigTextStyle(
                notificationBuilder
            ).bigText(getString(R.string.notification_privatednswarning_text))
        )
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(Notifications.ID_PRIVATEDNS_WARNING, notificationBuilder.build())
    }

539
540
541
542
    private fun hidePrivateDnsNotification() {
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Notifications.ID_PRIVATEDNS_WARNING)
    }

543
544
545
546
547
548
549
550
551
552
553
    private fun showMultipleUserNotification() {
        val notificationBuilder = NotificationCompat.Builder(
            this,
            Notifications.getHighPriorityChannelId(this)
        )
        notificationBuilder.priority = NotificationCompat.PRIORITY_HIGH
        notificationBuilder.setOngoing(false)
        notificationBuilder.setSmallIcon(R.drawable.ic_cloud_strikethrough)
        notificationBuilder.setContentTitle(getString(R.string.notification_multipleuserswarning_title))
        notificationBuilder.setContentText(getString(R.string.notification_multipleuserswarning_text))
        notificationBuilder.setStyle(
554
            NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_multipleuserswarning_text))
555
556
557
558
559
560
561
562
        )
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(Notifications.ID_MULTIPLEUSERS_WARNING, notificationBuilder.build())
    }

    private fun hideMultipleUserNotification() {
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Notifications.ID_MULTIPLEUSERS_WARNING)
    }

563
564
565
566
567
568
569
570
571
572
573
    private fun showBadConnectionNotification() {
        val builder = NotificationCompat.Builder(
            this,
            Notifications.getBadConnectionChannelId(this)
        )
        builder.priority = NotificationCompat.PRIORITY_DEFAULT
        builder.setOngoing(false)
        builder.setSmallIcon(R.drawable.ic_cloud_strikethrough)
        builder.setContentTitle(getString(R.string.notification_bad_connection_title))
        builder.setContentText(getString(R.string.notification_bad_connection_text))
        builder.setStyle(
574
            NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_bad_connection_text))
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
        )
        val ignoreIntent = Intent(this, DnsVpnService::class.java).putExtra(
            "command",
            Command.IGNORE_BAD_CONNECTION
        )
        val ignorePendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            PendingIntent.getForegroundService(
                this@DnsVpnService,
                RequestCodes.REQUEST_CODE_IGNORE_BAD_CONNECTION,
                ignoreIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
        } else {
            PendingIntent.getService(
                this@DnsVpnService,
                RequestCodes.REQUEST_CODE_IGNORE_BAD_CONNECTION,
                ignoreIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
        }

        builder.addAction(NotificationCompat.Action(null, getString(R.string.notification_service_killed_ignore), ignorePendingIntent))
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(Notifications.ID_BAD_SERVER_CONNECTION, builder.build())
    }

    private fun hideBadConnectionNotification() {
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Notifications.ID_BAD_SERVER_CONNECTION)
    }

604
605
606
607
608
609
610
611
612
613
614
    private fun showCronetErrorNotification() {
        val builder = NotificationCompat.Builder(
            this,
            Notifications.getHighPriorityChannelId(this)
        )
        builder.priority = NotificationCompat.PRIORITY_DEFAULT
        builder.setOngoing(false)
        builder.setSmallIcon(R.drawable.ic_cloud_strikethrough)
        builder.setContentTitle(getString(R.string.notification_cronet_failed_title))
        builder.setContentText(getString(R.string.notification_cronet_failed_text))
        builder.setStyle(
615
            NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_cronet_failed_text))
616
617
618
619
620
621
622
623
        )
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(Notifications.ID_CRONET_FAILED, builder.build())
    }

    private fun hideCronetErrorNotification() {
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Notifications.ID_CRONET_FAILED)
    }

Daniel Wolf's avatar
Daniel Wolf committed
624
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
625
        log("Service onStartCommand", intent = intent)
626
        runInNonVpnMode = getPreferences().runWithoutVpn
627
        if(stopping) {
628
629
630
            updateNotification()
            return START_NOT_STICKY
        }
Daniel Wolf's avatar
Daniel Wolf committed
631
        if (intent != null && intent.hasExtra("command")) {
632
            when (intent.getSerializableExtra("command") as Command) {
Daniel Wolf's avatar
Daniel Wolf committed
633
                Command.STOP -> {
634
                    log("Received STOP command.")
635
                    if (PinActivity.shouldValidatePin(this, intent)) {
636
637
638
639
640
641
642
643
                        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
644
                }
645
                Command.RESTART -> {
646
                    log("Received RESTART command, restarting vpn.")
647
                    recreateVpn(intent.getBooleanExtra("fetch_servers", false), intent)
648
                    setNotificationText()
649
                }
650
                Command.PAUSE_RESUME -> {
651
                    if (vpnProxy != null) {
652
                        log("Received PAUSE_RESUME command while app running, destroying vpn.")
653
654
655
                        destroy(false)
                        pauseNotificationAction?.title = getString(R.string.all_resume)
                        pauseNotificationAction?.icon = R.drawable.ic_stat_resume
656
                        notificationBuilder.setSmallIcon(R.drawable.ic_notification_paused)
657
658
659
                        LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(
                            BROADCAST_VPN_PAUSED))
                        paused = true
660
                    } else {
661
                        log("Received PAUSE_RESUME command while app paused, restarting vpn.")
662
663
664
                        recreateVpn(false, null)
                        pauseNotificationAction?.title = getString(R.string.all_pause)
                        pauseNotificationAction?.icon = R.drawable.ic_stat_pause
665
                        notificationBuilder.setSmallIcon(R.drawable.ic_mainnotification)
666
667
668
                        LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(
                            BROADCAST_VPN_RESUMED))
                        paused = false
669
670
671
                    }
                    updateNotification()
                }
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
                Command.RESUME -> {
                    if(vpnProxy == null) {
                        log("Received RESUME command while app not running, creating vpn.")
                        recreateVpn(false, null)
                        pauseNotificationAction?.title = getString(R.string.all_pause)
                        pauseNotificationAction?.icon = R.drawable.ic_stat_pause
                        notificationBuilder.setSmallIcon(R.drawable.ic_mainnotification)
                        LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(
                            BROADCAST_VPN_RESUMED))
                        paused = false
                    }
                }
                Command.PAUSE -> {
                    if(vpnProxy != null) {
                        log("Received PAUSE command while app running, destroying vpn.")
                        destroy(false)
                        pauseNotificationAction?.title = getString(R.string.all_resume)
                        pauseNotificationAction?.icon = R.drawable.ic_stat_resume
                        notificationBuilder.setSmallIcon(R.drawable.ic_notification_paused)
                        LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(
                            BROADCAST_VPN_PAUSED))
                        paused = true
                    }
                }
696
697
698
699
                Command.IGNORE_SERVICE_KILLED -> {
                    getPreferences().ignoreServiceKilled = true
                    updateNotification()
                }
700
                Command.INVALIDATE_DNS_CACHE -> {
701
                    dnsProxy?.dnsCache?.clear()
702
703
                    restartVpn(this, false)
                }
704
705
706
707
708
                Command.IGNORE_BAD_CONNECTION -> {
                    watchdogDisabledForSession = true
                    connectionWatchDog?.stop()
                    hideBadConnectionNotification()
                }
Daniel Wolf's avatar
Daniel Wolf committed
709
            }
710
        } else {
711
            log("No command passed, fetching servers and establishing connection if needed")
712
            log("Checking whether The VPN is prepared")
713
            if (!runInNonVpnMode && prepare(this) != null) {
714
                log("The VPN isn't prepared, stopping self and starting Background configure")
Daniel Wolf's avatar
Daniel Wolf committed
715
                updateNotification()
716
717
718
                stopForeground(true)
                destroy()
                stopSelf()
719
720
                BackgroundVpnConfigureActivity.prepareVpn(
                    this,
721
                    userServerConfig
722
                )
723
724
725
            } else {
                log("The VPN is prepared, proceeding.")
                if (!destroyed) {
Daniel Wolf's avatar
Daniel Wolf committed
726
                    if (!this::serverConfig.isInitialized) {
727
728
729
                        setServerConfiguration(intent)
                        setNotificationText()
                    }
Daniel Wolf's avatar
Daniel Wolf committed
730
                    updateNotification()
731
732
                    establishVpn()
                }
733
            }
734
735
736
737
        }
        return if (destroyed) Service.START_NOT_STICKY else Service.START_STICKY
    }

738
739
740
741
742
743
744
745
746
    override fun onTrimMemory(level: Int) {
        if(level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND ||
                level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL ||
                level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
            dnsCache?.clear()
            localResolver?.cleanup()
        }
    }

747
    private fun setServerConfiguration(intent: Intent?) {
748
        log("Updating server configuration..")
749
        userServerConfig = BackgroundVpnConfigureActivity.readServerInfoFromIntent(intent)
Daniel Wolf's avatar
Daniel Wolf committed
750
        serverConfig = getServerConfig()
751
752
753
754
755
        resolveAllServerAddresses()
        log("Server configuration updated to $serverConfig")
    }

    private fun resolveAllServerAddresses() {
756
757
758
        val initialBackoffTime = 200
        var tries = 0.toDouble()
        var totalTries = 0
759
        addressResolveScope.cancel()
760
        serverConfig.forEachAddress { _, address ->
761
            val listener = address.addressCreator.whenResolveFinished { resolveException, resolveResult ->
762
763
764
765
766
                if(resolveException != null) {
                    showNoConnectionNotification()
                    if (resolveException is TimeoutException || resolveException is UnknownHostException) {
                        log("Address resolve failed: $resolveException. Total tries $totalTries/70")
                        if (totalTries <= 70) {
767
                            addressResolveScope.launch {
768
769
770
771
772
                                val exponentialBackoff =
                                    (initialBackoffTime * 2.toDouble().pow(tries++)).toLong()
                                delay(min(45000L, exponentialBackoff))
                                totalTries++
                                if (tries >= 9) tries = 0.toDouble()
773
                                if(isActive) address.addressCreator.resolveOrGetResultOrNull(true)
774
                            }
775
776
777
778
779
780
781
782
783
                            true
                        } else false
                    } else {
                        log("Address resolve failed: $resolveException. Not retrying.")
                        false
                    }
                } else if(resolveResult != null){
                    hideNoConnectionNotification()
                    false
784
785
786
                } else {
                    false
                }
787
            }
788
            if (!address.addressCreator.isCurrentlyResolving()) {
789
790
791
792
793
                addressResolveScope.launch {
                    if(!address.addressCreator.isCurrentlyResolving() && !address.addressCreator.resolveOrGetResultOrNull(
                            true
                        ).isNullOrEmpty()) address.addressCreator.removeListener(listener)
                }
794
            }
795
        }
796
797
798
    }

    private fun setNotificationText() {
799
800
801
802
803
804
805
806
807
        if (this::serverConfig.isInitialized) {
            val primaryServer: String
            val secondaryServer: String?
            if (serverConfig.httpsConfiguration != null) {
                notificationBuilder.setContentTitle(getString(R.string.notification_main_title_https))
                primaryServer = serverConfig.httpsConfiguration!![0].urlCreator.address.getUrl(true)
                secondaryServer =
                    serverConfig.httpsConfiguration!!.getOrNull(1)
                        ?.urlCreator?.address?.getUrl(true)
Daniel Wolf's avatar
Daniel Wolf committed
808
            } else if(serverConfig.tlsConfiguration != null){
809
810
811
                notificationBuilder.setContentTitle(getString(R.string.notification_main_title_tls))
                primaryServer = serverConfig.tlsConfiguration!![0].formatToString()
                secondaryServer = serverConfig.tlsConfiguration!!.getOrNull(1)?.formatToString()
Daniel Wolf's avatar
Daniel Wolf committed
812
813
814
815
816
            } else if(serverConfig.quicConfiguration != null) {
                notificationBuilder.setContentTitle(getString(R.string.notification_main_title_quic))
                primaryServer = serverConfig.quicConfiguration!![0].getUrl(true)
                secondaryServer = serverConfig.quicConfiguration!!.getOrNull(1)?.getUrl(true)
            } else error("Unknown type")
817
818
819
820
821
822
823
824
825
826
            val text =
                when {
                    getPreferences().simpleNotification -> getString(
                        R.string.notification_simple_text,
                        serverConfig.name
                    )
                    secondaryServer != null -> getString(
                        if (getPreferences().isBypassBlacklist) R.string.notification_main_text_with_secondary else R.string.notification_main_text_with_secondary_whitelist,
                        primaryServer,
                        secondaryServer,
827
                        packageBypassAmount
828
829
830
831
                    )
                    else -> getString(
                        if (getPreferences().isBypassBlacklist) R.string.notification_main_text else R.string.notification_main_text_whitelist,
                        primaryServer,
832
                        packageBypassAmount
833
834
835
836
837
838
839
                    )
                }
            if (simpleNotification) {
                notificationBuilder.setStyle(null)
                notificationBuilder.setContentText(text)
            } else {
                notificationBuilder.setStyle(
840
                    NotificationCompat.BigTextStyle().bigText(
841
842
                        text
                    )
843
                )
844
            }
845
        }
Daniel Wolf's avatar
Daniel Wolf committed
846
847
    }

848
    private fun establishVpn() {
849
        log("Establishing VPN")
850
        if (vpnProxy == null) {
851
            destroyed = false
852
            val runVpn = {
853
854
855
856
857
858
859
860
861
862
863
864
865
                try {
                    if(!runInNonVpnMode) fileDescriptor = createBuilder().establish()
                    run()
                    setNotificationText()
                    updateNotification()
                } catch (ex:SecurityException) {
                    if(ex.message?.contains("INTERACT_ACROSS_USERS", true) == true) {
                        showMultipleUserNotification()
                        stopForeground(true)
                        destroy()
                        stopSelf()
                    }
                }
866
867
868
            }
            val timeDiff = lastVPNStopTime?.let { System.currentTimeMillis() - it }
            if(timeDiff != null && timeDiff < 750) {
869
                launch {
870
871
872
873
874
875
876
                    delay(750-timeDiff)
                    if(isActive) runVpn()
                }
            } else {
                runVpn()
            }

877
        } else log("Connection already running, no need to establish.")
878
879
    }

880
    private fun recreateVpn(reloadServerConfiguration: Boolean, intent: Intent?) {
881
        log("Recreating the VPN (destroying & establishing)")
882
        destroy(false)
883
        if (runInNonVpnMode || prepare(this) == null) {
884
885
            log("VpnService is still prepared, establishing VPN.")
            destroyed = false
Daniel Wolf's avatar
Daniel Wolf committed
886
            if (reloadServerConfiguration || !this::serverConfig.isInitialized) {
887
888
                log("Re-fetching the servers (from intent or settings)")
                setServerConfiguration(intent)
889
            } else serverConfig.forEachAddress { _, address ->
890
891
                address.addressCreator.reset()
                resolveAllServerAddresses()
892
893
            }
            establishVpn()
894
            setNotificationText()
Daniel Wolf's avatar
Daniel Wolf committed
895
            updateNotification()
896
897
898
899
        } else {
            log("VpnService isn't prepared, launching BackgroundVpnConfigureActivity.")
            BackgroundVpnConfigureActivity.prepareVpn(
                this,
900
                userServerConfig
901
902
903
904
905
            )
            log("BackgroundVpnConfigureActivity launched, stopping service.")
            stopForeground(true)
            stopSelf()
        }
906
907
    }

908
    private fun destroy(isStoppingCompletely: Boolean = true) {
909
        log("Destroying the VPN")
910
        if (isStoppingCompletely || connectedToANetwork == true) hideNoConnectionNotification()
911
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Notifications.ID_DNSSERVER_MODE)
Daniel Wolf's avatar
Daniel Wolf committed
912
913
        if (!destroyed) {
            vpnProxy?.stop()
914
            dnsServerProxy?.stop()
915
            connectionWatchDog?.stop()
916
            vpnWatchdog?.stop()
917
918
919
920
            if(ipTablesRedirector == null || ipTablesRedirector?.endForward() == IpTablesPacketRedirector.IpTablesMode.SUCCEEDED) {
                getPreferences().lastIptablesRedirectAddress = null
                getPreferences().lastIptablesRedirectAddressIPv6 = null
            }
Daniel Wolf's avatar
Daniel Wolf committed
921
            fileDescriptor?.close()
922
            addressResolveScope.cancel()
923
            lastVPNStopTime = System.currentTimeMillis()
924
925
926
            if (isStoppingCompletely) {
                if (networkCallback != null) {
                    (getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).unregisterNetworkCallback(
Daniel Wolf's avatar
Daniel Wolf committed
927
                        networkCallback!!
928
929
930
                    )
                    networkCallback = null
                }
931
                screenStateReceiver?.also { tryUnregisterReceiver(it) }
932
            }
933
934
            vpnProxy = null
            fileDescriptor = null
935
            destroyed = true