commit ba03ae5f2ec1ce5c9fd46e4063b477fc8626f2d1 Author: Alex Catarineu acat@torproject.org Date: Tue Jan 26 10:39:41 2021 +0100
Bug 40027: Make torbutton_send_ctrl_cmd async
- makes torbutton_send_ctrl_cmd async; now implemented using torController.sendCommand rather than handling all of the io, concurrency, and syncrhonization - insert asyncs/awaits/catch to the relevant places where torbutton_send_ctrl_cmd are called - removed call to torbutton_do_tor_check() from torbutton_new_window() - NS_BASE_STREAM_CLOSED returned from read no longer triggers error callback inside io.pumpInputStream - tor.controllerCache is now expicitly a JavaScript map object - controller() function now has option to always get a new tor.controller, instead of a cached one - refactored controller() function to use explicit map() functions --- chrome/content/torbutton.js | 105 +++++++++++++------------------------------- modules/tor-control-port.js | 52 +++++++++++++++------- 2 files changed, 66 insertions(+), 91 deletions(-)
diff --git a/chrome/content/torbutton.js b/chrome/content/torbutton.js index 48539a96..8c016b39 100644 --- a/chrome/content/torbutton.js +++ b/chrome/content/torbutton.js @@ -28,7 +28,7 @@ let { torbutton_log, torbutton_get_property_string, } = ChromeUtils.import("resource://torbutton/modules/utils.js", {}); -let { configureControlPortModule } = Cu.import("resource://torbutton/modules/tor-control-port.js", {}); +let { configureControlPortModule, controller } = Cu.import("resource://torbutton/modules/tor-control-port.js", {});
const k_tb_tor_check_failed_topic = "Torbutton:TorCheckFailed";
@@ -299,19 +299,19 @@ torbutton_init = function() {
var torbutton_abouttor_message_handler = { // Receive IPC messages from the about:tor content script. - receiveMessage: function(aMessage) { + receiveMessage: async function(aMessage) { switch(aMessage.name) { case "AboutTor:Loaded": aMessage.target.messageManager.sendAsyncMessage("AboutTor:ChromeData", - this.getChromeData(true)); + await this.getChromeData(true)); break; } },
// Send privileged data to all of the about:tor content scripts. - updateAllOpenPages: function() { + updateAllOpenPages: async function() { window.messageManager.broadcastAsyncMessage("AboutTor:ChromeData", - this.getChromeData(false)); + await this.getChromeData(false)); },
// The chrome data contains all of the data needed by the about:tor @@ -319,11 +319,11 @@ var torbutton_abouttor_message_handler = { // It is sent to the content process when an about:tor window is opened // and in response to events such as the browser noticing that Tor is // not working. - getChromeData: function(aIsRespondingToPageLoad) { + getChromeData: async function(aIsRespondingToPageLoad) { let dataObj = { mobile: torbutton_is_mobile(), updateChannel: AppConstants.MOZ_UPDATE_CHANNEL, - torOn: torbutton_tor_check_ok() + torOn: await torbutton_tor_check_ok() };
if (aIsRespondingToPageLoad) { @@ -446,70 +446,27 @@ function torbutton_array_to_hexdigits(array) {
// Bug 1506 P4: Control port interaction. Needed for New Identity. // -// Executes a command on the control port. -// Return a string response upon success and null upon error. -function torbutton_send_ctrl_cmd(command) { - - // We spin the event queue until it is empty and we can be sure that sending - // NEWNYM is not leading to a deadlock (see bug 9531 comment 23 for an - // invstigation on why and when this may happen). This is surrounded by - // suppressing/unsuppressing user initiated events in a window's document to - // be sure that these events are not interfering with processing events being - // in the event queue. - var thread = Services.tm.currentThread; - m_tb_domWindowUtils.suppressEventHandling(true); - while (thread.processNextEvent(false)) {} - m_tb_domWindowUtils.suppressEventHandling(false); - +// Asynchronously executes a command on the control port. +// returns the response as a string, or null on error +async function torbutton_send_ctrl_cmd(command) { + const getErrorMessage = e => (e && (e.torMessage || e.message)) || ""; + let response = null; try { - let sts = Cc["@mozilla.org/network/socket-transport-service;1"] - .getService(Ci.nsISocketTransportService); - let socket; - if (m_tb_control_ipc_file) { - socket = sts.createUnixDomainTransport(m_tb_control_ipc_file); - } else { - socket = sts.createTransport([], m_tb_control_host, - m_tb_control_port, null); - } - - // If we don't get a response from the control port in 2 seconds, someting is wrong.. - socket.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 2); - - var input = socket.openInputStream(3, 1, 1); // 3 == OPEN_BLOCKING|OPEN_UNBUFFERED - var output = socket.openOutputStream(3, 1, 1); // 3 == OPEN_BLOCKING|OPEN_UNBUFFERED - - var inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream); - var outputStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream); + const avoidCache = true; + let torController = controller(e => { throw e; }, avoidCache);
- inputStream.setInputStream(input); - outputStream.setOutputStream(output); - - var auth_cmd = "AUTHENTICATE "+m_tb_control_pass+"\r\n"; - outputStream.writeBytes(auth_cmd, auth_cmd.length); - - var bytes = torbutton_socket_readline(inputStream); - - if (bytes.indexOf("250") != 0) { - torbutton_safelog(4, "Unexpected auth response on control port "+m_tb_control_desc+":", bytes); - return null; + let bytes = await torController.sendCommand(command); + if (!bytes.startsWith("250")) { + throw `Unexpected command response on control port '${bytes}'`; } + response = bytes.slice(4);
- outputStream.writeBytes(command, command.length); - bytes = torbutton_socket_readline(inputStream); - if(bytes.indexOf("250") != 0) { - torbutton_safelog(4, "Unexpected command response on control port "+m_tb_control_desc+":", bytes); - return null; - } - - // Closing these streams prevents a shutdown hang on Mac OS. See bug 10201. - inputStream.close(); - outputStream.close(); - socket.close(Cr.NS_OK); - return bytes.substr(4); - } catch(e) { - torbutton_log(4, "Exception on control port "+e); - return null; + torController.close(); + } catch(err) { + let msg = getErrorMessage(err); + torbutton_log(4, `Error: ${msg}`); } + return response; }
// Bug 1506 P4: Needed for New IP Address @@ -800,7 +757,7 @@ async function torbutton_do_new_identity() { torbutton_log(5, "Torbutton cannot safely newnym. It does not have access to the Tor Control Port."); window.alert(warning); } else { - if (!torbutton_send_ctrl_cmd("SIGNAL NEWNYM\r\n")) { + if (!await torbutton_send_ctrl_cmd("SIGNAL NEWNYM")) { var warning = torbutton_get_property_string("torbutton.popup.no_newnym"); torbutton_log(5, "Torbutton was unable to request a new circuit from Tor"); window.alert(warning); @@ -930,7 +887,7 @@ function torbutton_use_nontor_proxy() torbutton_do_new_identity(); }
-function torbutton_do_tor_check() +async function torbutton_do_tor_check() { let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"] .getService(Ci.nsISupports).wrappedJSObject; @@ -948,7 +905,7 @@ function torbutton_do_tor_check() !env.exists(kEnvUseTransparentProxy) && !env.exists(kEnvSkipControlPortTest) && m_tb_prefs.getBoolPref("extensions.torbutton.local_tor_check")) { - if (torbutton_local_tor_check()) + if (await torbutton_local_tor_check()) checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful; else { // The check failed. Update toolbar icon and tooltip. @@ -961,7 +918,7 @@ function torbutton_do_tor_check() } }
-function torbutton_local_tor_check() +async function torbutton_local_tor_check() { let didLogError = false;
@@ -972,7 +929,7 @@ function torbutton_local_tor_check() // Ask tor for its SOCKS listener address and port and compare to the // browser preferences. const kCmdArg = "net/listeners/socks"; - let resp = torbutton_send_ctrl_cmd("GETINFO " + kCmdArg + "\r\n"); + let resp = await torbutton_send_ctrl_cmd("GETINFO " + kCmdArg); if (!resp) return false;
@@ -1116,9 +1073,9 @@ function torbutton_initiate_remote_tor_check() { } } // torbutton_initiate_remote_tor_check()
-function torbutton_tor_check_ok() +async function torbutton_tor_check_ok() { - torbutton_do_tor_check(); + await torbutton_do_tor_check(); let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"] .getService(Ci.nsISupports).wrappedJSObject; return (checkSvc.kCheckFailed != checkSvc.statusOfTorCheck); @@ -1486,8 +1443,6 @@ function torbutton_new_window(event) progress.addProgressListener(torbutton_resizelistener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); } - - torbutton_do_tor_check(); }
// Bug 1506 P2: This is only needed because we have observers diff --git a/modules/tor-control-port.js b/modules/tor-control-port.js index a3e72dd5..7891f0ee 100644 --- a/modules/tor-control-port.js +++ b/modules/tor-control-port.js @@ -82,7 +82,9 @@ io.pumpInputStream = function (inputStream, onInputData, onError) { onInputData(chunk); awaitNextChunk(); } catch (err) { - onError(err); + if (err.result !== Cr.NS_BASE_STREAM_CLOSED) { + onError(err); + } } } }, 0, 0, Services.tm.currentThread); @@ -285,9 +287,9 @@ io.controlSocket = function (ipcFile, host, port, password, onError) { // Pass asynchronous notifications to notification dispatcher. mainDispatcher.addCallback(/^650/, notificationDispatcher.pushMessage); // Log in to control port. - sendCommand("authenticate " + (password || "")); + sendCommand("authenticate " + (password || "")).catch(onError); // Activate needed events. - sendCommand("setevents stream"); + sendCommand("setevents stream").catch(onError); return { close : socket.close, sendCommand : sendCommand, addNotificationCallback : notificationDispatcher.addCallback, removeNotificationCallback : notificationDispatcher.removeCallback }; @@ -677,7 +679,7 @@ let tor = {}; // __tor.controllerCache__. // A map from "unix:socketpath" or "host:port" to controller objects. Prevents // redundant instantiation of control sockets. -tor.controllerCache = {}; +tor.controllerCache = new Map();
// __tor.controller(ipcFile, host, port, password, onError)__. // Creates a tor controller at the given ipcFile or host and port, with the @@ -697,7 +699,8 @@ tor.controller = function (ipcFile, host, port, password, onError) { watchEvent : (type, filter, onData) => event.watchEvent(socket, type, filter, onData), isOpen : () => isOpen, - close : () => { isOpen = false; socket.close(); } + close : () => { isOpen = false; socket.close(); }, + sendCommand: cmd => socket.sendCommand(cmd), }; };
@@ -732,23 +735,40 @@ var configureControlPortModule = function (ipcFile, host, port, password) { // let replyPromise = c.getInfo("ip-to-country/16.16.16.16"); // // Close the controller permanently // c.close(); -var controller = function (onError) { +var controller = function (onError, avoidCache) { if (!controlPortInfo.ipcFile && !controlPortInfo.host) throw new Error("Please call configureControlPortModule first");
const dest = (controlPortInfo.ipcFile) ? `unix:${controlPortInfo.ipcFile.path}` : `${controlPortInfo.host}:${controlPortInfo.port}`; - const maybeController = tor.controllerCache[dest]; - if (maybeController && maybeController.isOpen()) - return maybeController; - - tor.controllerCache[dest] = tor.controller(controlPortInfo.ipcFile, - controlPortInfo.host, - controlPortInfo.port, - controlPortInfo.password, - onError); - return tor.controllerCache[dest]; + + // constructor shorthand + const newTorController = + () => { + return tor.controller( + controlPortInfo.ipcFile, + controlPortInfo.host, + controlPortInfo.port, + controlPortInfo.password, + onError); + }; + + // avoid cache so always return a new controller + if (avoidCache) { + return newTorController(); + } + + // first check our cache and see if we already have one + let cachedController = tor.controllerCache.get(dest); + if (cachedController && cachedController.isOpen()) { + return cachedController; + } + + // create a new one and store in the map + cachedController = newTorController(); + tor.controllerCache.set(dest, cachedController); + return cachedController; };
// Export functions for external use.
tbb-commits@lists.torproject.org