commit de8cb7a32e0d2a70812322ce1d6a1f4627ef33f0
Author: Arthur Edelstein <arthuredelstein(a)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);
},