diff options
Diffstat (limited to 'app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt')
-rw-r--r-- | app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt | 113 |
1 files changed, 64 insertions, 49 deletions
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 ec0512a..ce41b89 100644 --- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt +++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt | |||
@@ -2,9 +2,9 @@ package org.pacien.tincapp.service | |||
2 | 2 | ||
3 | import android.app.Service | 3 | import android.app.Service |
4 | import android.content.Intent | 4 | import android.content.Intent |
5 | import android.net.Uri | ||
6 | import android.net.VpnService | 5 | import android.net.VpnService |
7 | import android.os.ParcelFileDescriptor | 6 | import android.os.ParcelFileDescriptor |
7 | import android.support.v4.content.LocalBroadcastManager | ||
8 | import android.util.Log | 8 | import android.util.Log |
9 | import java8.util.concurrent.CompletableFuture | 9 | import java8.util.concurrent.CompletableFuture |
10 | import org.apache.commons.configuration2.ex.ConversionException | 10 | import org.apache.commons.configuration2.ex.ConversionException |
@@ -19,32 +19,41 @@ import org.pacien.tincapp.data.TincConfiguration | |||
19 | import org.pacien.tincapp.data.VpnInterfaceConfiguration | 19 | import org.pacien.tincapp.data.VpnInterfaceConfiguration |
20 | import org.pacien.tincapp.extensions.Java.applyIgnoringException | 20 | import org.pacien.tincapp.extensions.Java.applyIgnoringException |
21 | import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg | 21 | import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg |
22 | import org.pacien.tincapp.intent.action.TINC_SCHEME | 22 | import org.pacien.tincapp.intent.Actions |
23 | import org.pacien.tincapp.utils.PemUtils | 23 | import org.pacien.tincapp.utils.TincKeyring |
24 | import java.io.File | ||
25 | import java.io.FileNotFoundException | 24 | import java.io.FileNotFoundException |
26 | import java.io.IOException | ||
27 | 25 | ||
28 | /** | 26 | /** |
29 | * @author pacien | 27 | * @author pacien |
30 | */ | 28 | */ |
31 | class TincVpnService : VpnService() { | 29 | class TincVpnService : VpnService() { |
32 | |||
33 | override fun onDestroy() { | 30 | override fun onDestroy() { |
34 | stopVpn() | 31 | stopVpn() |
35 | super.onDestroy() | 32 | super.onDestroy() |
36 | } | 33 | } |
37 | 34 | ||
38 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { | 35 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { |
39 | if (isConnected()) stopVpn() | 36 | Log.i(TAG, intent.action) |
40 | startVpn(intent.data.schemeSpecificPart, intent.data.fragment) | 37 | |
41 | return Service.START_REDELIVER_INTENT | 38 | when { |
39 | intent.action == Actions.ACTION_CONNECT && intent.scheme == Actions.TINC_SCHEME -> | ||
40 | startVpn(intent.data.schemeSpecificPart, intent.data.fragment) | ||
41 | intent.action == Actions.ACTION_DISCONNECT -> | ||
42 | stopVpn() | ||
43 | else -> | ||
44 | throw IllegalArgumentException("Invalid intent action received.") | ||
45 | } | ||
46 | |||
47 | return Service.START_NOT_STICKY | ||
42 | } | 48 | } |
43 | 49 | ||
44 | private fun startVpn(netName: String, passphrase: String? = null) { | 50 | private fun startVpn(netName: String, passphrase: String? = null): Unit = synchronized(this) { |
45 | if (netName.isBlank()) | 51 | if (netName.isBlank()) |
46 | return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") | 52 | return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") |
47 | 53 | ||
54 | if (TincKeyring.needsPassphrase(netName) && passphrase == null) | ||
55 | return reportError(resources.getString(R.string.message_passphrase_required)) | ||
56 | |||
48 | if (!AppPaths.storageAvailable()) | 57 | if (!AppPaths.storageAvailable()) |
49 | return reportError(resources.getString(R.string.message_storage_unavailable)) | 58 | return reportError(resources.getString(R.string.message_storage_unavailable)) |
50 | 59 | ||
@@ -52,6 +61,7 @@ class TincVpnService : VpnService() { | |||
52 | return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") | 61 | return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") |
53 | 62 | ||
54 | Log.i(TAG, "Starting tinc daemon for network \"$netName\".") | 63 | Log.i(TAG, "Starting tinc daemon for network \"$netName\".") |
64 | if (isConnected()) stopVpn() | ||
55 | 65 | ||
56 | val interfaceCfg = try { | 66 | val interfaceCfg = try { |
57 | VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName))) | 67 | VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName))) |
@@ -65,37 +75,41 @@ class TincVpnService : VpnService() { | |||
65 | Builder().setSession(netName) | 75 | Builder().setSession(netName) |
66 | .applyCfg(interfaceCfg) | 76 | .applyCfg(interfaceCfg) |
67 | .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) } | 77 | .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) } |
68 | .establish() | 78 | .establish()!! |
69 | } catch (e: IllegalArgumentException) { | 79 | } catch (e: IllegalArgumentException) { |
70 | return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") | 80 | return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") |
71 | } | 81 | } |
72 | 82 | ||
73 | val privateKeys = try { | 83 | val privateKeys = try { |
74 | val tincCfg = TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))) | 84 | TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))).let { tincCfg -> |
75 | 85 | Pair( | |
76 | Pair( | 86 | TincKeyring.openPrivateKey(tincCfg.ed25519PrivateKeyFile ?: AppPaths.defaultEd25519PrivateKeyFile(netName), passphrase), |
77 | openPrivateKey(tincCfg.ed25519PrivateKeyFile ?: AppPaths.defaultEd25519PrivateKeyFile(netName), passphrase), | 87 | TincKeyring.openPrivateKey(tincCfg.privateKeyFile ?: AppPaths.defaultRsaPrivateKeyFile(netName), passphrase)) |
78 | openPrivateKey(tincCfg.privateKeyFile ?: AppPaths.defaultRsaPrivateKeyFile(netName), passphrase) | 88 | } |
79 | ) | ||
80 | } catch (e: FileNotFoundException) { | 89 | } catch (e: FileNotFoundException) { |
81 | Pair(null, null) | 90 | Pair(null, null) |
82 | } catch (e: PEMException) { | 91 | } catch (e: PEMException) { |
83 | return reportError(resources.getString(R.string.message_could_not_decrypt_private_keys_format, e.message)) | 92 | return reportError(resources.getString(R.string.message_could_not_decrypt_private_keys_format, e.message)) |
84 | } | 93 | } |
85 | 94 | ||
86 | val daemon = Tincd.start(netName, deviceFd!!.fd, privateKeys.first?.fd, privateKeys.second?.fd) | 95 | val daemon = Tincd.start(netName, deviceFd.fd, privateKeys.first?.fd, privateKeys.second?.fd) |
87 | setState(netName, interfaceCfg, deviceFd, daemon) | 96 | setState(netName, interfaceCfg, deviceFd, daemon) |
88 | Log.i(TAG, "tinc daemon started.") | 97 | waitForDaemonStartup().thenRun { |
98 | deviceFd.close() | ||
99 | Log.i(TAG, "tinc daemon started.") | ||
100 | broadcastEvent(Actions.EVENT_CONNECTED) | ||
101 | } | ||
89 | } | 102 | } |
90 | 103 | ||
91 | private fun openPrivateKey(f: File?, passphrase: String?): ParcelFileDescriptor? { | 104 | private fun stopVpn(): Unit = synchronized(this) { |
92 | if (f == null || !f.exists() || passphrase == null) return null | 105 | Log.i(TAG, "Stopping any running tinc daemon.") |
93 | 106 | netName?.let { | |
94 | val pipe = ParcelFileDescriptor.createPipe() | 107 | Tinc.stop(it).thenRun { |
95 | val decryptedKey = PemUtils.decrypt(PemUtils.read(f), passphrase) | 108 | Log.i(TAG, "All tinc daemons stopped.") |
96 | val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]) | 109 | broadcastEvent(Actions.EVENT_DISCONNECTED) |
97 | PemUtils.write(decryptedKey, outputStream.writer()) | 110 | setState(null, null, null, null) |
98 | return pipe[0] | 111 | } |
112 | } | ||
99 | } | 113 | } |
100 | 114 | ||
101 | private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { | 115 | private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { |
@@ -108,10 +122,18 @@ class TincVpnService : VpnService() { | |||
108 | if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null) | 122 | if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null) |
109 | } | 123 | } |
110 | 124 | ||
111 | companion object { | 125 | private fun broadcastEvent(event: String) { |
126 | LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(event)) | ||
127 | } | ||
112 | 128 | ||
113 | val TAG = this::class.java.canonicalName!! | 129 | private fun waitForDaemonStartup() = |
130 | CompletableFuture | ||
131 | .runAsync { Thread.sleep(SETUP_DELAY) } | ||
132 | .thenCompose { netName?.let { Tinc.pid(it) } ?: CompletableFuture.completedFuture(0) } | ||
114 | 133 | ||
134 | companion object { | ||
135 | private const val SETUP_DELAY = 500L // ms | ||
136 | private val TAG = this::class.java.canonicalName!! | ||
115 | private var netName: String? = null | 137 | private var netName: String? = null |
116 | private var interfaceCfg: VpnInterfaceConfiguration? = null | 138 | private var interfaceCfg: VpnInterfaceConfiguration? = null |
117 | private var fd: ParcelFileDescriptor? = null | 139 | private var fd: ParcelFileDescriptor? = null |
@@ -119,35 +141,28 @@ class TincVpnService : VpnService() { | |||
119 | 141 | ||
120 | private fun setState(netName: String?, interfaceCfg: VpnInterfaceConfiguration?, | 142 | private fun setState(netName: String?, interfaceCfg: VpnInterfaceConfiguration?, |
121 | fd: ParcelFileDescriptor?, daemon: CompletableFuture<Void>?) { | 143 | fd: ParcelFileDescriptor?, daemon: CompletableFuture<Void>?) { |
144 | |||
122 | TincVpnService.netName = netName | 145 | TincVpnService.netName = netName |
123 | TincVpnService.interfaceCfg = interfaceCfg | 146 | TincVpnService.interfaceCfg = interfaceCfg |
124 | TincVpnService.fd = fd | 147 | TincVpnService.fd = fd |
125 | TincVpnService.daemon = daemon | 148 | TincVpnService.daemon = daemon |
126 | } | 149 | } |
127 | 150 | ||
128 | fun startVpn(netName: String, passphrase: String? = null) { | ||
129 | App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java) | ||
130 | .setData(Uri.Builder().scheme(TINC_SCHEME).opaquePart(netName).fragment(passphrase).build())) | ||
131 | } | ||
132 | |||
133 | fun stopVpn() { | ||
134 | try { | ||