retry on bind error (#345)

This commit is contained in:
callebtc
2025-08-29 21:10:39 +02:00
committed by GitHub
parent 3248f37932
commit 85ddf3ca38
@@ -41,9 +41,9 @@ object TorManager {
private val applyMutex = Mutex() private val applyMutex = Mutex()
@Volatile private var desiredMode: TorMode = TorMode.OFF @Volatile private var desiredMode: TorMode = TorMode.OFF
@Volatile private var currentSocksPort: Int = DEFAULT_SOCKS_PORT @Volatile private var currentSocksPort: Int = DEFAULT_SOCKS_PORT
@Volatile private var nextSocksPort: Int = DEFAULT_SOCKS_PORT
@Volatile private var lastLogTime = AtomicLong(0L) @Volatile private var lastLogTime = AtomicLong(0L)
@Volatile private var retryAttempts = 0 @Volatile private var retryAttempts = 0
@Volatile private var bindRetryAttempts = 0
private var inactivityJob: Job? = null private var inactivityJob: Job? = null
private var retryJob: Job? = null private var retryJob: Job? = null
private var currentApplication: Application? = null private var currentApplication: Application? = null
@@ -107,7 +107,8 @@ object TorManager {
stopArti() stopArti()
socksAddr = null socksAddr = null
_status.value = _status.value.copy(mode = TorMode.OFF, running = false, bootstrapPercent = 0) _status.value = _status.value.copy(mode = TorMode.OFF, running = false, bootstrapPercent = 0)
nextSocksPort = DEFAULT_SOCKS_PORT currentSocksPort = DEFAULT_SOCKS_PORT
bindRetryAttempts = 0
lifecycleState = LifecycleState.STOPPED lifecycleState = LifecycleState.STOPPED
// Rebuild clients WITHOUT proxy and reconnect relays // Rebuild clients WITHOUT proxy and reconnect relays
try { try {
@@ -117,7 +118,11 @@ object TorManager {
} }
TorMode.ON -> { TorMode.ON -> {
Log.i(TAG, "applyMode: ON -> starting arti") Log.i(TAG, "applyMode: ON -> starting arti")
nextSocksPort = if (currentSocksPort < DEFAULT_SOCKS_PORT) DEFAULT_SOCKS_PORT else currentSocksPort // Reset port to default unless we're already using a higher port
if (currentSocksPort < DEFAULT_SOCKS_PORT) {
currentSocksPort = DEFAULT_SOCKS_PORT
}
bindRetryAttempts = 0
lifecycleState = LifecycleState.STARTING lifecycleState = LifecycleState.STARTING
// For OFF->ON, no delay needed // For OFF->ON, no delay needed
val needsDelay = s.mode == TorMode.ISOLATION val needsDelay = s.mode == TorMode.ISOLATION
@@ -136,7 +141,11 @@ object TorManager {
} }
TorMode.ISOLATION -> { TorMode.ISOLATION -> {
Log.i(TAG, "applyMode: ISOLATION -> starting arti") Log.i(TAG, "applyMode: ISOLATION -> starting arti")
nextSocksPort = if (currentSocksPort < DEFAULT_SOCKS_PORT) DEFAULT_SOCKS_PORT else currentSocksPort // Reset port to default unless we're already using a higher port
if (currentSocksPort < DEFAULT_SOCKS_PORT) {
currentSocksPort = DEFAULT_SOCKS_PORT
}
bindRetryAttempts = 0
lifecycleState = LifecycleState.STARTING lifecycleState = LifecycleState.STARTING
// For ON->ISOLATION, immediate status change and delay // For ON->ISOLATION, immediate status change and delay
_status.value = _status.value.copy(running = false, bootstrapPercent = 0) _status.value = _status.value.copy(running = false, bootstrapPercent = 0)
@@ -164,15 +173,11 @@ object TorManager {
try { try {
stopArtiInternal() stopArtiInternal()
Log.i(TAG, "Starting Arti…") Log.i(TAG, "Starting Arti on port $currentSocksPort")
if (useDelay) { if (useDelay) {
delay(RESTART_DELAY_MS) delay(RESTART_DELAY_MS)
} }
// Determine port
val port = nextSocksPort
currentSocksPort = port
val logListener = ArtiLogListener { logLine -> val logListener = ArtiLogListener { logLine ->
val text = logLine ?: return@ArtiLogListener val text = logLine ?: return@ArtiLogListener
val s = text.toString() val s = text.toString()
@@ -185,13 +190,14 @@ object TorManager {
) { ) {
_status.value = _status.value.copy(bootstrapPercent = 100) _status.value = _status.value.copy(bootstrapPercent = 100)
retryAttempts = 0 // Reset retry attempts on successful bootstrap retryAttempts = 0 // Reset retry attempts on successful bootstrap
bindRetryAttempts = 0 // Reset bind retry attempts on successful bootstrap
startInactivityMonitoring() startInactivityMonitoring()
} }
} }
val proxy = ArtiProxy.Builder(application) val proxy = ArtiProxy.Builder(application)
.setSocksPort(port) .setSocksPort(currentSocksPort)
.setDnsPort(port + 1) .setDnsPort(currentSocksPort + 1)
.setLogListener(logListener) .setLogListener(logListener)
.build() .build()
@@ -204,11 +210,39 @@ object TorManager {
startInactivityMonitoring() startInactivityMonitoring()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error starting Arti: ${e.message}") Log.e(TAG, "Error starting Arti on port $currentSocksPort: ${e.message}")
scheduleRetry(application, isolation)
// Check if this is a bind error
val isBindError = isBindError(e)
if (isBindError && bindRetryAttempts < MAX_RETRY_ATTEMPTS) {
bindRetryAttempts++
currentSocksPort++
Log.w(TAG, "Port bind failed (attempt $bindRetryAttempts/$MAX_RETRY_ATTEMPTS), retrying with port $currentSocksPort")
// Immediate retry with incremented port, no exponential backoff for bind errors
startArti(application, isolation, useDelay = false)
} else if (isBindError) {
Log.e(TAG, "Max bind retry attempts reached ($MAX_RETRY_ATTEMPTS), giving up")
lifecycleState = LifecycleState.STOPPED
_status.value = _status.value.copy(running = false, bootstrapPercent = 0)
} else {
// For non-bind errors, use the existing retry mechanism
scheduleRetry(application, isolation)
}
} }
} }
/**
* Checks if the exception indicates a port binding failure
*/
private fun isBindError(exception: Exception): Boolean {
val message = exception.message?.lowercase() ?: ""
return message.contains("bind") ||
message.contains("address already in use") ||
message.contains("port") && message.contains("use") ||
message.contains("permission denied") && message.contains("port") ||
message.contains("could not bind")
}
private fun stopArtiInternal() { private fun stopArtiInternal() {
try { try {
val proxy = artiProxyRef.getAndSet(null) val proxy = artiProxyRef.getAndSet(null)