diff options
author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-07-11 07:11:15 +0000 |
---|---|---|
committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-07-11 07:11:15 +0000 |
commit | 921756bb2f3bb7891386f5aac551fe775d454a78 (patch) | |
tree | 9d1162115ae1e27ad27417f3ba90a3fb72919e00 /ipscrambling/src/main | |
parent | 8b1855bce1313ad84df8f96efdbb62e2acf7ff33 (diff) |
2: integrate ipscrambling module in git repos and update dependencies
Diffstat (limited to 'ipscrambling/src/main')
3 files changed, 381 insertions, 0 deletions
diff --git a/ipscrambling/src/main/AndroidManifest.xml b/ipscrambling/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e948147 --- /dev/null +++ b/ipscrambling/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foundation.e.privacymodules.tor" + android:installLocation="internalOnly" + > + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> + + <application + android:allowBackup="true" + > + <service + android:name="org.torproject.android.service.OrbotService" + android:enabled="true" + android:exported="false" + android:permission="android.permission.BIND_VPN_SERVICE" + android:stopWithTask="false"> + <intent-filter> + <action android:name="android.net.VpnService" /> + </intent-filter> + </service> + </application> +</manifest> 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) + } +} |