aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpacien2018-08-06 13:18:12 +0200
committerpacien2018-08-06 13:18:12 +0200
commit51c7b7cb443da7fa7dd1314881f309eea4aa7d10 (patch)
tree8a5e69f2368a16d9219632d25dd4e01faaa4fe49
parentaef4ce4fb1cde5fda83c02d85b06238a934ab93a (diff)
downloadtincapp-51c7b7cb443da7fa7dd1314881f309eea4aa7d10.tar.gz
Enable connection restoration (always-on VPN)
-rw-r--r--app/src/main/java/org/pacien/tincapp/intent/Actions.kt1
-rw-r--r--app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt66
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 @@
19package org.pacien.tincapp.service 19package org.pacien.tincapp.service
20 20
21import android.app.Service 21import android.app.Service
22import android.content.Context
22import android.content.Intent 23import android.content.Intent
23import android.net.VpnService 24import android.net.VpnService
24import android.os.ParcelFileDescriptor 25import android.os.ParcelFileDescriptor
@@ -40,7 +41,6 @@ import org.pacien.tincapp.extensions.Java.defaultMessage
40import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg 41import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg
41import org.pacien.tincapp.intent.Actions 42import org.pacien.tincapp.intent.Actions
42import org.pacien.tincapp.utils.TincKeyring 43import org.pacien.tincapp.utils.TincKeyring
43import org.slf4j.Logger
44import org.slf4j.LoggerFactory 44import org.slf4j.LoggerFactory
45import java.io.FileNotFoundException 45import java.io.FileNotFoundException
46 46
@@ -48,12 +48,7 @@ import java.io.FileNotFoundException
48 * @author pacien 48 * @author pacien
49 */ 49 */
50class TincVpnService : VpnService() { 50class 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 } 213 }
194 214