fix startup issues
This commit is contained in:
@@ -47,7 +47,13 @@ class TorProxyScreen extends HookConsumerWidget {
|
||||
if (next.requireValue.isRunning != previous?.value?.isRunning ||
|
||||
next.requireValue.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 =
|
||||
torPendingRequest.value != null ||
|
||||
bootstrapProgress > 0 && bootstrapProgress < 100;
|
||||
|
||||
final torSettings = ref.watch(torSettingsWithDefaultsProvider);
|
||||
|
||||
useOnInitialization(() async {
|
||||
@@ -446,7 +459,7 @@ class TorProxyScreen extends HookConsumerWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (torIsBusy)
|
||||
if (torPendingRequest.value != false && torIsBusy)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -463,7 +476,9 @@ class TorProxyScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
)
|
||||
else if (torIsRunning)
|
||||
else if (torPendingRequest.value != false &&
|
||||
torIsRunning &&
|
||||
torIsBootstrapped)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Row(
|
||||
|
||||
+3
-1
@@ -23,7 +23,9 @@ class FlutterTorPlugin : FlutterPlugin, TorApi {
|
||||
|
||||
companion object {
|
||||
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
|
||||
|
||||
-2
@@ -47,8 +47,6 @@ class LogStreamHandler(messenger: BinaryMessenger) {
|
||||
* @param status Current Tor status
|
||||
*/
|
||||
fun sendStatusChange(status: TorStatus) {
|
||||
Log.d(TorManager.Companion.TAG, "sendStatusChange() returning: isRunning=${status.isRunning}, socksPort=${status.socksPort}, bootstrap=${status.bootstrapProgress}")
|
||||
|
||||
mainHandler.post {
|
||||
try {
|
||||
torLogApi.onStatusChanged(status) { }
|
||||
|
||||
@@ -134,10 +134,10 @@ class TorConfig(private val config: TorConfiguration) {
|
||||
append("DormantClientTimeout 10 minutes\n")
|
||||
append("DormantCanceledByStartup 1\n")
|
||||
|
||||
// Note: DisableNetwork is set to 1 in defaults.torrc
|
||||
// It will be enabled via control port after setup completes (matching Orbot)
|
||||
// We DON'T set it here to avoid overriding the defaults.torrc setting
|
||||
append("DisableNetwork 0\n")
|
||||
// CRITICAL: Set DisableNetwork 1 to prevent bootstrap before event listener is ready
|
||||
// This will be changed to 0 via control port AFTER we set up event listeners
|
||||
// This matches Orbot's approach and ensures we receive all bootstrap events
|
||||
append("DisableNetwork 1\n")
|
||||
|
||||
append("Log notice stdout\n") // Log to stdout for capture
|
||||
append("\n")
|
||||
|
||||
+104
-41
@@ -40,13 +40,21 @@ class TorManager(
|
||||
|
||||
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
|
||||
private set
|
||||
// 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
|
||||
@Volatile
|
||||
private var isRunning = false
|
||||
@Volatile
|
||||
private var bootstrapProgress = 0
|
||||
|
||||
// Lock for synchronizing status updates
|
||||
private val statusLock = Any()
|
||||
|
||||
/**
|
||||
* Start Tor with the given configuration
|
||||
* @param config Tor configuration from Flutter
|
||||
@@ -119,12 +127,10 @@ class TorManager(
|
||||
// Start TorService
|
||||
// Note: torrcFile is now written to the correct location via TorService.getTorrc()
|
||||
// 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()
|
||||
|
||||
isRunning = true
|
||||
logHandler.notice("Tor started successfully")
|
||||
sendStatusUpdate()
|
||||
|
||||
socksPort
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start Tor", e)
|
||||
@@ -190,7 +196,7 @@ class TorManager(
|
||||
controlConnection = conn
|
||||
setupControlConnection(conn)
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(Unit) {}
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
} else {
|
||||
val error = Exception("Failed to get control connection after 30 seconds")
|
||||
@@ -205,6 +211,7 @@ class TorManager(
|
||||
Log.w(TAG, "TorService disconnected")
|
||||
torService = null
|
||||
controlConnection = null
|
||||
torEventListener = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,11 +232,23 @@ class TorManager(
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
try {
|
||||
// Add event listener
|
||||
conn.addRawEventListener(TorEventListener())
|
||||
// Query control connection to verify SOCKS port (like Orbot's initControlConnection)
|
||||
// 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)
|
||||
conn.setEvents(listOf(
|
||||
@@ -243,14 +262,28 @@ class TorManager(
|
||||
TorControlCommands.EVENT_ADDRMAP
|
||||
))
|
||||
|
||||
// Enable network now that configuration is complete (like Orbot does)
|
||||
conn.setConf("DisableNetwork", "0")
|
||||
Log.d(TAG, "Control connection setup complete, enabling network")
|
||||
|
||||
Log.d(TAG, "Control connection setup complete")
|
||||
logHandler.notice("Connected to Tor control port")
|
||||
// Set isRunning=true BEFORE enabling network so status is consistent
|
||||
// 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) {
|
||||
Log.e(TAG, "Failed to setup control connection", e)
|
||||
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...")
|
||||
|
||||
try {
|
||||
// Shutdown Tor gracefully
|
||||
controlConnection?.shutdownTor("SHUTDOWN")
|
||||
delay(1000) // Give Tor time to shutdown
|
||||
|
||||
// DON'T call shutdownTor() here - let the service's onDestroy() handle it
|
||||
// Otherwise we get a broken pipe error when service tries to shutdown again
|
||||
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")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error stopping Tor", e)
|
||||
cleanup()
|
||||
delay(2000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,32 +315,57 @@ class TorManager(
|
||||
* Cleanup resources
|
||||
*/
|
||||
private fun cleanup() {
|
||||
isRunning = false
|
||||
bootstrapProgress = 0
|
||||
socksPort = -1
|
||||
synchronized(statusLock) {
|
||||
isRunning = false
|
||||
bootstrapProgress = 0
|
||||
socksPort = -1
|
||||
}
|
||||
|
||||
try {
|
||||
controlConnection?.let {
|
||||
// Don't shutdown again, just close
|
||||
// Unsubscribe from all events before removing listener
|
||||
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
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error closing control connection", e)
|
||||
}
|
||||
|
||||
try {
|
||||
torServiceConnection?.let {
|
||||
context.unbindService(it)
|
||||
// Service cleanup
|
||||
runBlocking(Dispatchers.Main) {
|
||||
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
|
||||
|
||||
pluggableTransportManager.stopAll()
|
||||
|
||||
sendStatusUpdate()
|
||||
}
|
||||
|
||||
@@ -326,18 +388,19 @@ class TorManager(
|
||||
* Get current Tor status
|
||||
*/
|
||||
fun getStatus(): TorStatus {
|
||||
val status = TorStatus(
|
||||
isRunning = isRunning,
|
||||
socksPort = if (isRunning) socksPort.toLong() else null,
|
||||
bootstrapProgress = bootstrapProgress.toLong(),
|
||||
currentCircuit = null, // TODO: track current circuit
|
||||
exitNodeCountry = null // TODO: track exit node country
|
||||
)
|
||||
synchronized(statusLock) {
|
||||
val status = TorStatus(
|
||||
isRunning = isRunning,
|
||||
socksPort = if (isRunning) socksPort.toLong() else null,
|
||||
bootstrapProgress = bootstrapProgress.toLong(),
|
||||
currentCircuit = null, // TODO: track current circuit
|
||||
exitNodeCountry = null // TODO: track exit node country
|
||||
)
|
||||
|
||||
Log.d(TAG, "getStatus() returning: isRunning=$isRunning, socksPort=$socksPort, bootstrap=$bootstrapProgress")
|
||||
// logHandler.sendStatusChange(status)
|
||||
Log.d(TAG, "getStatus() returning: isRunning=$isRunning, socksPort=$socksPort, bootstrap=$bootstrapProgress")
|
||||
|
||||
return status
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,13 +415,13 @@ class TorManager(
|
||||
*/
|
||||
private inner class TorEventListener : RawEventListener {
|
||||
override fun onEvent(eventType: String, eventData: String) {
|
||||
Log.d(TAG, "Tor event: $eventType - $eventData")
|
||||
|
||||
// Handle bootstrap progress (comes in NOTICE events)
|
||||
if (eventData.contains("Bootstrapped")) {
|
||||
val progress = extractBootstrapProgress(eventData)
|
||||
if (progress >= 0) {
|
||||
bootstrapProgress = progress
|
||||
synchronized(statusLock) {
|
||||
bootstrapProgress = progress
|
||||
}
|
||||
sendStatusUpdate()
|
||||
|
||||
if (progress == 100) {
|
||||
|
||||
Reference in New Issue
Block a user