summaryrefslogtreecommitdiff
path: root/ipscrambling/src/main/java
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-07-11 07:11:16 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-07-11 07:11:16 +0000
commitf9b0ce84cd3956ad169cb312c52398f7dd10d2fa (patch)
treed1057257791c1a0a9d7e3d7e369380f0438391fb /ipscrambling/src/main/java
parent4dcfe5eb84142df16f7b8c5431b4afeb2a28cbc2 (diff)
parent921756bb2f3bb7891386f5aac551fe775d454a78 (diff)
Merge branch '2-integrate_ipscrambling_module' into 'main'
2: integrate ipscrambling module in git repos and update dependencies See merge request e/os/advanced-privacy!137
Diffstat (limited to 'ipscrambling/src/main/java')
-rw-r--r--ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IIpScramblerModule.kt54
-rw-r--r--ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IpScramblerModule.kt301
2 files changed, 355 insertions, 0 deletions
diff --git a/ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IIpScramblerModule.kt b/ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IIpScramblerModule.kt
new file mode 100644
index 0000000..859319a
--- /dev/null
+++ b/ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IIpScramblerModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 E FOUNDATION
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package foundation.e.privacymodules.ipscrambler
+
+import android.content.Intent
+
+interface IIpScramblerModule {
+ fun prepareAndroidVpn(): Intent?
+
+ fun start(enableNotification: Boolean = true)
+
+ fun stop()
+
+ fun requestStatus()
+
+ var appList: Set<String>
+
+ var exitCountry: String
+ fun getAvailablesLocations(): Set<String>
+
+ val httpProxyPort: Int
+ val socksProxyPort: Int
+
+ fun addListener(listener: Listener)
+ fun removeListener(listener: Listener)
+ fun clearListeners()
+
+ fun onCleared()
+
+ interface Listener {
+ fun onStatusChanged(newStatus: Status)
+ fun log(message: String)
+ fun onTrafficUpdate(upload: Long, download: Long, read: Long, write: Long)
+ }
+
+ enum class Status {
+ OFF, ON, STARTING, STOPPING, START_DISABLED
+ }
+}
diff --git a/ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IpScramblerModule.kt b/ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IpScramblerModule.kt
new file mode 100644
index 0000000..1c39330
--- /dev/null
+++ b/ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IpScramblerModule.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2021 E FOUNDATION
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package foundation.e.privacymodules.ipscrambler
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.VpnService
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import android.util.Log
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import foundation.e.privacymodules.ipscrambler.IIpScramblerModule.Listener
+import foundation.e.privacymodules.ipscrambler.IIpScramblerModule.Status
+import org.torproject.android.service.OrbotConstants
+import org.torproject.android.service.OrbotConstants.ACTION_STOP_FOREGROUND_TASK
+import org.torproject.android.service.OrbotService
+import org.torproject.android.service.util.Prefs
+import java.security.InvalidParameterException
+
+@SuppressLint("CommitPrefEdits")
+class IpScramblerModule(private val context: Context) : IIpScramblerModule {
+ companion object {
+ const val TAG = "IpScramblerModule"
+
+ private val EXIT_COUNTRY_CODES = setOf("DE", "AT", "SE", "CH", "IS", "CA", "US", "ES", "FR", "BG", "PL", "AU", "BR", "CZ", "DK", "FI", "GB", "HU", "NL", "JP", "RO", "RU", "SG", "SK")
+
+ // Key where exit country is stored by orbot service.
+ private const val PREFS_KEY_EXIT_NODES = "pref_exit_nodes"
+ // Copy of the package private OrbotService.NOTIFY_ID value.
+ // const val ORBOT_SERVICE_NOTIFY_ID_COPY = 1
+ }
+
+ private var currentStatus: Status? = null
+ private val listeners = mutableSetOf<Listener>()
+
+ private val localBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.action ?: return
+ if (action == OrbotConstants.ACTION_RUNNING_SYNC) {
+ try {
+ intent.getStringExtra(OrbotConstants.EXTRA_STATUS)?.let {
+ val newStatus = Status.valueOf(it)
+ currentStatus = newStatus
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Can't parse Orbot service status.")
+ }
+ return
+ }
+
+ val msg = messageHandler.obtainMessage()
+ msg.obj = action
+ msg.data = intent.extras
+ messageHandler.sendMessage(msg)
+ }
+ }
+
+ private val messageHandler: Handler = object : Handler(Looper.getMainLooper()) {
+ override fun handleMessage(msg: Message) {
+ val action = msg.obj as? String ?: return
+ val data = msg.data
+ when (action) {
+ OrbotConstants.LOCAL_ACTION_LOG ->
+ data.getString(OrbotConstants.LOCAL_EXTRA_LOG)?.let { newLog(it) }
+
+ OrbotConstants.LOCAL_ACTION_BANDWIDTH -> {
+ trafficUpdate(
+ data.getLong("up", 0),
+ data.getLong("down", 0),
+ data.getLong("written", 0),
+ data.getLong("read", 0)
+ )
+ }
+
+ OrbotConstants.LOCAL_ACTION_PORTS -> {
+ httpProxyPort = data.getInt(OrbotService.EXTRA_HTTP_PROXY_PORT, -1)
+ socksProxyPort = data.getInt(OrbotService.EXTRA_SOCKS_PROXY_PORT, -1)
+ }
+
+ OrbotConstants.LOCAL_ACTION_STATUS ->
+ data.getString(OrbotConstants.EXTRA_STATUS)?.let {
+ try {
+ val newStatus = Status.valueOf(it)
+ updateStatus(newStatus, force = true)
+ } catch (e: Exception) {
+ Log.e(TAG, "Can't parse Orbot service status.")
+ }
+ }
+ }
+ super.handleMessage(msg)
+ }
+ }
+
+ init {
+ Prefs.setContext(context)
+
+ val lbm = LocalBroadcastManager.getInstance(context)
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(OrbotConstants.LOCAL_ACTION_STATUS)
+ )
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(OrbotConstants.LOCAL_ACTION_BANDWIDTH)
+ )
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(OrbotConstants.LOCAL_ACTION_LOG)
+ )
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(OrbotConstants.LOCAL_ACTION_PORTS)
+ )
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(OrbotConstants.ACTION_RUNNING_SYNC)
+ )
+
+ Prefs.getSharedPrefs(context).edit()
+ .putInt(OrbotConstants.PREFS_DNS_PORT, OrbotConstants.TOR_DNS_PORT_DEFAULT)
+ .apply()
+ }
+
+ private fun updateStatus(status: Status, force: Boolean = false) {
+ if (force || status != currentStatus) {
+ currentStatus = status
+ listeners.forEach {
+ it.onStatusChanged(status)
+ }
+ }
+ }
+
+ private fun isServiceRunning(): Boolean {
+ // Reset status, and then ask to refresh it synchronously.
+ currentStatus = Status.OFF
+ LocalBroadcastManager.getInstance(context)
+ .sendBroadcastSync(Intent(OrbotConstants.ACTION_CHECK_RUNNING_SYNC))
+ return currentStatus != Status.OFF
+ }
+
+ private fun newLog(message: String) {
+ listeners.forEach { it.log(message) }
+ }
+
+ private fun trafficUpdate(upload: Long, download: Long, read: Long, write: Long) {
+ listeners.forEach { it.onTrafficUpdate(upload, download, read, write) }
+ }
+
+ private fun sendIntentToService(action: String, extra: Bundle? = null) {
+ val intent = Intent(context, OrbotService::class.java)
+ intent.action = action
+ extra?.let { intent.putExtras(it) }
+ context.startService(intent)
+ }
+
+ @SuppressLint("ApplySharedPref")
+ private fun saveTorifiedApps(packageNames: Collection<String>) {
+ packageNames.joinToString("|")
+ Prefs.getSharedPrefs(context).edit().putString(
+ OrbotConstants.PREFS_KEY_TORIFIED, packageNames.joinToString("|")
+ ).commit()
+
+ if (isServiceRunning()) {
+ sendIntentToService(OrbotConstants.ACTION_RESTART_VPN)
+ }
+ }
+
+ private fun getTorifiedApps(): Set<String> {
+ val list = Prefs.getSharedPrefs(context).getString(OrbotConstants.PREFS_KEY_TORIFIED, "")
+ ?.split("|")
+ return if (list == null || list == listOf("")) {
+ emptySet()
+ } else {
+ list.toSet()
+ }
+ }
+
+ @SuppressLint("ApplySharedPref")
+ private fun setExitCountryCode(countryCode: String) {
+ val countryParam = when {
+ countryCode.isEmpty() -> ""
+ countryCode in EXIT_COUNTRY_CODES -> "{$countryCode}"
+ else -> throw InvalidParameterException(
+ "Only these countries are available: ${EXIT_COUNTRY_CODES.joinToString { ", " } }"
+ )
+ }
+
+ if (isServiceRunning()) {
+ val extra = Bundle()
+ extra.putString("exit", countryParam)
+ sendIntentToService(OrbotConstants.CMD_SET_EXIT, extra)
+ } else {
+ Prefs.getSharedPrefs(context)
+ .edit().putString(PREFS_KEY_EXIT_NODES, countryParam)
+ .commit()
+ }
+ }
+
+ private fun getExitCountryCode(): String {
+ val raw = Prefs.getExitNodes()
+ return if (raw.isEmpty()) raw else raw.slice(1..2)
+ }
+
+ override fun prepareAndroidVpn(): Intent? {
+ return VpnService.prepare(context)
+ }
+
+ override fun start(enableNotification: Boolean) {
+ Prefs.enableNotification(enableNotification)
+ Prefs.putUseVpn(true)
+ Prefs.putStartOnBoot(true)
+
+ sendIntentToService(OrbotConstants.ACTION_START)
+ sendIntentToService(OrbotConstants.ACTION_START_VPN)
+ }
+
+ override fun stop() {
+ updateStatus(Status.STOPPING)
+
+ Prefs.putUseVpn(false)
+ Prefs.putStartOnBoot(false)
+
+ sendIntentToService(OrbotConstants.ACTION_STOP_VPN)
+ sendIntentToService(
+ action = OrbotConstants.ACTION_STOP,
+ extra = Bundle().apply { putBoolean(ACTION_STOP_FOREGROUND_TASK, true) }
+ )
+ stoppingWatchdog(5)
+ }
+
+ private fun stoppingWatchdog(countDown: Int) {
+ Handler(Looper.getMainLooper()).postDelayed(
+ {
+ if (isServiceRunning() && countDown > 0) {
+ stoppingWatchdog(countDown - 1)
+ } else {
+ updateStatus(Status.OFF, force = true)
+ }
+ },
+ 500
+ )
+ }
+
+ override fun requestStatus() {
+ if (isServiceRunning()) {
+ sendIntentToService(OrbotConstants.ACTION_STATUS)
+ } else {
+ updateStatus(Status.OFF, force = true)
+ }
+ }
+
+ override var appList: Set<String>
+ get() = getTorifiedApps()
+ set(value) = saveTorifiedApps(value)
+
+ override var exitCountry: String
+ get() = getExitCountryCode()
+ set(value) = setExitCountryCode(value)
+
+ override fun getAvailablesLocations(): Set<String> = EXIT_COUNTRY_CODES
+
+ override var httpProxyPort: Int = -1
+ private set
+
+ override var socksProxyPort: Int = -1
+ private set
+
+ override fun addListener(listener: Listener) {
+ listeners.add(listener)
+ }
+ override fun removeListener(listener: Listener) {
+ listeners.remove(listener)
+ }
+ override fun clearListeners() {
+ listeners.clear()
+ }
+
+ override fun onCleared() {
+ LocalBroadcastManager.getInstance(context).unregisterReceiver(localBroadcastReceiver)
+ }
+}