richard pushed to branch tor-browser-115.3.0esr-13.0-1 at The Tor Project / Applications / Tor Browser
Commits:
-
14e1b34c
by Pier Angelo Vendrame at 2023-09-27T19:39:39+00:00
-
4b7f4df4
by Pier Angelo Vendrame at 2023-09-27T19:39:39+00:00
2 changed files:
- toolkit/components/tor-launcher/TorControlPort.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
Changes:
... | ... | @@ -272,6 +272,11 @@ class AsyncSocket { |
272 | 272 | *
|
273 | 273 | * @typedef {string} NodeFingerprint
|
274 | 274 | */
|
275 | +/**
|
|
276 | + * @typedef {object} CircuitInfo
|
|
277 | + * @property {CircuitID} id
|
|
278 | + * @property {NodeFingerprint[]} nodes
|
|
279 | + */
|
|
275 | 280 | /**
|
276 | 281 | * @typedef {object} Bridge
|
277 | 282 | * @property {string} transport The transport of the bridge, or vanilla if not
|
... | ... | @@ -729,12 +734,14 @@ export class TorController { |
729 | 734 | /**
|
730 | 735 | * Ask Tor a list of circuits.
|
731 | 736 | *
|
732 | - * @returns {string[]} An array with a string for each line
|
|
737 | + * @returns {CircuitInfo[]} An array with a string for each line
|
|
733 | 738 | */
|
734 | 739 | async getCircuits() {
|
735 | 740 | const circuits = await this.#getInfo("circuit-status");
|
736 | - // TODO: Do more parsing once we move the event parsing to this class!
|
|
737 | - return circuits.split(/\r?\n/);
|
|
741 | + return circuits
|
|
742 | + .split(/\r?\n/)
|
|
743 | + .map(this.#parseCircBuilt.bind(this))
|
|
744 | + .filter(circ => circ);
|
|
738 | 745 | }
|
739 | 746 | |
740 | 747 | // Configuration
|
... | ... | @@ -1022,25 +1029,15 @@ export class TorController { |
1022 | 1029 | this.#eventHandler.onBootstrapStatus(status);
|
1023 | 1030 | break;
|
1024 | 1031 | case "CIRC":
|
1025 | - const builtEvent =
|
|
1026 | - /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
|
|
1027 | - data.groups.data
|
|
1028 | - );
|
|
1032 | + const maybeCircuit = this.#parseCircBuilt(data.groups.data);
|
|
1029 | 1033 | const closedEvent = /^(?<ID>[a-zA-Z0-9]{1,16})\sCLOSED/.exec(
|
1030 | 1034 | data.groups.data
|
1031 | 1035 | );
|
1032 | - if (builtEvent) {
|
|
1033 | - const fp = /\$([0-9a-fA-F]{40})/g;
|
|
1034 | - const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
|
|
1035 | - g[1].toUpperCase()
|
|
1036 | + if (maybeCircuit) {
|
|
1037 | + this.#eventHandler.onCircuitBuilt(
|
|
1038 | + maybeCircuit.id,
|
|
1039 | + maybeCircuit.nodes
|
|
1036 | 1040 | );
|
1037 | - // In some cases, we might already receive SOCKS credentials in the
|
|
1038 | - // line. However, this might be a problem with onion services: we get
|
|
1039 | - // also a 4-hop circuit that we likely do not want to show to the
|
|
1040 | - // user, especially because it is used only temporarily, and it would
|
|
1041 | - // need a technical explaination.
|
|
1042 | - // const credentials = this.#parseCredentials(data.groups.data);
|
|
1043 | - this.#eventHandler.onCircuitBuilt(builtEvent.groups.ID, nodes);
|
|
1044 | 1041 | } else if (closedEvent) {
|
1045 | 1042 | this.#eventHandler.onCircuitClosed(closedEvent.groups.ID);
|
1046 | 1043 | }
|
... | ... | @@ -1068,7 +1065,7 @@ export class TorController { |
1068 | 1065 | }
|
1069 | 1066 | }
|
1070 | 1067 | |
1071 | - // Other helpers
|
|
1068 | + // Parsers
|
|
1072 | 1069 | |
1073 | 1070 | /**
|
1074 | 1071 | * Parse a bootstrap status line.
|
... | ... | @@ -1099,15 +1096,32 @@ export class TorController { |
1099 | 1096 | }
|
1100 | 1097 | |
1101 | 1098 | /**
|
1102 | - * Throw an exception when value is not a string.
|
|
1099 | + * Parse a CIRC BUILT event or a GETINFO circuit-status.
|
|
1103 | 1100 | *
|
1104 | - * @param {any} value The value to check
|
|
1105 | - * @param {string} name The name of the `value` argument
|
|
1101 | + * @param {string} line The line to parse
|
|
1102 | + * @returns {CircuitInfo?} The ID and nodes of the circuit, or null if the
|
|
1103 | + * parsing failed.
|
|
1106 | 1104 | */
|
1107 | - #expectString(value, name) {
|
|
1108 | - if (typeof value !== "string" && !(value instanceof String)) {
|
|
1109 | - throw new Error(`The ${name} argument is expected to be a string.`);
|
|
1105 | + #parseCircBuilt(line) {
|
|
1106 | + const builtEvent =
|
|
1107 | + /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
|
|
1108 | + line
|
|
1109 | + );
|
|
1110 | + if (!builtEvent) {
|
|
1111 | + return null;
|
|
1110 | 1112 | }
|
1113 | + const fp = /\$([0-9a-fA-F]{40})/g;
|
|
1114 | + const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
|
|
1115 | + g[1].toUpperCase()
|
|
1116 | + );
|
|
1117 | + // In some cases, we might already receive SOCKS credentials in the
|
|
1118 | + // line. However, this might be a problem with Onion services: we get
|
|
1119 | + // also a 4-hop circuit that we likely do not want to show to the
|
|
1120 | + // user, especially because it is used only temporarily, and it would
|
|
1121 | + // need a technical explaination.
|
|
1122 | + // So we do not try to extract them for now. Otherwise, we could do
|
|
1123 | + // const credentials = this.#parseCredentials(line);
|
|
1124 | + return { id: builtEvent.groups.ID, nodes };
|
|
1111 | 1125 | }
|
1112 | 1126 | |
1113 | 1127 | /**
|
... | ... | @@ -1146,6 +1160,20 @@ export class TorController { |
1146 | 1160 | )
|
1147 | 1161 | );
|
1148 | 1162 | }
|
1163 | + |
|
1164 | + // Other helpers
|
|
1165 | + |
|
1166 | + /**
|
|
1167 | + * Throw an exception when value is not a string.
|
|
1168 | + *
|
|
1169 | + * @param {any} value The value to check
|
|
1170 | + * @param {string} name The name of the `value` argument
|
|
1171 | + */
|
|
1172 | + #expectString(value, name) {
|
|
1173 | + if (typeof value !== "string" && !(value instanceof String)) {
|
|
1174 | + throw new Error(`The ${name} argument is expected to be a string.`);
|
|
1175 | + }
|
|
1176 | + }
|
|
1149 | 1177 | }
|
1150 | 1178 | |
1151 | 1179 | /**
|
... | ... | @@ -137,7 +137,7 @@ export class TorProvider { |
137 | 137 | * built before the new identity but not yet used. If we cleaned the map, we
|
138 | 138 | * risked of not having the data about it.
|
139 | 139 | *
|
140 | - * @type {Map<CircuitID, NodeFingerprint[]>}
|
|
140 | + * @type {Map<CircuitID, Promise<NodeFingerprint[]>>}
|
|
141 | 141 | */
|
142 | 142 | #circuits = new Map();
|
143 | 143 | /**
|
... | ... | @@ -204,6 +204,11 @@ export class TorProvider { |
204 | 204 | |
205 | 205 | logger.debug(`Notifying ${TorProviderTopics.ProcessIsReady}`);
|
206 | 206 | Services.obs.notifyObservers(null, TorProviderTopics.ProcessIsReady);
|
207 | + |
|
208 | + // If we are using an external Tor daemon, we might need to fetch circuits
|
|
209 | + // already, in case streams use them. Do not await because we do not want to
|
|
210 | + // block the intialization on this (it should not fail anyway...).
|
|
211 | + this.#fetchCircuits();
|
|
207 | 212 | }
|
208 | 213 | |
209 | 214 | /**
|
... | ... | @@ -799,6 +804,16 @@ export class TorProvider { |
799 | 804 | return crypto.getRandomValues(new Uint8Array(kPasswordLen));
|
800 | 805 | }
|
801 | 806 | |
807 | + /**
|
|
808 | + * Ask Tor the circuits it already knows to populate our circuit map with the
|
|
809 | + * circuits that were already open before we started listening for events.
|
|
810 | + */
|
|
811 | + async #fetchCircuits() {
|
|
812 | + for (const { id, nodes } of await this.#controller.getCircuits()) {
|
|
813 | + this.onCircuitBuilt(id, nodes);
|
|
814 | + }
|
|
815 | + }
|
|
816 | + |
|
802 | 817 | // Notification handlers
|
803 | 818 | |
804 | 819 | /**
|
... | ... | @@ -983,18 +998,35 @@ export class TorProvider { |
983 | 998 | * @param {CircuitID} circuitId The ID of the circuit used by the stream
|
984 | 999 | * @param {string} username The SOCKS username
|
985 | 1000 | * @param {string} password The SOCKS password
|
986 | - * @returns
|
|
987 | 1001 | */
|
988 | - onStreamSucceeded(streamId, circuitId, username, password) {
|
|
1002 | + async onStreamSucceeded(streamId, circuitId, username, password) {
|
|
989 | 1003 | if (!username || !password) {
|
990 | 1004 | return;
|
991 | 1005 | }
|
992 | 1006 | logger.debug("Stream succeeded event", username, password, circuitId);
|
993 | - const circuit = this.#circuits.get(circuitId);
|
|
1007 | + let circuit = this.#circuits.get(circuitId);
|
|
994 | 1008 | if (!circuit) {
|
995 | - logger.error(
|
|
996 | - "Seen a STREAM SUCCEEDED with an unknown circuit. Not notifying observers."
|
|
997 | - );
|
|
1009 | + circuit = new Promise((resolve, reject) => {
|
|
1010 | + this.#controlConnection.getCircuits().then(circuits => {
|
|
1011 | + for (const { id, nodes } of circuits) {
|
|
1012 | + if (id === circuitId) {
|
|
1013 | + resolve(nodes);
|
|
1014 | + return;
|
|
1015 | + }
|
|
1016 | + // Opportunistically collect circuits, since we are iterating them.
|
|
1017 | + this.#circuits.set(id, nodes);
|
|
1018 | + }
|
|
1019 | + logger.error(
|
|
1020 | + `Seen a STREAM SUCCEEDED with circuit ${circuitId}, but Tor did not send information about it.`
|
|
1021 | + );
|
|
1022 | + reject();
|
|
1023 | + });
|
|
1024 | + });
|
|
1025 | + this.#circuits.set(circuitId, circuit);
|
|
1026 | + }
|
|
1027 | + try {
|
|
1028 | + circuit = await circuit;
|
|
1029 | + } catch {
|
|
998 | 1030 | return;
|
999 | 1031 | }
|
1000 | 1032 | Services.obs.notifyObservers(
|