[tbb-commits] [Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.5-1] fixup! Add Tor integration and UI
Dan Ballard (@dan)
git at gitlab.torproject.org
Fri Jan 26 06:06:01 UTC 2024
Dan Ballard pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits:
6d0a6453 by Dan Ballard at 2024-01-25T16:15:45-08:00
fixup! Add Tor integration and UI
Bug 42252: Make TorController and interface and add a Geckoview implementation
- - - - -
4 changed files:
- fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
Changes:
=====================================
fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
=====================================
@@ -43,7 +43,8 @@ import org.mozilla.fenix.perf.StartupActivityLog
import org.mozilla.fenix.perf.StartupStateProvider
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
-import org.mozilla.fenix.tor.TorController
+import org.mozilla.fenix.tor.TorControllerGV
+import org.mozilla.fenix.tor.TorControllerTAS
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wifi.WifiConnectionMonitor
@@ -201,7 +202,7 @@ class Components(private val context: Context) {
),
)
}
- val torController by lazyMonitored { TorController(context) }
+ val torController by lazyMonitored { if (settings.useNewBootstrap) TorControllerGV(context) else TorControllerTAS(context) }
}
/**
=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
=====================================
@@ -4,22 +4,7 @@
package org.mozilla.fenix.tor
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
import androidx.lifecycle.LifecycleCoroutineScope
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withTimeoutOrNull
-
-import org.mozilla.fenix.BuildConfig
-
-import org.torproject.android.service.TorService
-import org.torproject.android.service.TorServiceConstants
-import org.torproject.android.service.util.Prefs
interface TorEvents {
fun onTorConnecting()
@@ -28,347 +13,59 @@ interface TorEvents {
fun onTorStopped()
}
-private enum class TorStatus {
- OFF,
- STARTING,
- ON,
- STOPPING,
- UNKNOWN;
+internal enum class TorStatus(val status: String) {
+ OFF("OFF"),
+ STARTING("STARTING"),
+ ON("ON"),
+ STOPPING("STOPPING"),
+ UNKNOWN("UNKNOWN");
- fun getStateFromString(status: String): TorStatus {
- return when (status) {
- TorServiceConstants.STATUS_ON -> ON
- TorServiceConstants.STATUS_STARTING -> STARTING
- TorServiceConstants.STATUS_STOPPING -> STOPPING
- TorServiceConstants.STATUS_OFF -> OFF
- else -> UNKNOWN
+ companion object {
+ fun fromString(status: String): TorStatus {
+ return when (status) {
+ "ON" -> ON
+ "STARTING" -> STARTING
+ "STOPPING" -> STOPPING
+ "OFF" -> OFF
+ else -> UNKNOWN
+ }
}
}
fun isOff() = this == OFF
fun isOn() = this == ON
fun isStarting() = this == STARTING
- fun isStarted() = ((this == TorStatus.STARTING) || (this == TorStatus.ON))
+ fun isStarted() = ((this == STARTING) || (this == ON))
fun isStopping() = this == STOPPING
fun isUnknown() = this == UNKNOWN
}
- at SuppressWarnings("TooManyFunctions")
-class TorController(
- private val context: Context
-) : TorEvents {
-
- private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
- private val entries = mutableListOf<Pair<String?, String?>>()
- val logEntries get() = entries
-
- private var torListeners = mutableListOf<TorEvents>()
-
- private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
- private var lockTorListenersMutation = false
-
- private var lastKnownStatus = TorStatus.OFF
- private var wasTorBootstrapped = false
- private var isTorRestarting = false
-
- // This may be a lie
- private var isTorBootstrapped = false
- get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
-
- val isDebugLoggingEnabled get() =
- context
- .getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
- .getBoolean("pref_enable_logging", false)
-
- val isStarting get() = lastKnownStatus.isStarting()
- val isRestarting get() = isTorRestarting
- val isBootstrapped get() = isTorBootstrapped
- val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
-
+interface TorController: TorEvents {
+ val logEntries: MutableList<Pair<String?, String?>>
+ val isStarting: Boolean
+ val isRestarting: Boolean
+ val isBootstrapped: Boolean
+ val isConnected: Boolean
var bridgesEnabled: Boolean
- get() = Prefs.bridgesEnabled()
- set(value) { Prefs.putBridgesEnabled(value) }
-
var bridgeTransport: TorBridgeTransportConfig
- get() {
- return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
- Prefs.getBridgesList()
- )
- }
- set(value) {
- if (value == TorBridgeTransportConfig.USER_PROVIDED) {
- // Don't set the pref when the value is USER_PROVIDED because
- // "user_provided" is not a valid bridge or transport type.
- // This call should be followed by setting userProvidedBridges.
- return
- }
- Prefs.setBridgesList(value.transportName)
- }
-
var userProvidedBridges: String?
- get() {
- val bridges = Prefs.getBridgesList()
- val bridgeType =
- TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
- return when (bridgeType) {
- TorBridgeTransportConfig.USER_PROVIDED -> bridges
- else -> null
- }
- }
- set(value) {
- Prefs.setBridgesList(value)
- }
-
- fun start() {
- // Register receiver
- lbm.registerReceiver(
- persistentBroadcastReceiver,
- IntentFilter(TorServiceConstants.ACTION_STATUS)
- )
- lbm.registerReceiver(
- persistentBroadcastReceiver,
- IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
- )
- }
- fun stop() {
- lbm.unregisterReceiver(persistentBroadcastReceiver)
- }
+ fun start()
+ fun stop()
- private val persistentBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == null ||
- (intent.action != TorServiceConstants.ACTION_STATUS &&
- intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
- ) {
- return
- }
- val action = intent.action
+ override fun onTorConnecting()
+ override fun onTorConnected()
+ override fun onTorStatusUpdate(entry: String?, status: String?)
+ override fun onTorStopped()
- val logentry: String?
- val status: String?
- if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
- logentry = intent.getExtras()
- ?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
- } else {
- logentry = null
- }
-
- status = intent.getExtras()
- ?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
-
- if (logentry == null && status == null) {
- return
- }
-
- onTorStatusUpdate(logentry, status)
-
- if (status == null) {
- return
- }
-
- val newStatus = lastKnownStatus.getStateFromString(status)
-
- if (newStatus.isUnknown() && wasTorBootstrapped) {
- stopTor()
- }
-
- entries.add(Pair(logentry, status))
-
- if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
- wasTorBootstrapped = true
- onTorConnected()
- }
-
- if (lastKnownStatus.isStopping() && newStatus.isOff()) {
- if (isTorRestarting) {
- initiateTorBootstrap()
- } else {
- onTorStopped()
- }
- }
-
- if (lastKnownStatus.isOff() && newStatus.isStarting()) {
- isTorRestarting = false
- }
-
- lastKnownStatus = newStatus
- }
- }
-
- override fun onTorConnecting() {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorConnecting() }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
-
- override fun onTorConnected() {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorConnected() }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
+ fun registerTorListener(l: TorEvents)
+ fun unregisterTorListener(l: TorEvents)
- override fun onTorStatusUpdate(entry: String?, status: String?) {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorStatusUpdate(entry, status) }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
-
- override fun onTorStopped() {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorStopped() }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
-
- fun registerTorListener(l: TorEvents) {
- if (torListeners.contains(l)) {
- return
- }
-
- if (lockTorListenersMutation) {
- pendingRegisterChangeList.add(Pair(l, true))
- } else {
- torListeners.add(l)
- }
- }
-
- fun unregisterTorListener(l: TorEvents) {
- if (!torListeners.contains(l)) {
- return
- }
-
- if (lockTorListenersMutation) {
- pendingRegisterChangeList.add(Pair(l, false))
- } else {
- torListeners.remove(l)
- }
- }
-
- private fun handlePendingRegistrationChanges() {
- pendingRegisterChangeList.forEach {
- if (it.second) {
- registerTorListener(it.first)
- } else {
- unregisterTorListener(it.first)
- }
- }
-
- pendingRegisterChangeList.clear()
- }
-
- /**
- * Receive the current Tor status.
- *
- * Send a request for the current status and receive the response.
- * Returns true if Tor is running, false otherwise.
- *
- */
- private suspend fun checkTorIsStarted(): Boolean {
- val channel = Channel<Boolean>()
-
- // Register receiver
- val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
- val localBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.action ?: return
- // We only want ACTION_STATUS messages
- if (action != TorServiceConstants.ACTION_STATUS) {
- return
- }
- // The current status has the EXTRA_STATUS key
- val currentStatus =
- intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
- channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
- }
- }
- lbm.registerReceiver(
- localBroadcastReceiver,
- IntentFilter(TorServiceConstants.ACTION_STATUS)
- )
-
- // Request service status
- sendServiceAction(TorServiceConstants.ACTION_STATUS)
-
- // Wait for response and unregister receiver
- var torIsStarted = false
- withTimeoutOrNull(torServiceResponseTimeout) {
- torIsStarted = channel.receive()
- }
- lbm.unregisterReceiver(localBroadcastReceiver)
- return torIsStarted
- }
-
- fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false) {
- if (BuildConfig.DISABLE_TOR) {
- return
- }
-
- context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
- .edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
-
- if (lifecycleScope == null) {
- sendServiceAction(TorServiceConstants.ACTION_START)
- } else {
- lifecycleScope.launch {
- val torNeedsStart = !checkTorIsStarted()
- if (torNeedsStart) {
- sendServiceAction(TorServiceConstants.ACTION_START)
- }
- }
- }
- }
-
- fun stopTor() {
- if (BuildConfig.DISABLE_TOR) {
- return
- }
-
- val torService = Intent(context, TorService::class.java)
- context.stopService(torService)
- }
-
- fun setTorStopped() {
- lastKnownStatus = TorStatus.OFF
- onTorStopped()
- }
-
- fun restartTor() {
- // tor-android-service doesn't dynamically update the torrc file,
- // and it doesn't use SETCONF, so we completely restart the service.
- // However, don't restart if we aren't started and we weren't
- // previously started.
- if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
- return
- }
+ fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false)
+ fun stopTor()
+ fun setTorStopped()
+ fun restartTor()
+}
- if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
- // If we aren't started, but we were previously bootstrapped,
- // then we handle a "restart" request as a "start" restart
- initiateTorBootstrap()
- } else {
- // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
- // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
- // service.
- isTorRestarting = true
- stopTor()
- }
- }
- private fun sendServiceAction(action: String) {
- val torServiceStatus = Intent(context, TorService::class.java)
- torServiceStatus.action = action
- context.startService(torServiceStatus)
- }
- companion object {
- const val torServiceResponseTimeout = 5000L
- }
-}
=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
=====================================
@@ -0,0 +1,289 @@
+package org.mozilla.fenix.tor
+
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.LifecycleCoroutineScope
+import mozilla.components.browser.engine.gecko.GeckoEngine
+import org.mozilla.fenix.ext.components
+import org.mozilla.geckoview.TorIntegrationAndroid
+import org.mozilla.geckoview.TorIntegrationAndroid.BootstrapStateChangeListener
+import org.mozilla.geckoview.TorSettings
+import org.mozilla.geckoview.TorSettings.BridgeBuiltinType
+import org.mozilla.geckoview.TorSettings.BridgeSource
+
+// Enum matching TorConnectState from TorConnect.sys.mjs that we get from onBootstrapStateChange
+internal enum class TorConnectState(val state: String) {
+ Initial("Initial"),
+ Configuring("Configuring"),
+ AutoBootstrapping("AutoBootstrapping"),
+ Bootstrapping("Bootstrapping"),
+ Error("Error"),
+ Bootstrapped("Bootstrapped"),
+ Disabled("Disabled");
+
+ fun isStarting() = this == Bootstrapping || this == AutoBootstrapping
+ fun isError() = this == Error
+
+ fun isStarted() = this == Bootstrapped
+
+ fun isOff() = this == Initial || this == Configuring || this == Disabled || this == Error
+
+
+ // Convert to TorStatus that firefox-android uses based on tor-android-service
+ fun toTorStatus(): TorStatus {
+ return when (this) {
+ Initial -> TorStatus.OFF
+ Configuring -> TorStatus.OFF
+ AutoBootstrapping -> TorStatus.STARTING
+ Bootstrapping -> TorStatus.STARTING
+ Error -> TorStatus.UNKNOWN
+ Bootstrapped -> TorStatus.ON
+ Disabled -> TorStatus.OFF
+ }
+ }
+}
+
+class TorControllerGV(
+ private val context: Context,
+) : TorController, TorEvents, BootstrapStateChangeListener {
+
+ private val TAG = "TorControllerGV"
+
+ private var torListeners = mutableListOf<TorEvents>()
+
+ private var lastKnownStatus = TorConnectState.Initial
+ private var wasTorBootstrapped = false
+ private var isTorRestarting = false
+
+ private var isTorBootstrapped = false
+ get() = ((lastKnownStatus.isStarted()) && wasTorBootstrapped)
+
+ private val entries = mutableListOf<Pair<String?, String?>>()
+ override val logEntries get() = entries
+ override val isStarting get() = lastKnownStatus.isStarting()
+ override val isRestarting get() = isTorRestarting
+ override val isBootstrapped get() = isTorBootstrapped
+ override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
+
+ private fun getTorIntegration(): TorIntegrationAndroid {
+ return (context.components.core.engine as GeckoEngine).getTorIntegrationController()
+ }
+
+ private fun getTorSettings(): TorSettings? {
+ return getTorIntegration().getSettings()
+ }
+
+ override var bridgesEnabled: Boolean
+ get() {
+ return getTorSettings()?.bridgesEnabled ?: false
+ }
+ set(value) {
+ getTorSettings()?.let {
+ it.bridgesEnabled = value
+ getTorIntegration().setSettings(it, true, true)
+ }
+ }
+
+
+ override var bridgeTransport: TorBridgeTransportConfig
+ get() {
+ return when (getTorSettings()?.bridgesSource) {
+ BridgeSource.BuiltIn -> {
+ when (getTorSettings()?.bridgesBuiltinType) {
+ BridgeBuiltinType.Obfs4 -> TorBridgeTransportConfig.BUILTIN_OBFS4
+ BridgeBuiltinType.MeekAzure -> TorBridgeTransportConfig.BUILTIN_MEEK_AZURE
+ BridgeBuiltinType.Snowflake -> TorBridgeTransportConfig.BUILTIN_SNOWFLAKE
+ else -> TorBridgeTransportConfig.USER_PROVIDED
+ }
+
+ }
+
+ BridgeSource.UserProvided -> TorBridgeTransportConfig.USER_PROVIDED
+ else -> TorBridgeTransportConfig.USER_PROVIDED
+ }
+ }
+ set(value) {
+ getTorSettings()?.let {
+ if (value == TorBridgeTransportConfig.USER_PROVIDED) {
+ it.bridgesSource = BridgeSource.BuiltIn
+ } else {
+ val bbt: BridgeBuiltinType = when (value) {
+ TorBridgeTransportConfig.BUILTIN_OBFS4 -> BridgeBuiltinType.Obfs4
+ TorBridgeTransportConfig.BUILTIN_MEEK_AZURE -> BridgeBuiltinType.MeekAzure
+ TorBridgeTransportConfig.BUILTIN_SNOWFLAKE -> BridgeBuiltinType.Snowflake
+ else -> BridgeBuiltinType.Invalid
+ }
+ it.bridgesBuiltinType = bbt
+ }
+ getTorIntegration().setSettings(it, true, true)
+ }
+ }
+
+
+ override var userProvidedBridges: String?
+ get() {
+ return getTorSettings()?.bridgeBridgeStrings?.joinToString("\r\n")
+ }
+ set(value) {
+ getTorSettings()?.let {
+ it.bridgeBridgeStrings = value?.split("\r\n")?.toTypedArray() ?: arrayOf<String>()
+ getTorIntegration().setSettings(it, true, true)
+ }
+ }
+
+ override fun start() {
+ getTorIntegration().registerBootstrapStateChangeListener(this)
+ }
+
+ override fun stop() {
+ getTorIntegration().unregisterBootstrapStateChangeListener(this)
+ }
+
+ // TorEvents
+ override fun onTorConnecting() {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorConnecting() }
+ }
+ }
+
+ // TorEvents
+ override fun onTorConnected() {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorConnected() }
+ }
+ }
+
+ // TorEvents
+ override fun onTorStatusUpdate(entry: String?, status: String?) {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorStatusUpdate(entry, status) }
+ }
+ }
+
+ // TorEvents
+ override fun onTorStopped() {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorStopped() }
+ }
+ }
+
+ override fun registerTorListener(l: TorEvents) {
+ synchronized(torListeners) {
+ if (torListeners.contains(l)) {
+ return
+ }
+ torListeners.add(l)
+ }
+ }
+
+ override fun unregisterTorListener(l: TorEvents) {
+ synchronized(torListeners) {
+ if (!torListeners.contains(l)) {
+ return
+ }
+ torListeners.remove(l)
+ }
+ }
+
+ override fun initiateTorBootstrap(
+ lifecycleScope: LifecycleCoroutineScope?,
+ withDebugLogging: Boolean,
+ ) {
+ getTorIntegration().beginBootstrap()
+ }
+
+ override fun stopTor() {
+ getTorIntegration().cancelBootstrap()
+ }
+
+ override fun setTorStopped() {
+ lastKnownStatus = TorConnectState.Disabled
+ onTorStopped()
+ }
+
+ override fun restartTor() {
+ if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
+ // If we aren't started, but we were previously bootstrapped,
+ // then we handle a "restart" request as a "start" restart
+ initiateTorBootstrap()
+ } else {
+ // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
+ // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
+ // service.
+ isTorRestarting = true
+ stopTor()
+ }
+ }
+
+ // TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents)
+ // Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events
+ // and state for firefox-android (designed for tor-android-service)
+ // fun onTorConnecting()
+ // fun onTorConnected()
+ // fun onTorStatusUpdate(entry: String?, status: String?)
+ // fun onTorStopped()
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapStateChange(newStateVal: String?) {
+ Log.d(TAG, "onBootstrapStateChange($newStateVal)")
+ val newState: TorConnectState = TorConnectState.valueOf(newStateVal ?: "Error")
+
+ if (newState.isError() && wasTorBootstrapped) {
+ stopTor()
+ }
+
+ if (newState.isStarted()) {
+ wasTorBootstrapped = true
+ onTorConnected()
+ }
+
+ if (wasTorBootstrapped && newState == TorConnectState.Configuring) {
+ wasTorBootstrapped = false
+ if (isTorRestarting) {
+ initiateTorBootstrap()
+ } else {
+ onTorStopped()
+ }
+ }
+
+ if (lastKnownStatus.isOff() && newState.isStarting()) {
+ isTorRestarting = false
+ }
+
+ lastKnownStatus = newState
+
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapProgress(progress: Double, status: String?, hasWarnings: Boolean) {
+ Log.d(TAG, "onBootstrapProgress($progress, $status, $hasWarnings)")
+ if (progress == 100.0) {
+ lastKnownStatus = TorConnectState.Bootstrapped
+ wasTorBootstrapped = true
+ onTorConnected()
+ } else {
+ lastKnownStatus = TorConnectState.Bootstrapping
+ onTorConnecting()
+
+ }
+ entries.add(Pair(status, lastKnownStatus.toTorStatus().status))
+ onTorStatusUpdate(status, lastKnownStatus.toTorStatus().status)
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapComplete() {
+ lastKnownStatus = TorConnectState.Bootstrapped
+ this.onTorConnected()
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapError(message: String?, details: String?) {
+ lastKnownStatus = TorConnectState.Error
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onSettingsRequested() {
+ // noop
+ }
+}
=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
=====================================
@@ -0,0 +1,332 @@
+package org.mozilla.fenix.tor
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
+import org.mozilla.fenix.BuildConfig
+import org.torproject.android.service.TorService
+import org.torproject.android.service.TorServiceConstants
+import org.torproject.android.service.util.Prefs
+
+ at SuppressWarnings("TooManyFunctions")
+class TorControllerTAS (private val context: Context): TorController {
+ private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
+ private val entries = mutableListOf<Pair<String?, String?>>()
+ override val logEntries get() = entries
+
+ private var torListeners = mutableListOf<TorEvents>()
+
+ private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
+ private var lockTorListenersMutation = false
+
+ private var lastKnownStatus = TorStatus.OFF
+ private var wasTorBootstrapped = false
+ private var isTorRestarting = false
+
+ // This may be a lie
+ private var isTorBootstrapped = false
+ get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
+
+ val isDebugLoggingEnabled get() =
+ context
+ .getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
+ .getBoolean("pref_enable_logging", false)
+
+ override val isStarting get() = lastKnownStatus.isStarting()
+ override val isRestarting get() = isTorRestarting
+ override val isBootstrapped get() = isTorBootstrapped
+ override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
+
+ override var bridgesEnabled: Boolean
+ get() = Prefs.bridgesEnabled()
+ set(value) { Prefs.putBridgesEnabled(value) }
+
+ override var bridgeTransport: TorBridgeTransportConfig
+ get() {
+ return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
+ Prefs.getBridgesList()
+ )
+ }
+ set(value) {
+ if (value == TorBridgeTransportConfig.USER_PROVIDED) {
+ // Don't set the pref when the value is USER_PROVIDED because
+ // "user_provided" is not a valid bridge or transport type.
+ // This call should be followed by setting userProvidedBridges.
+ return
+ }
+ Prefs.setBridgesList(value.transportName)
+ }
+
+ override var userProvidedBridges: String?
+ get() {
+ val bridges = Prefs.getBridgesList()
+ val bridgeType =
+ TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
+ return when (bridgeType) {
+ TorBridgeTransportConfig.USER_PROVIDED -> bridges
+ else -> null
+ }
+ }
+ set(value) {
+ Prefs.setBridgesList(value)
+ }
+
+ override fun start() {
+ // Register receiver
+ lbm.registerReceiver(
+ persistentBroadcastReceiver,
+ IntentFilter(TorServiceConstants.ACTION_STATUS)
+ )
+ lbm.registerReceiver(
+ persistentBroadcastReceiver,
+ IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
+ )
+ }
+
+ override fun stop() {
+ lbm.unregisterReceiver(persistentBroadcastReceiver)
+ }
+
+ private val persistentBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == null ||
+ (intent.action != TorServiceConstants.ACTION_STATUS &&
+ intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
+ ) {
+ return
+ }
+ val action = intent.action
+
+ val logentry: String?
+ val status: String?
+ if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
+ logentry = intent.getExtras()
+ ?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
+ } else {
+ logentry = null
+ }
+
+ status = intent.getExtras()
+ ?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
+
+ if (logentry == null && status == null) {
+ return
+ }
+
+ onTorStatusUpdate(logentry, status)
+
+ if (status == null) {
+ return
+ }
+
+ val newStatus = TorStatus.fromString(status)
+
+ if (newStatus.isUnknown() && wasTorBootstrapped) {
+ stopTor()
+ }
+
+ entries.add(Pair(logentry, status))
+
+ if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
+ wasTorBootstrapped = true
+ onTorConnected()
+ }
+
+ if (lastKnownStatus.isStopping() && newStatus.isOff()) {
+ if (isTorRestarting) {
+ initiateTorBootstrap()
+ } else {
+ onTorStopped()
+ }
+ }
+
+ if (lastKnownStatus.isOff() && newStatus.isStarting()) {
+ isTorRestarting = false
+ }
+
+ lastKnownStatus = newStatus
+ }
+ }
+
+ override fun onTorConnecting() {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorConnecting() }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun onTorConnected() {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorConnected() }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun onTorStatusUpdate(entry: String?, status: String?) {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorStatusUpdate(entry, status) }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun onTorStopped() {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorStopped() }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun registerTorListener(l: TorEvents) {
+ if (torListeners.contains(l)) {
+ return
+ }
+
+ if (lockTorListenersMutation) {
+ pendingRegisterChangeList.add(Pair(l, true))
+ } else {
+ torListeners.add(l)
+ }
+ }
+
+ override fun unregisterTorListener(l: TorEvents) {
+ if (!torListeners.contains(l)) {
+ return
+ }
+
+ if (lockTorListenersMutation) {
+ pendingRegisterChangeList.add(Pair(l, false))
+ } else {
+ torListeners.remove(l)
+ }
+ }
+
+ private fun handlePendingRegistrationChanges() {
+ pendingRegisterChangeList.forEach {
+ if (it.second) {
+ registerTorListener(it.first)
+ } else {
+ unregisterTorListener(it.first)
+ }
+ }
+
+ pendingRegisterChangeList.clear()
+ }
+
+ /**
+ * Receive the current Tor status.
+ *
+ * Send a request for the current status and receive the response.
+ * Returns true if Tor is running, false otherwise.
+ *
+ */
+ private suspend fun checkTorIsStarted(): Boolean {
+ val channel = Channel<Boolean>()
+
+ // Register receiver
+ val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
+ val localBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.action ?: return
+ // We only want ACTION_STATUS messages
+ if (action != TorServiceConstants.ACTION_STATUS) {
+ return
+ }
+ // The current status has the EXTRA_STATUS key
+ val currentStatus =
+ intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
+ channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
+ }
+ }
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(TorServiceConstants.ACTION_STATUS)
+ )
+
+ // Request service status
+ sendServiceAction(TorServiceConstants.ACTION_STATUS)
+
+ // Wait for response and unregister receiver
+ var torIsStarted = false
+ withTimeoutOrNull(torServiceResponseTimeout) {
+ torIsStarted = channel.receive()
+ }
+ lbm.unregisterReceiver(localBroadcastReceiver)
+ return torIsStarted
+ }
+
+ override fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope?, withDebugLogging: Boolean) {
+ if (BuildConfig.DISABLE_TOR) {
+ return
+ }
+
+ context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
+ .edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
+
+ if (lifecycleScope == null) {
+ sendServiceAction(TorServiceConstants.ACTION_START)
+ } else {
+ lifecycleScope.launch {
+ val torNeedsStart = !checkTorIsStarted()
+ if (torNeedsStart) {
+ sendServiceAction(TorServiceConstants.ACTION_START)
+ }
+ }
+ }
+ }
+
+ override fun stopTor() {
+ if (BuildConfig.DISABLE_TOR) {
+ return
+ }
+
+ val torService = Intent(context, TorService::class.java)
+ context.stopService(torService)
+ }
+
+ override fun setTorStopped() {
+ lastKnownStatus = TorStatus.OFF
+ onTorStopped()
+ }
+
+ override fun restartTor() {
+ // tor-android-service doesn't dynamically update the torrc file,
+ // and it doesn't use SETCONF, so we completely restart the service.
+ // However, don't restart if we aren't started and we weren't
+ // previously started.
+ if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
+ return
+ }
+
+ if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
+ // If we aren't started, but we were previously bootstrapped,
+ // then we handle a "restart" request as a "start" restart
+ initiateTorBootstrap()
+ } else {
+ // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
+ // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
+ // service.
+ isTorRestarting = true
+ stopTor()
+ }
+ }
+
+ private fun sendServiceAction(action: String) {
+ val torServiceStatus = Intent(context, TorService::class.java)
+ torServiceStatus.action = action
+ context.startService(torServiceStatus)
+ }
+
+ companion object {
+ const val torServiceResponseTimeout = 5000L
+ }
+}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/6d0a64536f0208b482feefe64fe0b7e43214a96c
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/6d0a64536f0208b482feefe64fe0b7e43214a96c
You're receiving this email because of your account on gitlab.torproject.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tbb-commits/attachments/20240126/013055b6/attachment-0001.htm>
More information about the tbb-commits
mailing list