Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
The Tor Project
Applications
fenix
Commits
41bc4134
Commit
41bc4134
authored
Sep 09, 2020
by
Matthew Finkel
Browse files
Bug 40028: Implement Tor Service controller
parent
49bea73a
Changes
2
Hide whitespace changes
Inline
Side-by-side
app/src/main/java/org/mozilla/fenix/HomeActivity.kt
View file @
41bc4134
...
...
@@ -40,8 +40,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import
kotlinx.coroutines.Job
import
kotlinx.coroutines.delay
import
kotlinx.coroutines.launch
import
kotlinx.coroutines.withTimeoutOrNull
import
kotlinx.coroutines.channels.Channel
import
mozilla.components.browser.search.SearchEngine
import
mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import
mozilla.components.browser.state.state.SessionState
...
...
@@ -275,63 +273,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components
.
appStartupTelemetry
.
onHomeActivityOnRestart
(
rootContainer
)
}
/**
* 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
(
this
@HomeActivity
)
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
.
offer
(
currentStatus
===
TorServiceConstants
.
STATUS_ON
)
}
}
lbm
.
registerReceiver
(
localBroadcastReceiver
,
IntentFilter
(
TorServiceConstants
.
ACTION_STATUS
)
)
// Request service status
val
torServiceStatus
=
Intent
(
this
@HomeActivity
,
TorService
::
class
.
java
)
torServiceStatus
.
action
=
TorServiceConstants
.
ACTION_STATUS
startService
(
torServiceStatus
)
// Wait for response and unregister receiver
var
torIsStarted
=
false
withTimeoutOrNull
(
timeout
)
{
torIsStarted
=
channel
.
receive
()
}
lbm
.
unregisterReceiver
(
localBroadcastReceiver
)
return
torIsStarted
}
@CallSuper
override
fun
onResume
()
{
if
(!
BuildConfig
.
DISABLE_TOR
)
{
lifecycleScope
.
launch
{
val
torNeedsStart
=
!
checkTorIsStarted
()
if
(
torNeedsStart
)
{
val
torServiceStatus
=
Intent
(
this
@HomeActivity
,
TorService
::
class
.
java
)
torServiceStatus
.
action
=
TorServiceConstants
.
ACTION_START
startService
(
torServiceStatus
)
}
}
}
super
.
onResume
()
// Diagnostic breadcrumb for "Display already aquired" crash:
...
...
@@ -437,13 +380,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
)
privateNotificationObserver
?.
stop
()
if
(
BuildConfig
.
DISABLE_TOR
)
{
return
}
val
torService
=
Intent
(
this
,
TorService
::
class
.
java
)
stopService
(
torService
)
}
override
fun
onConfigurationChanged
(
newConfig
:
Configuration
)
{
...
...
@@ -933,7 +869,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
const
val
EXTRA_DELETE_PRIVATE_TABS
=
"notification_delete_and_open"
const
val
EXTRA_OPENED_FROM_NOTIFICATION
=
"notification_open"
const
val
START_IN_RECENTS_SCREEN
=
"start_in_recents_screen"
const
val
timeout
=
5000L
// PWA must have been used within last 30 days to be considered "recently used" for the
// telemetry purposes.
...
...
app/src/main/java/org/mozilla/fenix/tor/TorController.kt
0 → 100644
View file @
41bc4134
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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
()
fun
onTorConnected
()
fun
onTorStatusUpdate
(
entry
:
String
?,
status
:
String
?)
fun
onTorStopped
()
}
private
enum
class
TorStatus
{
OFF
,
STARTING
,
ON
,
STOPPING
,
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
}
}
fun
isOff
()
=
this
==
OFF
fun
isOn
()
=
this
==
ON
fun
isStarting
()
=
this
==
STARTING
fun
isStarted
()
=
((
this
==
TorStatus
.
STARTING
)
||
(
this
==
TorStatus
.
ON
))
fun
isStopping
()
=
this
==
STOPPING
fun
isUnknown
()
=
this
==
UNKNOWN
}
@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
)
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
)
}
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
=
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
()
}
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
.
offer
(
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
}
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
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment