[tor-commits] [tor-browser] 51/85: fixup! Bug 40597: Implement TorSettings module
gitolite role
git at cupani.torproject.org
Fri Jun 10 17:01:42 UTC 2022
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2
in repository tor-browser.
commit 91ad24bac8bf814874739e655819e80b5d1ae531
Author: Pier Angelo Vendrame <pierov at torproject.org>
AuthorDate: Fri Apr 8 15:24:50 2022 +0200
fixup! Bug 40597: Implement TorSettings module
Changes introduced by !275
---
browser/modules/Moat.jsm | 37 +++++++-
browser/modules/TorConnect.jsm | 210 +++++++++++++++++++++++++++++++++--------
2 files changed, 206 insertions(+), 41 deletions(-)
diff --git a/browser/modules/Moat.jsm b/browser/modules/Moat.jsm
index 2995a9148f0a3..35931172f3ee4 100644
--- a/browser/modules/Moat.jsm
+++ b/browser/modules/Moat.jsm
@@ -393,7 +393,7 @@ class InternetTestResponseListener {
}
// callers wait on this for final response
- status() {
+ get status() {
return this._promise;
}
@@ -406,8 +406,18 @@ class InternetTestResponseListener {
statuses = {
components: status,
successful: Components.isSuccessCode(status),
- http: request.responseStatus,
};
+ try {
+ if (statuses.successful) {
+ statuses.http = request.responseStatus;
+ statuses.date = request.getResponseHeader("Date");
+ }
+ } catch (err) {
+ console.warn(
+ "Successful request, but could not get the HTTP status or date",
+ err
+ );
+ }
} catch (err) {
this._reject(err);
}
@@ -549,7 +559,7 @@ class MoatRPC {
const listener = new InternetTestResponseListener();
await ch.asyncOpen(listener, ch);
- return listener.status();
+ return listener.status;
}
//
@@ -777,4 +787,25 @@ class MoatRPC {
return map;
}
+
+ // Request a copy of the defaul/fallback bridge settings, takes the following parameters:
+ // - transports: optional, an array of transports available to the client; if empty (or not
+ // given) returns settings using all working transports known to the server
+ //
+ // returns an array of settings objects in roughly the same format as the _settings
+ // object on the TorSettings module
+ async circumvention_defaults(transports) {
+ const args = {
+ transports: transports ? transports : [],
+ };
+ const response = await this._makeRequest("circumvention/defaults", args);
+ if ("errors" in response) {
+ const code = response.errors[0].code;
+ const detail = response.errors[0].detail;
+ throw new Error(`MoatRPC: ${detail} (${code})`);
+ } else if ("settings" in response) {
+ return this._fixupSettingsList(response.settings);
+ }
+ return [];
+ }
}
diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm
index 13c1f54d2ee97..711134326a14d 100644
--- a/browser/modules/TorConnect.jsm
+++ b/browser/modules/TorConnect.jsm
@@ -1,12 +1,12 @@
"use strict";
-var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState", "TorCensorshipLevel"];
+var EXPORTED_SYMBOLS = ["InternetStatus", "TorConnect", "TorConnectTopics", "TorConnectState"];
const { Services } = ChromeUtils.import(
"resource://gre/modules/Services.jsm"
);
-const { setTimeout } = ChromeUtils.import(
+const { setTimeout, clearTimeout } = ChromeUtils.import(
"resource://gre/modules/Timer.jsm"
);
@@ -61,17 +61,6 @@ const TorConnectState = Object.freeze({
Disabled: "Disabled",
});
-const TorCensorshipLevel = Object.freeze({
- /* No censorship detected */
- None: 0,
- /* Moderate censorship detected, autobootstrap may evade it */
- Moderate: 1,
- /* Severe censorship detected, but connection may still succeed */
- Severe: 2,
- /* Extreme censorship detected, connection will always end in an error */
- Extreme: 3,
-});
-
/*
TorConnect State Transitions
@@ -225,13 +214,98 @@ const debug_sleep = async (ms) => {
});
}
+const InternetStatus = Object.freeze({
+ Unknown: -1,
+ Offline: 0,
+ Online: 1,
+});
+
+class InternetTest {
+ constructor() {
+ this._status = InternetStatus.Unknown;
+ this._error = null;
+ this._pending = false;
+ this._timeout = setTimeout(() => {
+ this._timeout = null;
+ this.test();
+ }, this.timeoutRand());
+ this.onResult = (online, date) => {}
+ this.onError = (err) => {};
+ }
+
+ test() {
+ if (this._pending) {
+ return;
+ }
+ this.cancel();
+ this._pending = true;
+
+ this._testAsync()
+ .then((status) => {
+ this._pending = false;
+ this._status = status.successful ? InternetStatus.Online : InternetStatus.Offline;
+ this.onResult(this.status, status.date);
+ })
+ .catch(error => {
+ this._error = error;
+ this._pending = false;
+ this.onError(error);
+ });
+ }
+
+ cancel() {
+ if (this._timeout !== null) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+ }
+
+ async _testAsync() {
+ // Callbacks for the Internet test are desirable, because we will be
+ // waiting both for the bootstrap, and for the Internet test.
+ // However, managing Moat with async/await is much easier as it avoids a
+ // callback hell, and it makes extra explicit that we are uniniting it.
+ const mrpc = new MoatRPC();
+ let status = null;
+ let error = null;
+ try {
+ await mrpc.init();
+ status = await mrpc.testInternetConnection();
+ } catch (err) {
+ console.error("Error while checking the Internet connection", err);
+ error = err;
+ } finally {
+ mrpc.uninit();
+ }
+ if (error !== null) {
+ throw error;
+ }
+ return status;
+ }
+
+ get status() {
+ return this._status;
+ }
+
+ get error() {
+ return this._error;
+ }
+
+ // We randomize the Internet test timeout to make fingerprinting it harder, at least a little bit...
+ timeoutRand() {
+ const offset = 30000;
+ const randRange = 5000;
+ return offset + randRange * (Math.random() * 2 - 1);
+ }
+}
+
const TorConnect = (() => {
let retval = {
_state: TorConnectState.Initial,
_bootstrapProgress: 0,
_bootstrapStatus: null,
- _detectedCensorshiplevel: TorCensorshipLevel.None,
+ _internetStatus: InternetStatus.Unknown,
// list of country codes Moat has settings for
_countryCodes: [],
_countryNames: Object.freeze((() => {
@@ -246,8 +320,13 @@ const TorConnect = (() => {
_errorMessage: null,
_errorDetails: null,
_logHasWarningOrError: false,
+ _hasBootstrapEverFailed: false,
_transitionPromise: null,
+ // This is used as a helper to make the state of about:torconnect persistent
+ // during a session, but TorConnect does not use this data at all.
+ _uiState: {},
+
/* These functions represent ongoing work associated with one of our states
Some of these functions are mostly empty, apart from defining an
on_transition function used to resolve their Promise */
@@ -294,26 +373,49 @@ const TorConnect = (() => {
[TorConnectState.Bootstrapping, new StateCallback(TorConnectState.Bootstrapping, async function() {
// wait until bootstrap completes or we get an error
await new Promise(async (resolve, reject) => {
-
- // we reset the bootstrap failure count so users can manually try settings without the
- // censorship circumvention state machine getting in the way
- TorConnect._detectedCensorshipLevel = TorCensorshipLevel.None;
-
// debug hook to simulate censorship preventing bootstrapping
if (Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) > 0) {
this.on_transition = (nextState) => {
resolve();
};
await debug_sleep(1500);
+ TorConnect._hasBootstrapEverFailed = true;
TorConnect._changeState(TorConnectState.Error, "Bootstrap failed (for debugging purposes)", "Error: Censorship simulation", true);
TorProtocolService._torBootstrapDebugSetError();
return;
}
const tbr = new TorBootstrapRequest();
+ const internetTest = new InternetTest();
+
+ let bootstrapError = "";
+ let bootstrapErrorDetails = "";
+ const maybeTransitionToError = () => {
+ if (internetTest.status === InternetStatus.Unknown && internetTest.error === null) {
+ // We have been called by a failed bootstrap, but the internet test has not run yet - force
+ // it to run immediately!
+ internetTest.test();
+ // Return from this call, because the Internet test's callback will call us again
+ return;
+ }
+ // Do not transition to the offline error until we are sure that also the bootstrap failed, in
+ // case Moat is down but the bootstrap can proceed anyway.
+ if (bootstrapError === "") {
+ return;
+ }
+ if (internetTest.status === InternetStatus.Offline) {
+ TorConnect._changeState(TorConnectState.Error, TorStrings.torConnect.offline, "", true);
+ } else {
+ // Give priority to the bootstrap error, in case the Internet test fails
+ TorConnect._hasBootstrapEverFailed = true;
+ TorConnect._changeState(TorConnectState.Error, bootstrapError, bootstrapErrorDetails, true);
+ }
+ }
+
this.on_transition = async (nextState) => {
if (nextState === TorConnectState.Configuring) {
// stop bootstrap process if user cancelled
+ internetTest.cancel();
await tbr.cancel();
}
resolve();
@@ -323,11 +425,24 @@ const TorConnect = (() => {
TorConnect._updateBootstrapStatus(progress, status);
};
tbr.onbootstrapcomplete = () => {
+ internetTest.cancel();
TorConnect._changeState(TorConnectState.Bootstrapped);
};
tbr.onbootstraperror = (message, details) => {
- TorConnect._changeState(TorConnectState.Error, message, details, true);
+ // We have to wait for the Internet test to finish before sending the bootstrap error
+ bootstrapError = message;
+ bootstrapErrorDetails = details;
+ maybeTransitionToError();
+ };
+
+ internetTest.onResult = (status, date) => {
+ // TODO: Use the date to save the clock skew?
+ TorConnect._internetStatus = status;
+ maybeTransitionToError();
};
+ internetTest.onError = () => {
+ maybeTransitionToError();
+ }
tbr.bootstrap();
});
@@ -349,7 +464,7 @@ const TorConnect = (() => {
// always fail even after manually selecting location specific settings
if (censorshipLevel == 3) {
await debug_sleep(2500);
- TorConnect._changeState(TorConnectState.Error, "Error: Extreme Censorship simulation", "", true);
+ TorConnect._changeState(TorConnectState.Error, "Error: censorship simulation", "", true);
return;
// only fail after auto selecting, manually selecting succeeds
} else if (censorshipLevel == 2 && !countryCode) {
@@ -378,12 +493,24 @@ const TorConnect = (() => {
if (this.transitioning) return;
- if (this.settings === null) {
- // unable to determine country
- throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry);
- } else if (this.settings.length === 0) {
- // no settings available for country
- throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry);
+ const noCountry = this.settings !== null;
+ const noLocalizedSettings = this.settings && this.settings.length === 0;
+ if (noCountry || noLocalizedSettings) {
+ try {
+ this.settings = await this.mrpc.circumvention_defaults([...TorBuiltinBridgeTypes, "vanilla"]);
+ } catch (err) {
+ console.error("We could not get localized settings, but defaults settings failed as well", err);
+ }
+ }
+ if (this.settings === null || this.settings.length === 0) {
+ // The fallback has failed as well, so throw the original error
+ if (noCountry) {
+ // unable to determine country
+ throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry);
+ } else {
+ // no settings available for country
+ throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry);
+ }
}
// apply each of our settings and try to bootstrap with each
@@ -477,11 +604,7 @@ const TorConnect = (() => {
TorConnect._errorMessage = errorMessage;
TorConnect._errorDetails = errorDetails;
- if (bootstrappingFailure && TorConnect._detectedCensorshipLevel < TorCensorshipLevel.Extreme) {
- TorConnect._detectedCensorshipLevel += 1;
- }
-
- Services.obs.notifyObservers({message: errorMessage, details: errorDetails, censorshipLevel: TorConnect.detectedCensorshipLevel}, TorConnectTopics.BootstrapError);
+ Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError);
TorConnect._changeState(TorConnectState.Configuring);
});
@@ -615,8 +738,16 @@ const TorConnect = (() => {
return this._bootstrapStatus;
},
- get detectedCensorshipLevel() {
- return this._detectedCensorshipLevel;
+ get internetStatus() {
+ return this._internetStatus;
+ },
+
+ get countryCodes() {
+ return this._countryCodes;
+ },
+
+ get countryNames() {
+ return this._countryNames;
},
get errorMessage() {
@@ -631,12 +762,15 @@ const TorConnect = (() => {
return this._logHasWarningOrError;
},
- get countryCodes() {
- return this._countryCodes;
+ get hasBootstrapEverFailed() {
+ return this._hasBootstrapEverFailed;
},
- get countryNames() {
- return this._countryNames;
+ get uiState() {
+ return this._uiState;
+ },
+ set uiState(newState) {
+ this._uiState = newState;
},
/*
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the tor-commits
mailing list