[tor-commits] [torbutton/master] Bug #13882: Fix display of bridges after bridge settings have been changed

mikeperry at torproject.org mikeperry at torproject.org
Wed Feb 18 20:12:51 UTC 2015


commit de8cb7a32e0d2a70812322ce1d6a1f4627ef33f0
Author: Arthur Edelstein <arthuredelstein at gmail.com>
Date:   Mon Feb 16 17:47:13 2015 -0800

    Bug #13882: Fix display of bridges after bridge settings have been changed
---
 src/chrome/content/tor-circuit-display.js |   71 ++++++++++-------------
 src/modules/tor-control-port.js           |   88 ++++++++++++++++++++++-------
 2 files changed, 99 insertions(+), 60 deletions(-)

diff --git a/src/chrome/content/tor-circuit-display.js b/src/chrome/content/tor-circuit-display.js
index 35a7dfc..e2628b4 100644
--- a/src/chrome/content/tor-circuit-display.js
+++ b/src/chrome/content/tor-circuit-display.js
@@ -43,41 +43,23 @@ let logger = Cc["@torproject.org/torbutton-logger;1"]
 let credentialsToNodeDataMap = {},
     // A mutable map that reports `true` for IDs of "mature" circuits
     // (those that have conveyed a stream).
-    knownCircuitIDs = {},
-    // A map from bridge fingerprint to its IP address.
-    bridgeIDtoIPmap = new Map();
+    knownCircuitIDs = {};
 
 // __trimQuotes(s)__.
 // Removes quotation marks around a quoted string.
 let trimQuotes = s => s ? s.match(/^\"(.*)\"$/)[1] : undefined;
 
-// __readBridgeIPs(controller)__.
-// Gets a map from bridge ID to bridge IP, and stores it
-// in `bridgeIDtoIPmap`.
-let readBridgeIPs = function (controller) {
-  Task.spawn(function* () {
-    let configText = yield controller.getInfo("config-text"),
-        bridgeEntries = configText.Bridge;
-    if (bridgeEntries) {
-      bridgeEntries.map(entry => {
-        let IPplusPort, ID,
-            tokens = entry.split(/\s+/);
-          // First check if we have a "vanilla" bridge:
-        if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
-          [IPplusPort, ID] = tokens;
-        // Several bridge types have a similar format:
-        } else if (["fte", "obfs3", "obfs4", "scramblesuit"]
-                     .indexOf(tokens[0]) >= 0) {
-          [IPplusPort, ID] = tokens.slice(1);
-        }
-        // (For now, we aren't dealing with meek bridges and flashproxy.)
-        if (IPplusPort && ID) {
-          let IP = IPplusPort.split(":")[0];
-          bridgeIDtoIPmap.set(ID.toUpperCase(), IP);
-        }
-      });
+// __getBridge(id)__.
+// Gets the bridge parameters for a given node ID. If the node
+// is not currently used as a bridge, returns null.
+let getBridge = function* (controller, id) {
+  let bridges = yield controller.getConf("bridge");
+  for (let bridge of bridges) {
+    if (bridge.ID.toUpperCase() === id.toUpperCase()) {
+      return bridge;
     }
-  }).then(null, Cu.reportError);
+  }
+  return null;
 };
 
 // nodeDataForID(controller, id)__.
@@ -85,17 +67,22 @@ let readBridgeIPs = function (controller) {
 // Example: `nodeDataForID(controller, "20BC91DC525C3DC9974B29FBEAB51230DE024C44")`
 // => `{ type : "default", ip : "12.23.34.45", countryCode : "fr" }`
 let nodeDataForID = function* (controller, id) {
-  let result = {}; // type, ip, countryCode;
-  if (bridgeIDtoIPmap.has(id.toUpperCase())) {
-    result.ip = bridgeIDtoIPmap.get(id.toUpperCase());
+  let result = {},
+      bridge = yield getBridge(controller, id); // type, ip, countryCode;
+  if (bridge) {
     result.type = "bridge";
-  } else {
-    // Get the IP address for the given node ID.
+    result.bridgeType = bridge.type;
+    // Attempt to get an IP address from bridge address string.
     try {
-      let statusMap = yield controller.getInfo("ns/id/" + id);
-      result.ip = statusMap.IP;
+      result.ip = bridge.address.split(":")[0];
     } catch (e) { }
+  } else {
     result.type = "default";
+    // Get the IP address for the given node ID.
+     try {
+       let statusMap = yield controller.getInfo("ns/id/" + id);
+       result.ip = statusMap.IP;
+     } catch (e) { }
   }
   if (result.ip) {
     // Get the country code for the node's IP address.
@@ -141,8 +128,10 @@ let collectIsolationData = function (aController) {
         logger.eclog(3, "streamEvent.CircuitID: " + streamEvent.CircuitID);
         knownCircuitIDs[streamEvent.CircuitID] = true;
         let circuitStatus = yield getCircuitStatusByID(aController, streamEvent.CircuitID),
-            credentials = trimQuotes(circuitStatus.SOCKS_USERNAME) + ":" +
-                          trimQuotes(circuitStatus.SOCKS_PASSWORD);
+            credentials = circuitStatus ?
+                            (trimQuotes(circuitStatus.SOCKS_USERNAME) + ":" +
+                             trimQuotes(circuitStatus.SOCKS_PASSWORD)) :
+                            null;
         if (credentials) {
           let nodeData = yield nodeDataForCircuit(aController, circuitStatus);
           credentialsToNodeDataMap[credentials] = nodeData;
@@ -195,11 +184,12 @@ let showCircuitDisplay = function (show) {
 // `"France (12.34.56.78)"`.
 let nodeLines = function (nodeData) {
   let result = [uiString("this_browser")];
-  for (let {ip, countryCode, type} of nodeData) {
+  for (let {ip, countryCode, type, bridgeType} of nodeData) {
     let bridge = type === "bridge";
     result.push((countryCode ? localizedCountryNameFromCode(countryCode)
                              : uiString("unknown_country")) +
-                " (" + (bridge ? uiString("tor_bridge")
+                " (" + (bridge ? (uiString("tor_bridge") + 
+                                   ((bridgeType !== "vanilla") ? (": " + bridgeType) : ""))
                                : (ip || uiString("ip_unknown"))) + ")");
   }
   result[4] = uiString("internet");
@@ -340,7 +330,6 @@ let setupDisplay = function (host, port, password, enablePrefName) {
             logger.eclog(5, "Disabling tor display circuit because of an error.");
             stop();
           });
-          readBridgeIPs(myController);
           syncDisplayWithSelectedTab(true);
           collectIsolationData(myController);
        }
diff --git a/src/modules/tor-control-port.js b/src/modules/tor-control-port.js
index 3fa7756..a55a0d8 100644
--- a/src/modules/tor-control-port.js
+++ b/src/modules/tor-control-port.js
@@ -372,22 +372,22 @@ utils.listMapData = function (parameterString, listNames) {
 };
 
 // ## info
-// A namespace for functions related to tor's GETINFO command.
+// A namespace for functions related to tor's GETINFO and GETCONF command.
 let info = info || {};
 
 // __info.keyValueStringsFromMessage(messageText)__.
-// Takes a message (text) response to GETINFO and provides a series of key-value
-// strings, which are either multiline (with a `250+` prefix):
+// Takes a message (text) response to GETINFO or GETCONF and provides
+// a series of key-value strings, which are either multiline (with a `250+` prefix):
 //
 //     250+config/defaults=
 //     AccountingMax "0 bytes"
 //     AllowDotExit "0"
 //     .
 //
-// or single-line (with a `250-` prefix):
+// or single-line (with a `250-` or `250 ` prefix):
 //
 //     250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
-info.keyValueStringsFromMessage = utils.extractor(/^(250\+[\s\S]+?^\.|250-.+?)$/gmi);
+info.keyValueStringsFromMessage = utils.extractor(/^(250\+[\s\S]+?^\.|250[-\ ].+?)$/gmi);
 
 // __info.applyPerLine(transformFunction)__.
 // Returns a function that splits text into lines,
@@ -462,9 +462,33 @@ info.configTextParser = function(text) {
   return result;
 };
 
+
+// __info.bridgeParser(bridgeLine)__.
+// Takes a single line from a `getconf bridge` result and returns
+// a map containing the bridge's type, address, and ID.
+info.bridgeParser = function(bridgeLine) {
+  let result = {},
+      tokens = bridgeLine.split(/\s+/);
+  // First check if we have a "vanilla" bridge:
+  if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
+    result.type = "vanilla";
+    [result.address, result.ID] = tokens;
+  // Several bridge types have a similar format:
+  } else {
+    result.type = tokens[0];
+    if (["fte", "obfs3", "obfs4", "scramblesuit"]
+               .indexOf(result.type) >= 0) {
+      [result.address, result.ID] = tokens.slice(1);
+    } else if (result.type === "meek") {
+      // do nothing for now
+    }
+  }
+  return result.type ? result : null;
+};
+
 // __info.parsers__.
-// A map of GETINFO keys to parsing function, which convert result strings to JavaScript
-// data.
+// A map of GETINFO and GETCONF keys to parsing function, which convert
+// result strings to JavaScript data.
 info.parsers = {
   "version" : utils.identity,
   "config-file" : utils.identity,
@@ -474,7 +498,8 @@ info.parsers = {
   "ns/name/" : info.routerStatusParser,
   "ip-to-country/" : utils.identity,
   "circuit-status" : info.applyPerLine(info.circuitStatusParser),
-  "stream-status" : info.applyPerLine(info.streamStatusParser)
+  "stream-status" : info.applyPerLine(info.streamStatusParser),
+  "bridge" : info.bridgeParser
 };
 
 // __info.getParser(key)__.
@@ -482,22 +507,37 @@ info.parsers = {
 // convert its corresponding valueString to JavaScript data.
 info.getParser = function(key) {
   return info.parsers[key] ||
-         info.parsers[key.substring(0, key.lastIndexOf("/") + 1)] ||
-         "unknown";
+         info.parsers[key.substring(0, key.lastIndexOf("/") + 1)];
 };
 
 // __info.stringToValue(string)__.
-// Converts a key-value string as from GETINFO to a value.
+// Converts a key-value string as from GETINFO or GETCONF to a value.
 info.stringToValue = function (string) {
   // key should look something like `250+circuit-status=` or `250-circuit-status=...`
-  let key = string.match(/^250[\+-](.+?)=/mi)[1],
-      // matchResult finds a single-line result for `250-` or a multi-line one for `250+`.
-      matchResult = string.match(/250\-.+?=(.*?)$/mi) ||
+  // or `250 circuit-status...`
+  let matchForKey = string.match(/^250[ +-](.+?)=/mi),
+      key = matchForKey ? matchForKey[1] : null;
+  if (key === null) return null;
+  // matchResult finds a single-line result for `250-` or a multi-line one for `250+`.
+  let matchResult = string.match(/250[ -].+?=(.*?)$/mi) ||
                     string.match(/250\+.+?=([\s\S]*?)^\.$/mi),
       // Retrieve the captured group (the text of the value in the key-value pair)
-      valueString = matchResult ? matchResult[1] : null;
-  // Return value where the latter has been parsed according to the key requested.
-  return info.getParser(key)(valueString);
+      valueString = matchResult ? matchResult[1] : null,
+      // Get the parser functino for the key found.
+      parse = info.getParser(key.toLowerCase());
+  if (parse === undefined) {
+    throw new Error("No parser found for '" + key + "'");
+  }
+  // Return value produced by the parser.
+  return parse(valueString);
+};
+
+// __info.getMultipleResponseValues(message)__.
+// Process multiple responses to a GETINFO or GETCONF request.
+info.getMultipleResponseValues = function (message) {
+  return info.keyValueStringsFromMessage(message)
+             .map(info.stringToValue)
+             .filter(utils.identity);
 };
 
 // __info.getInfoMultiple(aControlSocket, keys)__.
@@ -519,8 +559,7 @@ info.getInfoMultiple = function (aControlSocket, keys) {
   }
   */
   return aControlSocket.sendCommand("getinfo " + keys.join(" "))
-                       .then(message => info.keyValueStringsFromMessage(message)
-                                            .map(info.stringToValue));
+                       .then(info.getMultipleResponseValues);
 };
 
 // __info.getInfo(controlSocket, key)__.
@@ -537,6 +576,16 @@ info.getInfo = function (aControlSocket, key) {
   return info.getInfoMultiple(aControlSocket, [key]).then(data => data[0]);
 };
 
+// __info.getConf(aControlSocket, key)__.
+// Sends GETCONF for a single key. Returns a promise with the result.
+info.getConf = function (aControlSocket, key) {
+  // GETCONF with a single argument returns results that look like
+  // results from GETINFO with multiple arguments.
+  // So we can use the same kind of parsing for
+  return aControlSocket.sendCommand("getconf " + key)
+                       .then(info.getMultipleResponseValues);
+};
+
 // ## event
 // Handlers for events
 
@@ -587,6 +636,7 @@ tor.controller = function (host, port, password, onError) {
       isOpen = true;
   return { getInfo : key => info.getInfo(socket, key),
            getInfoMultiple : keys => info.getInfoMultiple(socket, keys),
+           getConf : key => info.getConf(socket, key),
            watchEvent : function (type, filter, onData) {
              event.watchEvent(socket, type, filter, onData);
            },





More information about the tor-commits mailing list