diff options
author | pacien | 2018-08-06 13:18:12 +0200 |
---|---|---|
committer | pacien | 2018-08-06 13:18:12 +0200 |
commit | 51c7b7cb443da7fa7dd1314881f309eea4aa7d10 (patch) | |
tree | 8a5e69f2368a16d9219632d25dd4e01faaa4fe49 /app/src/main | |
parent | aef4ce4fb1cde5fda83c02d85b06238a934ab93a (diff) | |
download | tincapp-51c7b7cb443da7fa7dd1314881f309eea4aa7d10.tar.gz |
Enable connection restoration (always-on VPN)
Diffstat (limited to 'app/src/main')
-rw-r--r-- | app/src/main/java/org/pacien/tincapp/intent/Actions.kt | 1 | ||||
-rw-r--r-- | app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt | 66 |
2 files changed, 45 insertions, 22 deletions
diff --git a/app/src/main/java/org/pacien/tincapp/intent/Actions.kt b/app/src/main/java/org/pacien/tincapp/intent/Actions.kt index f6fd12b..85cfd29 100644 --- a/app/src/main/java/org/pacien/tincapp/intent/Actions.kt +++ b/app/src/main/java/org/pacien/tincapp/intent/Actions.kt | |||
@@ -28,6 +28,7 @@ object Actions { | |||
28 | const val PREFIX = "${BuildConfig.APPLICATION_ID}.intent.action" | 28 | const val PREFIX = "${BuildConfig.APPLICATION_ID}.intent.action" |
29 | const val ACTION_CONNECT = "$PREFIX.CONNECT" | 29 | const val ACTION_CONNECT = "$PREFIX.CONNECT" |
30 | const val ACTION_DISCONNECT = "$PREFIX.DISCONNECT" | 30 | const val ACTION_DISCONNECT = "$PREFIX.DISCONNECT" |
31 | const val ACTION_SYSTEM_CONNECT = "android.net.VpnService" | ||
31 | const val EVENT_CONNECTED = "$PREFIX.CONNECTED" | 32 | const val EVENT_CONNECTED = "$PREFIX.CONNECTED" |
32 | const val EVENT_DISCONNECTED = "$PREFIX.DISCONNECTED" | 33 | const val EVENT_DISCONNECTED = "$PREFIX.DISCONNECTED" |
33 | const val EVENT_ABORTED = "$PREFIX.ABORTED" | 34 | const val EVENT_ABORTED = "$PREFIX.ABORTED" |
diff --git a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt index e499e84..884229d 100644 --- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt +++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt | |||
@@ -19,6 +19,7 @@ | |||
19 | package org.pacien.tincapp.service | 19 | package org.pacien.tincapp.service |
20 | 20 | ||
21 | import android.app.Service | 21 | import android.app.Service |
22 | import android.content.Context | ||
22 | import android.content.Intent | 23 | import android.content.Intent |
23 | import android.net.VpnService | 24 | import android.net.VpnService |
24 | import android.os.ParcelFileDescriptor | 25 | import android.os.ParcelFileDescriptor |
@@ -40,7 +41,6 @@ import org.pacien.tincapp.extensions.Java.defaultMessage | |||
40 | import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg | 41 | import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg |
41 | import org.pacien.tincapp.intent.Actions | 42 | import org.pacien.tincapp.intent.Actions |
42 | import org.pacien.tincapp.utils.TincKeyring | 43 | import org.pacien.tincapp.utils.TincKeyring |
43 | import org.slf4j.Logger | ||
44 | import org.slf4j.LoggerFactory | 44 | import org.slf4j.LoggerFactory |
45 | import java.io.FileNotFoundException | 45 | import java.io.FileNotFoundException |
46 | 46 | ||
@@ -48,12 +48,7 @@ import java.io.FileNotFoundException | |||
48 | * @author pacien | 48 | * @author pacien |
49 | */ | 49 | */ |
50 | class TincVpnService : VpnService() { | 50 | class TincVpnService : VpnService() { |
51 | private var logger: Logger? = null | 51 | private val log by lazy { LoggerFactory.getLogger(this.javaClass)!! } |
52 | |||
53 | override fun onCreate() { | ||
54 | super.onCreate() | ||
55 | logger = LoggerFactory.getLogger(this.javaClass) | ||
56 | } | ||
57 | 52 | ||
58 | override fun onDestroy() { | 53 | override fun onDestroy() { |
59 | stopVpn() | 54 | stopVpn() |
@@ -61,13 +56,15 @@ class TincVpnService : VpnService() { | |||
61 | } | 56 | } |
62 | 57 | ||
63 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { | 58 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { |
64 | logger?.info("Intent received: {}", intent.action) | 59 | log.info("Intent received: {}", intent.toString()) |
65 | 60 | ||
66 | when { | 61 | when { |
67 | intent.action == Actions.ACTION_CONNECT && intent.scheme == Actions.TINC_SCHEME -> | 62 | intent.action == Actions.ACTION_CONNECT && intent.scheme == Actions.TINC_SCHEME -> |
68 | startVpn(intent.data.schemeSpecificPart, intent.data.fragment) | 63 | startVpn(intent.data.schemeSpecificPart, intent.data.fragment) |
69 | intent.action == Actions.ACTION_DISCONNECT -> | 64 | intent.action == Actions.ACTION_DISCONNECT -> |
70 | stopVpn() | 65 | stopVpn() |
66 | intent.action == Actions.ACTION_SYSTEM_CONNECT -> | ||
67 | restorePreviousConnection() | ||
71 | else -> | 68 | else -> |
72 | throw IllegalArgumentException("Invalid intent action received.") | 69 | throw IllegalArgumentException("Invalid intent action received.") |
73 | } | 70 | } |
@@ -75,6 +72,17 @@ class TincVpnService : VpnService() { | |||
75 | return Service.START_NOT_STICKY | 72 | return Service.START_NOT_STICKY |
76 | } | 73 | } |
77 | 74 | ||
75 | private fun restorePreviousConnection() { | ||
76 | val netName = getCurrentNetName() | ||
77 | if (netName == null) { | ||
78 | log.info("No connection to restore.") | ||
79 | return | ||
80 | } | ||
81 | |||
82 | log.info("Restoring previous connection to \"$netName\".") | ||
83 | startVpn(netName, getPassphrase()) | ||
84 | } | ||
85 | |||
78 | private fun startVpn(netName: String, passphrase: String? = null): Unit = synchronized(this) { | 86 | private fun startVpn(netName: String, passphrase: String? = null): Unit = synchronized(this) { |
79 | if (netName.isBlank()) | 87 | if (netName.isBlank()) |
80 | return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") | 88 | return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") |
@@ -88,7 +96,7 @@ class TincVpnService : VpnService() { | |||
88 | if (!AppPaths.confDir(netName).exists()) | 96 | if (!AppPaths.confDir(netName).exists()) |
89 | return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") | 97 | return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") |
90 | 98 | ||
91 | logger?.info("Starting tinc daemon for network \"$netName\".") | 99 | log.info("Starting tinc daemon for network \"$netName\".") |
92 | if (isConnected()) stopVpn() | 100 | if (isConnected()) stopVpn() |
93 | 101 | ||
94 | val privateKeys = try { | 102 | val privateKeys = try { |
@@ -129,7 +137,7 @@ class TincVpnService : VpnService() { | |||
129 | } | 137 | } |
130 | 138 | ||
131 | val daemon = Tincd.start(netName, deviceFd.fd, privateKeys.first?.fd, privateKeys.second?.fd) | 139 | val daemon = Tincd.start(netName, deviceFd.fd, privateKeys.first?.fd, privateKeys.second?.fd) |
132 | setState(netName, interfaceCfg, deviceFd, daemon) | 140 | setState(netName, passphrase, interfaceCfg, deviceFd, daemon) |
133 | 141 | ||
134 | waitForDaemonStartup().whenComplete { _, exception -> | 142 | waitForDaemonStartup().whenComplete { _, exception -> |
135 | deviceFd.close() | 143 | deviceFd.close() |
@@ -139,28 +147,28 @@ class TincVpnService : VpnService() { | |||
139 | if (exception != null) { | 147 | if (exception != null) { |
140 | reportError(resources.getString(R.string.message_daemon_exited, exception.cause!!.defaultMessage()), exception) | 148 | reportError(resources.getString(R.string.message_daemon_exited, exception.cause!!.defaultMessage()), exception) |
141 | } else { | 149 | } else { |
142 | logger?.info("tinc daemon started.") | 150 | log.info("tinc daemon started.") |
143 | broadcastEvent(Actions.EVENT_CONNECTED) | 151 | broadcastEvent(Actions.EVENT_CONNECTED) |
144 | } | 152 | } |
145 | } | 153 | } |
146 | } | 154 | } |
147 | 155 | ||
148 | private fun stopVpn(): Unit = synchronized(this) { | 156 | private fun stopVpn(): Unit = synchronized(this) { |
149 | logger?.info("Stopping any running tinc daemon.") | 157 | log.info("Stopping any running tinc daemon.") |
150 | netName?.let { | 158 | getCurrentNetName()?.let { |
151 | Tinc.stop(it).thenRun { | 159 | Tinc.stop(it).thenRun { |
152 | logger?.info("All tinc daemons stopped.") | 160 | log.info("All tinc daemons stopped.") |
153 | broadcastEvent(Actions.EVENT_DISCONNECTED) | 161 | broadcastEvent(Actions.EVENT_DISCONNECTED) |
154 | setState(null, null, null, null) | 162 | setState(null, null, null, null, null) |
155 | } | 163 | } |
156 | } | 164 | } |
157 | } | 165 | } |
158 | 166 | ||
159 | private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { | 167 | private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { |
160 | if (e != null) | 168 | if (e != null) |
161 | logger?.error(msg, e) | 169 | log.error(msg, e) |
162 | else | 170 | else |
163 | logger?.error(msg) | 171 | log.error(msg) |
164 | 172 | ||
165 | broadcastEvent(Actions.EVENT_ABORTED) | 173 | broadcastEvent(Actions.EVENT_ABORTED) |
166 | App.alert(R.string.title_unable_to_start_tinc, msg, | 174 | App.alert(R.string.title_unable_to_start_tinc, msg, |
@@ -178,21 +186,35 @@ class TincVpnService : VpnService() { | |||
178 | 186 | ||
179 | companion object { | 187 | companion object { |
180 | private const val SETUP_DELAY = 500L // ms | 188 | private const val SETUP_DELAY = 500L // ms |
181 | private var netName: String? = null | 189 | |
190 | private val STORE_NAME = this::class.java.`package`.name | ||
191 | private const val STORE_KEY_NETNAME = "netname" | ||
192 | private const val STORE_KEY_PASSPHRASE = "passphrase" | ||
193 | |||
194 | private val context by lazy { App.getContext() } | ||
195 | private val store by lazy { context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE)!! } | ||
196 | |||
182 | private var interfaceCfg: VpnInterfaceConfiguration? = null | 197 | private var interfaceCfg: VpnInterfaceConfiguration? = null |
183 | private var fd: ParcelFileDescriptor? = null | 198 | private var fd: ParcelFileDescriptor? = null |
184 | private var daemon: CompletableFuture<Unit>? = null | 199 | private var daemon: CompletableFuture<Unit>? = null |
185 | 200 | ||
186 | private fun setState(netName: String?, interfaceCfg: VpnInterfaceConfiguration?, | 201 | private fun saveConnection(netName: String?, passphrase: String?) = |
187 | fd: ParcelFileDescriptor?, daemon: CompletableFuture<Unit>?) { | 202 | store.edit() |
203 | .putString(STORE_KEY_NETNAME, netName) | ||
204 | .putString(STORE_KEY_PASSPHRASE, passphrase) | ||
205 | .apply() | ||
188 | 206 | ||
189 | TincVpnService.netName = netName | 207 | private fun setState(netName: String?, passphrase: String?, interfaceCfg: VpnInterfaceConfiguration?, |
208 | fd: ParcelFileDescriptor?, daemon: CompletableFuture<Unit>?) { | ||
209 | saveConnection(netName, passphrase) | ||
190 | TincVpnService.interfaceCfg = interfaceCfg | 210 | TincVpnService.interfaceCfg = interfaceCfg |
191 | TincVpnService.fd = fd | 211 | TincVpnService.fd = fd |
192 | TincVpnService.daemon = daemon | 212 | TincVpnService.daemon = daemon |
193 | } |