Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ class LanternVpnService :

private var mInterface: ParcelFileDescriptor? = null

/**
* Safely close the TUN interface file descriptor.
* Synchronized to prevent double-close from concurrent callers
* (onDestroy, postServiceClose, doStopVPN can all race).
*/
@Synchronized
private fun closeTunInterface() {
try {
mInterface?.close()
} catch (e: Exception) {
AppLogger.e(TAG, "Error closing TUN interface", e)
} finally {
mInterface = null
}
}

// Create a CoroutineScope tied to the service's lifecycle.
// SupervisorJob ensures that failure in one child doesn't cancel the whole scope.
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
Expand Down Expand Up @@ -124,6 +140,11 @@ class LanternVpnService :

override fun onDestroy() {
try {
// Close TUN fd synchronously BEFORE cancelling scope to prevent orphaned TUN.
// Without this, destroy() -> doStopVPN() launches a coroutine that gets
// immediately cancelled by serviceScope.cancel(), leaving the TUN open and
// routing all traffic into a black hole.
closeTunInterface()
destroy()
} finally {
serviceScope.cancel()
Expand All @@ -146,8 +167,7 @@ class LanternVpnService :

override fun postServiceClose() {
AppLogger.i(TAG, "postServiceClose called")
mInterface = null

closeTunInterface()
}

override fun restartService() {
Expand Down Expand Up @@ -263,8 +283,7 @@ class LanternVpnService :
VpnStatusManager.postVPNStatus(VPNStatus.Disconnecting)
serviceScope.launch {
try {
mInterface?.close()
mInterface = null
closeTunInterface()
runCatching { Mobile.stopVPN() }
.onFailure { e -> AppLogger.e(TAG, "Mobile.stopVPN() failed", e) }

Expand Down
Loading