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
Matthew Finkel
fenix
Commits
b4d323b9
Commit
b4d323b9
authored
Sep 09, 2020
by
Matthew Finkel
Browse files
Bug 40028: Implement Tor Service controller
parent
e4626405
Changes
2
Show whitespace changes
Inline
Side-by-side
app/src/main/java/org/mozilla/fenix/HomeActivity.kt
View file @
b4d323b9
...
...
@@ -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 @
b4d323b9
/* 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