fix startup issues
This commit is contained in:
@@ -47,7 +47,13 @@ class TorProxyScreen extends HookConsumerWidget {
|
|||||||
if (next.requireValue.isRunning != previous?.value?.isRunning ||
|
if (next.requireValue.isRunning != previous?.value?.isRunning ||
|
||||||
next.requireValue.bootstrapProgress !=
|
next.requireValue.bootstrapProgress !=
|
||||||
previous?.value?.bootstrapProgress) {
|
previous?.value?.bootstrapProgress) {
|
||||||
torPendingRequest.value = null;
|
if (torPendingRequest.value == true) {
|
||||||
|
if (next.requireValue.bootstrapProgress > 0) {
|
||||||
|
torPendingRequest.value = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
torPendingRequest.value = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -58,9 +64,16 @@ class TorProxyScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final torIsBootstrapped = ref.watch(
|
||||||
|
torProxyServiceProvider.select(
|
||||||
|
(value) => value.value?.bootstrapProgress == 100,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final torIsBusy =
|
final torIsBusy =
|
||||||
torPendingRequest.value != null ||
|
torPendingRequest.value != null ||
|
||||||
bootstrapProgress > 0 && bootstrapProgress < 100;
|
bootstrapProgress > 0 && bootstrapProgress < 100;
|
||||||
|
|
||||||
final torSettings = ref.watch(torSettingsWithDefaultsProvider);
|
final torSettings = ref.watch(torSettingsWithDefaultsProvider);
|
||||||
|
|
||||||
useOnInitialization(() async {
|
useOnInitialization(() async {
|
||||||
@@ -446,7 +459,7 @@ class TorProxyScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (torIsBusy)
|
if (torPendingRequest.value != false && torIsBusy)
|
||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -463,7 +476,9 @@ class TorProxyScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
else if (torIsRunning)
|
else if (torPendingRequest.value != false &&
|
||||||
|
torIsRunning &&
|
||||||
|
torIsBootstrapped)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 24.0),
|
padding: const EdgeInsets.only(top: 24.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
+3
-1
@@ -23,7 +23,9 @@ class FlutterTorPlugin : FlutterPlugin, TorApi {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "FlutterTorPlugin"
|
private const val TAG = "FlutterTorPlugin"
|
||||||
private const val SERVICE_CONNECTION_TIMEOUT_MS = 10000L
|
// Increased timeout to 60 seconds to account for native TorService binding
|
||||||
|
// which can take 30+ seconds during initial setup
|
||||||
|
private const val SERVICE_CONNECTION_TIMEOUT_MS = 60000L
|
||||||
}
|
}
|
||||||
|
|
||||||
private var context: Context? = null
|
private var context: Context? = null
|
||||||
|
|||||||
-2
@@ -47,8 +47,6 @@ class LogStreamHandler(messenger: BinaryMessenger) {
|
|||||||
* @param status Current Tor status
|
* @param status Current Tor status
|
||||||
*/
|
*/
|
||||||
fun sendStatusChange(status: TorStatus) {
|
fun sendStatusChange(status: TorStatus) {
|
||||||
Log.d(TorManager.Companion.TAG, "sendStatusChange() returning: isRunning=${status.isRunning}, socksPort=${status.socksPort}, bootstrap=${status.bootstrapProgress}")
|
|
||||||
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
try {
|
try {
|
||||||
torLogApi.onStatusChanged(status) { }
|
torLogApi.onStatusChanged(status) { }
|
||||||
|
|||||||
@@ -134,10 +134,10 @@ class TorConfig(private val config: TorConfiguration) {
|
|||||||
append("DormantClientTimeout 10 minutes\n")
|
append("DormantClientTimeout 10 minutes\n")
|
||||||
append("DormantCanceledByStartup 1\n")
|
append("DormantCanceledByStartup 1\n")
|
||||||
|
|
||||||
// Note: DisableNetwork is set to 1 in defaults.torrc
|
// CRITICAL: Set DisableNetwork 1 to prevent bootstrap before event listener is ready
|
||||||
// It will be enabled via control port after setup completes (matching Orbot)
|
// This will be changed to 0 via control port AFTER we set up event listeners
|
||||||
// We DON'T set it here to avoid overriding the defaults.torrc setting
|
// This matches Orbot's approach and ensures we receive all bootstrap events
|
||||||
append("DisableNetwork 0\n")
|
append("DisableNetwork 1\n")
|
||||||
|
|
||||||
append("Log notice stdout\n") // Log to stdout for capture
|
append("Log notice stdout\n") // Log to stdout for capture
|
||||||
append("\n")
|
append("\n")
|
||||||
|
|||||||
+104
-41
@@ -40,13 +40,21 @@ class TorManager(
|
|||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
|
// Store event listener as a field to prevent garbage collection
|
||||||
|
private var torEventListener: TorEventListener? = null
|
||||||
|
|
||||||
var socksPort: Int = -1
|
var socksPort: Int = -1
|
||||||
private set
|
private set
|
||||||
// Note: No controlPort - tor-android uses ControlSocket (Unix domain socket) instead
|
// Note: No controlPort - tor-android uses ControlSocket (Unix domain socket) instead
|
||||||
// This is more secure than TCP ControlPort as it uses file permissions for access control
|
// This is more secure than TCP ControlPort as it uses file permissions for access control
|
||||||
|
@Volatile
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
|
@Volatile
|
||||||
private var bootstrapProgress = 0
|
private var bootstrapProgress = 0
|
||||||
|
|
||||||
|
// Lock for synchronizing status updates
|
||||||
|
private val statusLock = Any()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start Tor with the given configuration
|
* Start Tor with the given configuration
|
||||||
* @param config Tor configuration from Flutter
|
* @param config Tor configuration from Flutter
|
||||||
@@ -119,12 +127,10 @@ class TorManager(
|
|||||||
// Start TorService
|
// Start TorService
|
||||||
// Note: torrcFile is now written to the correct location via TorService.getTorrc()
|
// Note: torrcFile is now written to the correct location via TorService.getTorrc()
|
||||||
// so TorService will automatically find and use it
|
// so TorService will automatically find and use it
|
||||||
|
// Note: isRunning will be set to true in setupControlConnection() before network is enabled
|
||||||
|
// This ensures status is consistent when bootstrap events start arriving
|
||||||
startTorService()
|
startTorService()
|
||||||
|
|
||||||
isRunning = true
|
|
||||||
logHandler.notice("Tor started successfully")
|
|
||||||
sendStatusUpdate()
|
|
||||||
|
|
||||||
socksPort
|
socksPort
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to start Tor", e)
|
Log.e(TAG, "Failed to start Tor", e)
|
||||||
@@ -190,7 +196,7 @@ class TorManager(
|
|||||||
controlConnection = conn
|
controlConnection = conn
|
||||||
setupControlConnection(conn)
|
setupControlConnection(conn)
|
||||||
if (continuation.isActive) {
|
if (continuation.isActive) {
|
||||||
continuation.resume(Unit) {}
|
continuation.resume(Unit)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val error = Exception("Failed to get control connection after 30 seconds")
|
val error = Exception("Failed to get control connection after 30 seconds")
|
||||||
@@ -205,6 +211,7 @@ class TorManager(
|
|||||||
Log.w(TAG, "TorService disconnected")
|
Log.w(TAG, "TorService disconnected")
|
||||||
torService = null
|
torService = null
|
||||||
controlConnection = null
|
controlConnection = null
|
||||||
|
torEventListener = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,11 +232,23 @@ class TorManager(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup control connection and event listeners
|
* Setup control connection and event listeners
|
||||||
|
* This follows Orbot's approach: query ports first, then set up events, then enable network
|
||||||
*/
|
*/
|
||||||
private fun setupControlConnection(conn: TorControlConnection) {
|
private fun setupControlConnection(conn: TorControlConnection) {
|
||||||
try {
|
try {
|
||||||
// Add event listener
|
// Query control connection to verify SOCKS port (like Orbot's initControlConnection)
|
||||||
conn.addRawEventListener(TorEventListener())
|
// This also properly initializes the control connection for event delivery
|
||||||
|
try {
|
||||||
|
conn.getInfo("net/listeners/socks")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Could not query SOCKS port from control connection", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
logHandler.notice("Connected to Tor control port")
|
||||||
|
|
||||||
|
// Create and store event listener instance (prevents garbage collection)
|
||||||
|
torEventListener = TorEventListener()
|
||||||
|
conn.addRawEventListener(torEventListener)
|
||||||
|
|
||||||
// Subscribe to events (matching Orbot's event subscriptions)
|
// Subscribe to events (matching Orbot's event subscriptions)
|
||||||
conn.setEvents(listOf(
|
conn.setEvents(listOf(
|
||||||
@@ -243,14 +262,28 @@ class TorManager(
|
|||||||
TorControlCommands.EVENT_ADDRMAP
|
TorControlCommands.EVENT_ADDRMAP
|
||||||
))
|
))
|
||||||
|
|
||||||
// Enable network now that configuration is complete (like Orbot does)
|
Log.d(TAG, "Control connection setup complete, enabling network")
|
||||||
conn.setConf("DisableNetwork", "0")
|
|
||||||
|
|
||||||
Log.d(TAG, "Control connection setup complete")
|
// Set isRunning=true BEFORE enabling network so status is consistent
|
||||||
logHandler.notice("Connected to Tor control port")
|
// when bootstrap events start arriving
|
||||||
|
synchronized(statusLock) {
|
||||||
|
isRunning = true
|
||||||
|
}
|
||||||
|
logHandler.notice("Tor started successfully")
|
||||||
|
sendStatusUpdate()
|
||||||
|
|
||||||
|
// Enable network now that configuration is complete (like Orbot does)
|
||||||
|
// This will trigger bootstrap events to start
|
||||||
|
conn.setConf("DisableNetwork", "0")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to setup control connection", e)
|
Log.e(TAG, "Failed to setup control connection", e)
|
||||||
logHandler.error("Control connection error: ${e.message}")
|
logHandler.error("Control connection error: ${e.message}")
|
||||||
|
// Reset isRunning on failure
|
||||||
|
synchronized(statusLock) {
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
torEventListener = null
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,15 +295,19 @@ class TorManager(
|
|||||||
logHandler.notice("Stopping Tor...")
|
logHandler.notice("Stopping Tor...")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Shutdown Tor gracefully
|
// DON'T call shutdownTor() here - let the service's onDestroy() handle it
|
||||||
controlConnection?.shutdownTor("SHUTDOWN")
|
// Otherwise we get a broken pipe error when service tries to shutdown again
|
||||||
delay(1000) // Give Tor time to shutdown
|
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
// Give the native service time to fully stop before potential restart
|
||||||
|
// This is CRITICAL to prevent binding to a stale service instance
|
||||||
|
delay(2000)
|
||||||
|
|
||||||
logHandler.notice("Tor stopped")
|
logHandler.notice("Tor stopped")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error stopping Tor", e)
|
Log.e(TAG, "Error stopping Tor", e)
|
||||||
cleanup()
|
cleanup()
|
||||||
|
delay(2000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,32 +315,57 @@ class TorManager(
|
|||||||
* Cleanup resources
|
* Cleanup resources
|
||||||
*/
|
*/
|
||||||
private fun cleanup() {
|
private fun cleanup() {
|
||||||
isRunning = false
|
synchronized(statusLock) {
|
||||||
bootstrapProgress = 0
|
isRunning = false
|
||||||
socksPort = -1
|
bootstrapProgress = 0
|
||||||
|
socksPort = -1
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
controlConnection?.let {
|
// Unsubscribe from all events before removing listener
|
||||||
// Don't shutdown again, just close
|
if (controlConnection != null) {
|
||||||
|
try {
|
||||||
|
controlConnection?.setEvents(emptyList())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Connection may already be closed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove event listener
|
||||||
|
if (torEventListener != null && controlConnection != null) {
|
||||||
|
try {
|
||||||
|
controlConnection?.removeRawEventListener(torEventListener)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Connection may already be closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
torEventListener = null
|
||||||
controlConnection = null
|
controlConnection = null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Error closing control connection", e)
|
Log.w(TAG, "Error closing control connection", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Service cleanup
|
||||||
torServiceConnection?.let {
|
runBlocking(Dispatchers.Main) {
|
||||||
context.unbindService(it)
|
try {
|
||||||
|
torServiceConnection?.let {
|
||||||
|
context.unbindService(it)
|
||||||
|
}
|
||||||
|
torServiceConnection = null
|
||||||
|
|
||||||
|
// Small delay to ensure unbind completes before stopping service
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
// Stop the native TorService to ensure clean restart
|
||||||
|
val intent = Intent(context, org.torproject.jni.TorService::class.java)
|
||||||
|
context.stopService(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Error unbinding TorService", e)
|
||||||
}
|
}
|
||||||
torServiceConnection = null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Error unbinding TorService", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
torService = null
|
torService = null
|
||||||
|
|
||||||
pluggableTransportManager.stopAll()
|
pluggableTransportManager.stopAll()
|
||||||
|
|
||||||
sendStatusUpdate()
|
sendStatusUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,18 +388,19 @@ class TorManager(
|
|||||||
* Get current Tor status
|
* Get current Tor status
|
||||||
*/
|
*/
|
||||||
fun getStatus(): TorStatus {
|
fun getStatus(): TorStatus {
|
||||||
val status = TorStatus(
|
synchronized(statusLock) {
|
||||||
isRunning = isRunning,
|
val status = TorStatus(
|
||||||
socksPort = if (isRunning) socksPort.toLong() else null,
|
isRunning = isRunning,
|
||||||
bootstrapProgress = bootstrapProgress.toLong(),
|
socksPort = if (isRunning) socksPort.toLong() else null,
|
||||||
currentCircuit = null, // TODO: track current circuit
|
bootstrapProgress = bootstrapProgress.toLong(),
|
||||||
exitNodeCountry = null // TODO: track exit node country
|
currentCircuit = null, // TODO: track current circuit
|
||||||
)
|
exitNodeCountry = null // TODO: track exit node country
|
||||||
|
)
|
||||||
|
|
||||||
Log.d(TAG, "getStatus() returning: isRunning=$isRunning, socksPort=$socksPort, bootstrap=$bootstrapProgress")
|
Log.d(TAG, "getStatus() returning: isRunning=$isRunning, socksPort=$socksPort, bootstrap=$bootstrapProgress")
|
||||||
// logHandler.sendStatusChange(status)
|
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -352,13 +415,13 @@ class TorManager(
|
|||||||
*/
|
*/
|
||||||
private inner class TorEventListener : RawEventListener {
|
private inner class TorEventListener : RawEventListener {
|
||||||
override fun onEvent(eventType: String, eventData: String) {
|
override fun onEvent(eventType: String, eventData: String) {
|
||||||
Log.d(TAG, "Tor event: $eventType - $eventData")
|
|
||||||
|
|
||||||
// Handle bootstrap progress (comes in NOTICE events)
|
// Handle bootstrap progress (comes in NOTICE events)
|
||||||
if (eventData.contains("Bootstrapped")) {
|
if (eventData.contains("Bootstrapped")) {
|
||||||
val progress = extractBootstrapProgress(eventData)
|
val progress = extractBootstrapProgress(eventData)
|
||||||
if (progress >= 0) {
|
if (progress >= 0) {
|
||||||
bootstrapProgress = progress
|
synchronized(statusLock) {
|
||||||
|
bootstrapProgress = progress
|
||||||
|
}
|
||||||
sendStatusUpdate()
|
sendStatusUpdate()
|
||||||
|
|
||||||
if (progress == 100) {
|
if (progress == 100) {
|
||||||
|
|||||||
Reference in New Issue
Block a user