[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-148.0a1-16.0-2] 6 commits: fixup! TB 40933: Add tor-launcher functionality



Title: GitLab

Pier Angelo Vendrame pushed to branch tor-browser-148.0a1-16.0-2 at The Tor Project / Applications / Tor Browser

Commits:

  • b07229fd
    by Pier Angelo Vendrame at 2026-03-02T17:49:55+01:00
    fixup! TB 40933: Add tor-launcher functionality
    
    TB 44635: Gather conflux information on circuits.
    
  • b7ffbcb1
    by Pier Angelo Vendrame at 2026-03-02T17:49:55+01:00
    fixup! TB 40933: Add tor-launcher functionality
    
    TB 44635: Gather conflux information on circuits.
    
    Proactively gather data about the circuits in TorProvider, and send the
    complete information about a circuit, not only its node fingerprints.
    Also, gather conflux sets, and send both conflux circuits to the
    circuit display backend.
    
  • 13a9f1cc
    by Pier Angelo Vendrame at 2026-03-02T17:49:55+01:00
    fixup! TB 3455: Add DomainIsolator, for isolating circuit by domain.
    
    TB 44635: Gather conflux information on circuits.
    
    Reword CircuitID to IsolationKey, for better clarity.
    
  • 66986d27
    by Pier Angelo Vendrame at 2026-03-02T17:49:56+01:00
    fixup! TB 3455: Add DomainIsolator, for isolating circuit by domain.
    
    TB 44635: Gather conflux information on circuits.
    
    Relay information collection now happens at the tor provider level.
    So, adapt the code of TorDomainIsolator to take the data alrady
    prepared.
    
  • ef0640a3
    by Pier Angelo Vendrame at 2026-03-02T17:49:56+01:00
    fixup! TB 41600: Add a tor circuit display panel.
    
    TB 44635: Gather conflux information on circuits.
    
    Consume only the first circuit on the circuit display.
    
  • 61cf191b
    by Pier Angelo Vendrame at 2026-03-02T17:49:56+01:00
    fixup! TB 42247: Android helpers for the TorProvider
    
    TB 44635: Gather conflux information on circuits.
    
    Update the getCircuit name to getCircuits.
    

6 changed files:

Changes:

  • browser/components/torcircuit/content/torCircuitPanel.js
    ... ... @@ -293,11 +293,14 @@ var gTorCircuitPanel = {
    293 293
       _updateCurrentBrowser() {
    
    294 294
         const browser = gBrowser.selectedBrowser;
    
    295 295
         const domain = TorDomainIsolator.getDomainForBrowser(browser);
    
    296
    -    const nodes = TorDomainIsolator.getCircuit(
    
    296
    +    const circuits = TorDomainIsolator.getCircuits(
    
    297 297
           browser,
    
    298 298
           domain,
    
    299 299
           browser.contentPrincipal.originAttributes.userContextId
    
    300 300
         );
    
    301
    +    // TODO: Handle multiple circuits (for conflux). Only show the primary
    
    302
    +    // circuit until the UI for that is developed.
    
    303
    +    const nodes = circuits.length ? circuits[0] : [];
    
    301 304
         // We choose the currentURI, which matches what is shown in the URL bar and
    
    302 305
         // will match up with the domain.
    
    303 306
         // In contrast, documentURI corresponds to the shown page. E.g. it could
    

  • mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
    ... ... @@ -2760,8 +2760,8 @@ public class GeckoSession {
    2760 2760
        * @return The circuit information as a {@link GeckoResult} object.
    
    2761 2761
        */
    
    2762 2762
       @AnyThread
    
    2763
    -  public @NonNull GeckoResult<GeckoBundle> getTorCircuit() {
    
    2764
    -    return mEventDispatcher.queryBundle("GeckoView:GetTorCircuit");
    
    2763
    +  public @NonNull GeckoResult<GeckoBundle> getTorCircuits() {
    
    2764
    +    return mEventDispatcher.queryBundle("GeckoView:GetTorCircuits");
    
    2765 2765
       }
    
    2766 2766
     
    
    2767 2767
       /**
    

  • mobile/shared/modules/geckoview/GeckoViewContent.sys.mjs
    ... ... @@ -297,8 +297,8 @@ export class GeckoViewContent extends GeckoViewModule {
    297 297
           case "GeckoView:HasCookieBannerRuleForBrowsingContextTree":
    
    298 298
             this._hasCookieBannerRuleForBrowsingContextTree(aCallback);
    
    299 299
             break;
    
    300
    -      case "GeckoView:GetTorCircuit":
    
    301
    -        this._getTorCircuit(aCallback);
    
    300
    +      case "GeckoView:GetTorCircuits":
    
    301
    +        this._getTorCircuits(aCallback);
    
    302 302
             break;
    
    303 303
           case "GeckoView:NewTorCircuit":
    
    304 304
             this._newTorCircuit(aCallback);
    
    ... ... @@ -472,15 +472,15 @@ export class GeckoViewContent extends GeckoViewModule {
    472 472
         }
    
    473 473
       }
    
    474 474
     
    
    475
    -  _getTorCircuit(aCallback) {
    
    475
    +  _getTorCircuits(aCallback) {
    
    476 476
         if (this.browser && aCallback) {
    
    477 477
           const domain = lazy.TorDomainIsolator.getDomainForBrowser(this.browser);
    
    478
    -      const nodes = lazy.TorDomainIsolator.getCircuit(
    
    478
    +      const circuits = lazy.TorDomainIsolator.getCircuits(
    
    479 479
             this.browser,
    
    480 480
             domain,
    
    481 481
             this.browser.contentPrincipal.originAttributes.userContextId
    
    482 482
           );
    
    483
    -      aCallback?.onSuccess({ domain, nodes });
    
    483
    +      aCallback?.onSuccess({ domain, circuits });
    
    484 484
         } else {
    
    485 485
           aCallback?.onSuccess(null);
    
    486 486
         }
    

  • toolkit/components/tor-launcher/TorControlPort.sys.mjs
    ... ... @@ -248,16 +248,23 @@ class AsyncSocket {
    248 248
      */
    
    249 249
     /**
    
    250 250
      * The ID of a circuit.
    
    251
    - * From control-spec.txt:
    
    251
    + * From the control port specs:
    
    252 252
      *   CircuitID = 1*16 IDChar
    
    253 253
      *   IDChar = ALPHA / DIGIT
    
    254 254
      *   Currently, Tor only uses digits, but this may change.
    
    255 255
      *
    
    256 256
      * @typedef {string} CircuitID
    
    257 257
      */
    
    258
    +/**
    
    259
    + * The ID to match paired conflux circuits.
    
    260
    + * From the control port specs:
    
    261
    + *   ConfluxID = 32*HEXDIG
    
    262
    + *
    
    263
    + * @typedef {string} ConfluxID
    
    264
    + */
    
    258 265
     /**
    
    259 266
      * The ID of a stream.
    
    260
    - * From control-spec.txt:
    
    267
    + * From the control port specs:
    
    261 268
      *   CircuitID = 1*16 IDChar
    
    262 269
      *   IDChar = ALPHA / DIGIT
    
    263 270
      *   Currently, Tor only uses digits, but this may change.
    
    ... ... @@ -266,7 +273,7 @@ class AsyncSocket {
    266 273
      */
    
    267 274
     /**
    
    268 275
      * The fingerprint of a node.
    
    269
    - * From control-spec.txt:
    
    276
    + * From the control port specs:
    
    270 277
      *   Fingerprint = "$" 40*HEXDIG
    
    271 278
      * However, we do not keep the $ in our structures.
    
    272 279
      *
    
    ... ... @@ -275,7 +282,10 @@ class AsyncSocket {
    275 282
     /**
    
    276 283
      * @typedef {object} CircuitInfo
    
    277 284
      * @property {CircuitID} id The ID of a circuit
    
    278
    - * @property {NodeFingerprint[]} nodes List of node fingerprints
    
    285
    + * @property {NodeFingerprint[]} nodes List of node fingerprints, ordered from
    
    286
    + * guard/bridge to exit.
    
    287
    + * @property {ConfluxID} [confluxId] The conflux ID, for associating conflux
    
    288
    + * circuits.
    
    279 289
      */
    
    280 290
     /**
    
    281 291
      * @typedef {object} Bridge
    
    ... ... @@ -823,8 +833,8 @@ export class TorController {
    823 833
         }
    
    824 834
         const cmd = `GETCONF ${key}`;
    
    825 835
         const reply = await this.#sendCommand(cmd);
    
    826
    -    // From control-spec.txt: a 'default' value semantically different from an
    
    827
    -    // empty string will not have an equal sign, just `250 $key`.
    
    836
    +    // From the control port specs: a 'default' value semantically different
    
    837
    +    // from an empty string will not have an equal sign, just `250 $key`.
    
    828 838
         const defaultRe = new RegExp(`^250[-\\s]${key}$`, "gim");
    
    829 839
         if (reply.match(defaultRe)) {
    
    830 840
           return [];
    
    ... ... @@ -1149,10 +1159,7 @@ export class TorController {
    1149 1159
               data.groups.data
    
    1150 1160
             );
    
    1151 1161
             if (maybeCircuit) {
    
    1152
    -          this.#eventHandler.onCircuitBuilt(
    
    1153
    -            maybeCircuit.id,
    
    1154
    -            maybeCircuit.nodes
    
    1155
    -          );
    
    1162
    +          this.#eventHandler.onCircuitBuilt(maybeCircuit);
    
    1156 1163
             } else if (closedEvent) {
    
    1157 1164
               this.#eventHandler.onCircuitClosed(closedEvent.groups.ID);
    
    1158 1165
             }
    
    ... ... @@ -1222,7 +1229,7 @@ export class TorController {
    1222 1229
        */
    
    1223 1230
       #parseCircBuilt(line) {
    
    1224 1231
         const builtEvent =
    
    1225
    -      /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
    
    1232
    +      /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)(?<details>.*)/.exec(
    
    1226 1233
             line
    
    1227 1234
           );
    
    1228 1235
         if (!builtEvent) {
    
    ... ... @@ -1232,6 +1239,8 @@ export class TorController {
    1232 1239
         const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
    
    1233 1240
           g[1].toUpperCase()
    
    1234 1241
         );
    
    1242
    +    const circuit = { id: builtEvent.groups.ID, nodes };
    
    1243
    +
    
    1235 1244
         // In some cases, we might already receive SOCKS credentials in the
    
    1236 1245
         // line. However, this might be a problem with Onion services: we get
    
    1237 1246
         // also a 4-hop circuit that we likely do not want to show to the
    
    ... ... @@ -1239,7 +1248,22 @@ export class TorController {
    1239 1248
         // need a technical explaination.
    
    1240 1249
         // So we do not try to extract them for now. Otherwise, we could do
    
    1241 1250
         // const credentials = this.#parseCredentials(line);
    
    1242
    -    return { id: builtEvent.groups.ID, nodes };
    
    1251
    +
    
    1252
    +    // NOTE: We use a greedy leading ".*" to skip over previous fields that
    
    1253
    +    // can contain arbitrary strings, like SOCKS_USERNAME and SOCKS_PASSWORD,
    
    1254
    +    // which allows them to contain " CONFLUX_ID=" within their values.
    
    1255
    +    // Although such a value is not expected from the usernames and passwords we
    
    1256
    +    // set in Tor Browser, it may be set by an external tor user.
    
    1257
    +    // NOTE: This assumes there is no other arbitrary string field after
    
    1258
    +    // CONFLUX_ID.
    
    1259
    +    const maybeConfluxId = builtEvent.groups.details.match(
    
    1260
    +      /.* CONFLUX_ID=([0-9a-fA-F]{32,})(?:$| )/
    
    1261
    +    );
    
    1262
    +    if (maybeConfluxId) {
    
    1263
    +      circuit.confluxId = maybeConfluxId[1];
    
    1264
    +    }
    
    1265
    +
    
    1266
    +    return circuit;
    
    1243 1267
       }
    
    1244 1268
     
    
    1245 1269
       /**
    
    ... ... @@ -1327,8 +1351,7 @@ export class TorController {
    1327 1351
     /**
    
    1328 1352
      * @callback OnCircuitBuilt
    
    1329 1353
      *
    
    1330
    - * @param {CircuitID} id The id of the circuit that has been built
    
    1331
    - * @param {NodeFingerprint[]} nodes The onion routers composing the circuit
    
    1354
    + * @param {CircuitInfo} circuit The information about the circuit
    
    1332 1355
      */
    
    1333 1356
     /**
    
    1334 1357
      * @callback OnCircuitClosed
    

  • toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs
    ... ... @@ -12,6 +12,7 @@ import {
    12 12
     const lazy = {};
    
    13 13
     
    
    14 14
     ChromeUtils.defineESModuleGetters(lazy, {
    
    15
    +  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
    
    15 16
       TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    16 17
       TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    17 18
     });
    
    ... ... @@ -51,26 +52,21 @@ const TOR_CIRCUIT_TOPIC = "TorCircuitChange";
    51 52
     const CLEAR_TIMEOUT = 600_000;
    
    52 53
     
    
    53 54
     /**
    
    54
    - * @typedef {string} CircuitId A string that we use to identify a circuit.
    
    55
    - * Currently, it is a string that combines SOCKS credentials, to make it easier
    
    56
    - * to use as a map key.
    
    57
    - * It is not related to Tor's CircuitIDs.
    
    55
    + * @typedef {string} IsolationKey A string that we use to identify an isolation
    
    56
    + * key. Currently, it is a string that combines SOCKS credentials.
    
    57
    + * Each isolation key is used to identify a set of circuits.
    
    58 58
      */
    
    59 59
     /**
    
    60 60
      * @typedef {number} BrowserId
    
    61 61
      */
    
    62
    -/**
    
    63
    - * @typedef {NodeData[]} CircuitData The data about the nodes, ordered from
    
    64
    - * guard (or bridge) to exit.
    
    65
    - */
    
    66 62
     /**
    
    67 63
      * @typedef BrowserCircuits Circuits related to a certain combination of
    
    68 64
      * isolators (first-party domain and user context ID, currently).
    
    69
    - * @property {CircuitId} current The id of the last known circuit that has been
    
    70
    - * used to fetch data for the isolated context.
    
    71
    - * @property {CircuitId?} pending The id of the last used circuit for this
    
    72
    - * isolation context. We might or might not know data about it, yet. But if we
    
    73
    - * know it, we should move this id into current.
    
    65
    + * @property {IsolationKey} current The last isolation key for which circuit
    
    66
    + * information is known.
    
    67
    + * @property {IsolationKey?} pending The last used isolation key.
    
    68
    + * We might or might not know data about it, yet. But if we know it, we should
    
    69
    + * move this key into current, and pending should be made null.
    
    74 70
      */
    
    75 71
     
    
    76 72
     /**
    
    ... ... @@ -112,9 +108,16 @@ class TorDomainIsolatorImpl {
    112 108
       #catchallDirtySince = Date.now();
    
    113 109
     
    
    114 110
       /**
    
    115
    -   * A map that associates circuit ids to the circuit information.
    
    111
    +   * A map that associates an isolation context to its circuits.
    
    116 112
        *
    
    117
    -   * @type {Map<CircuitId, CircuitData>}
    
    113
    +   * The circuits are represented with a multidimensional array.
    
    114
    +   * The outer layer contains an array for each circuit of the isolation context
    
    115
    +   * (when conflux is in use, a certain isolation context might use more than a
    
    116
    +   * circuit).
    
    117
    +   * The inner layer contains the data about a certain circuit, ordered from
    
    118
    +   * guard/bridge to exit.
    
    119
    +   *
    
    120
    +   * @type {Map<IsolationKey, NodeData[][]>}
    
    118 121
        */
    
    119 122
       #knownCircuits = new Map();
    
    120 123
     
    
    ... ... @@ -191,8 +194,11 @@ class TorDomainIsolatorImpl {
    191 194
       }
    
    192 195
     
    
    193 196
       /**
    
    194
    -   * Get the last circuit used in a certain browser.
    
    195
    -   * The returned data is created when the circuit is first seen, therefore it
    
    197
    +   * Get the circuits being used for a certain browser. There may be multiple
    
    198
    +   * if conflux is being used.
    
    199
    +   *
    
    200
    +   * Note, this returns the last known circuits for the browser. In particular,
    
    201
    +   * the returned data is created when the circuit is first seen, therefore it
    
    196 202
        * could be stale (i.e., the circuit might not be available anymore).
    
    197 203
        *
    
    198 204
        * @param {MozBrowser} browser The browser to get data for
    
    ... ... @@ -200,10 +206,12 @@ class TorDomainIsolatorImpl {
    200 206
        * for
    
    201 207
        * @param {number} userContextId The user context domain we want to get the
    
    202 208
        * circuit for
    
    203
    -   * @returns {NodeData[]} The node data, or an empty array if we do not have
    
    204
    -   * data for the requested key.
    
    209
    +   * @returns {NodeData[][]} An array of all the circuits being used for this
    
    210
    +   * context. Each circuit is represented by an array of nodes, ordered from
    
    211
    +   * the guard/bridge to the exit. If the context has no known circuits, then
    
    212
    +   * this will be an empty array.
    
    205 213
        */
    
    206
    -  getCircuit(browser, domain, userContextId) {
    
    214
    +  getCircuits(browser, domain, userContextId) {
    
    207 215
         const username = this.#makeUsername(domain, userContextId);
    
    208 216
         const circuits = this.#browsers.get(browser.browserId)?.get(username);
    
    209 217
         // This is the only place where circuit data can go out, so the only place
    
    ... ... @@ -278,8 +286,8 @@ class TorDomainIsolatorImpl {
    278 286
             // TODO: What UX to use here? See tor-browser#41708
    
    279 287
           }
    
    280 288
         } else if (topic === lazy.TorProviderTopics.CircuitCredentialsMatched) {
    
    281
    -      const { username, password, circuit } = subject.wrappedJSObject;
    
    282
    -      this.#updateCircuit(username, password, circuit);
    
    289
    +      const { username, password, circuits } = subject.wrappedJSObject;
    
    290
    +      this.#updateCircuits(username, password, circuits);
    
    283 291
         }
    
    284 292
       }
    
    285 293
     
    
    ... ... @@ -421,10 +429,10 @@ class TorDomainIsolatorImpl {
    421 429
        *
    
    422 430
        * @param {string} username The SOCKS username
    
    423 431
        * @param {string} password The SOCKS password
    
    424
    -   * @returns {CircuitId} A string that combines username and password and can
    
    425
    -   * be used for map lookups.
    
    432
    +   * @returns {IsolationKey} A string that combines username and password and
    
    433
    +   * can be used as a key for maps.
    
    426 434
        */
    
    427
    -  #credentialsToId(username, password) {
    
    435
    +  #credentialsToKey(username, password) {
    
    428 436
         return `${username}|${password}`;
    
    429 437
       }
    
    430 438
     
    
    ... ... @@ -540,7 +548,7 @@ class TorDomainIsolatorImpl {
    540 548
           this.#browsers.set(browser.browserId, browserCircuits);
    
    541 549
         }
    
    542 550
         const circuitIds = browserCircuits.get(username) ?? {};
    
    543
    -    const id = this.#credentialsToId(username, password);
    
    551
    +    const id = this.#credentialsToKey(username, password);
    
    544 552
         if (circuitIds.current === id) {
    
    545 553
           // The circuit with these credentials was already built (we already knew
    
    546 554
           // its nodes, or we would not have promoted it to the current circuit).
    
    ... ... @@ -580,36 +588,25 @@ class TorDomainIsolatorImpl {
    580 588
       }
    
    581 589
     
    
    582 590
       /**
    
    583
    -   * Update a circuit, and notify the related circuit displays if it changed.
    
    591
    +   * Update the circuits associated to a certain isolation context, and notify
    
    592
    +   * the related circuit displays if they changed.
    
    584 593
        *
    
    585 594
        * This function is called when a certain stream has succeeded and so we can
    
    586
    -   * associate its SOCKS credential to the circuit it is using.
    
    587
    -   * We receive only the fingerprints of the circuit nodes, but they are enough
    
    588
    -   * to check if the circuit has changed. If it has, we also get the nodes'
    
    589
    -   * information through the control port.
    
    595
    +   * associate its SOCKS credentials to the circuit it is using.
    
    590 596
        *
    
    591 597
        * @param {string} username The SOCKS username
    
    592 598
        * @param {string} password The SOCKS password
    
    593
    -   * @param {NodeFingerprint[]} circuit The fingerprints of the nodes that
    
    594
    -   * compose the circuit
    
    599
    +   * @param {NodeData[][]} circuits The circuits being used for this isolation
    
    600
    +   * context. Each is represented by an array of nodes.
    
    595 601
        */
    
    596
    -  async #updateCircuit(username, password, circuit) {
    
    597
    -    const id = this.#credentialsToId(username, password);
    
    598
    -    let data = this.#knownCircuits.get(id) ?? [];
    
    599
    -    // Should we modify the lower layer to send a circuit identifier, instead?
    
    600
    -    if (
    
    601
    -      circuit.length === data.length &&
    
    602
    -      circuit.every((fp, index) => fp === data[index].fingerprint)
    
    603
    -    ) {
    
    602
    +  async #updateCircuits(username, password, circuits) {
    
    603
    +    const key = this.#credentialsToKey(username, password);
    
    604
    +    let current = this.#knownCircuits.get(key);
    
    605
    +    if (lazy.ObjectUtils.deepEqual(current, circuits)) {
    
    604 606
           return;
    
    605 607
         }
    
    606
    -
    
    607
    -    const provider = await lazy.TorProviderBuilder.build();
    
    608
    -    data = await Promise.all(
    
    609
    -      circuit.map(fingerprint => provider.getNodeInfo(fingerprint))
    
    610
    -    );
    
    611
    -    logger.debug(`Updating circuit ${id}`, data);
    
    612
    -    this.#knownCircuits.set(id, data);
    
    608
    +    logger.info(`Updating circuits for ${key}`, circuits);
    
    609
    +    this.#knownCircuits.set(key, circuits);
    
    613 610
         // We know that something changed, but we cannot know if anyone is
    
    614 611
         // interested in this change. So, we have to notify all the possible
    
    615 612
         // consumers of the data in any case.
    

  • toolkit/components/tor-launcher/TorProvider.sys.mjs
    ... ... @@ -154,9 +154,24 @@ export class TorProvider {
    154 154
        * built before the new identity but not yet used. If we cleaned the map, we
    
    155 155
        * risked of not having the data about it.
    
    156 156
        *
    
    157
    -   * @type {Map<CircuitID, Promise<NodeFingerprint[]>>}
    
    157
    +   * @type {Map<CircuitID, CircuitInfo>}
    
    158 158
        */
    
    159 159
       #circuits = new Map();
    
    160
    +
    
    161
    +  /**
    
    162
    +   * Cache with node information.
    
    163
    +   *
    
    164
    +   * As a matter of fact, the circuit display backend continuously ask for
    
    165
    +   * information about the same nodes (e.g., the guards/bridges, and the exit
    
    166
    +   * for conflux circuits).
    
    167
    +   * Therefore, we can keep a cache of them to avoid a few control port lookups.
    
    168
    +   * And since it is likely we will get asked information about all nodes that
    
    169
    +   * appear in circuits, we can build this cache proactively.
    
    170
    +   *
    
    171
    +   * @type {Map<NodeFingerprint, Promise<NodeData>>}
    
    172
    +   */
    
    173
    +  #nodeInfo = new Map();
    
    174
    +
    
    160 175
       /**
    
    161 176
        * The last used bridge, or null if bridges are not in use or if it was not
    
    162 177
        * possible to detect the bridge. This needs the user to have specified bridge
    
    ... ... @@ -457,50 +472,6 @@ export class TorProvider {
    457 472
         );
    
    458 473
       }
    
    459 474
     
    
    460
    -  /**
    
    461
    -   * Returns tha data about a relay or a bridge.
    
    462
    -   *
    
    463
    -   * @param {string} id The fingerprint of the node to get data about
    
    464
    -   * @returns {Promise<NodeData>}
    
    465
    -   */
    
    466
    -  async getNodeInfo(id) {
    
    467
    -    const node = {
    
    468
    -      fingerprint: id,
    
    469
    -      ipAddrs: [],
    
    470
    -      bridgeType: null,
    
    471
    -      regionCode: null,
    
    472
    -    };
    
    473
    -    const bridge = (await this.#controller.getBridges())?.find(
    
    474
    -      foundBridge => foundBridge.id?.toUpperCase() === id.toUpperCase()
    
    475
    -    );
    
    476
    -    if (bridge) {
    
    477
    -      node.bridgeType = bridge.transport ?? "";
    
    478
    -      // Attempt to get an IP address from bridge address string.
    
    479
    -      const ip = bridge.addr.match(/^\[?([^\]]+)\]?:\d+$/)?.[1];
    
    480
    -      if (ip && !ip.startsWith("0.")) {
    
    481
    -        node.ipAddrs.push(ip);
    
    482
    -      }
    
    483
    -    } else {
    
    484
    -      node.ipAddrs = await this.#controller.getNodeAddresses(id);
    
    485
    -    }
    
    486
    -    // tor-browser#43116, tor-browser-build#41224: on Android, we do not ship
    
    487
    -    // the GeoIP databases to save some space. So skip it for now.
    
    488
    -    if (node.ipAddrs.length && !TorLauncherUtil.isAndroid) {
    
    489
    -      // Get the country code for the node's IP address.
    
    490
    -      try {
    
    491
    -        // Expect a 2-letter ISO3166-1 code, which should also be a valid
    
    492
    -        // BCP47 Region subtag.
    
    493
    -        const regionCode = await this.#controller.getIPCountry(node.ipAddrs[0]);
    
    494
    -        if (regionCode && regionCode !== "??") {
    
    495
    -          node.regionCode = regionCode.toUpperCase();
    
    496
    -        }
    
    497
    -      } catch (e) {
    
    498
    -        logger.warn(`Cannot get a country for IP ${node.ipAddrs[0]}`, e);
    
    499
    -      }
    
    500
    -    }
    
    501
    -    return node;
    
    502
    -  }
    
    503
    -
    
    504 475
       /**
    
    505 476
        * Add a private key to the Tor configuration.
    
    506 477
        *
    
    ... ... @@ -936,14 +907,76 @@ export class TorProvider {
    936 907
         return crypto.getRandomValues(new Uint8Array(kPasswordLen));
    
    937 908
       }
    
    938 909
     
    
    910
    +  // Circuit handling.
    
    911
    +
    
    939 912
       /**
    
    940 913
        * Ask Tor the circuits it already knows to populate our circuit map with the
    
    941 914
        * circuits that were already open before we started listening for events.
    
    942 915
        */
    
    943 916
       async #fetchCircuits() {
    
    944
    -    for (const { id, nodes } of await this.#controller.getCircuits()) {
    
    945
    -      this.onCircuitBuilt(id, nodes);
    
    917
    +    for (const circuit of await this.#controller.getCircuits()) {
    
    918
    +      this.onCircuitBuilt(circuit);
    
    919
    +    }
    
    920
    +  }
    
    921
    +
    
    922
    +  /**
    
    923
    +   * Returns tha data about a relay or a bridge.
    
    924
    +   *
    
    925
    +   * @param {string} id The fingerprint of the node to get data about
    
    926
    +   * @returns {Promise<NodeData>}
    
    927
    +   */
    
    928
    +  #getNodeInfo(id) {
    
    929
    +    // This is an async method, so it will not insert the result, but a promise.
    
    930
    +    // However, this is what we want.
    
    931
    +    const info = this.#nodeInfo.getOrInsertComputed(id, async () => {
    
    932
    +      const node = {
    
    933
    +        fingerprint: id,
    
    934
    +        ipAddrs: [],
    
    935
    +        bridgeType: null,
    
    936
    +        regionCode: null,
    
    937
    +      };
    
    938
    +
    
    939
    +      const bridge = (await this.#controller.getBridges())?.find(
    
    940
    +        foundBridge => foundBridge.id?.toUpperCase() === id.toUpperCase()
    
    941
    +      );
    
    942
    +      if (bridge) {
    
    943
    +        node.bridgeType = bridge.transport ?? "";
    
    944
    +        // Attempt to get an IP address from bridge address string.
    
    945
    +        const ip = bridge.addr.match(/^\[?([^\]]+)\]?:\d+$/)?.[1];
    
    946
    +        if (ip && !ip.startsWith("0.")) {
    
    947
    +          node.ipAddrs.push(ip);
    
    948
    +        }
    
    949
    +      } else {
    
    950
    +        node.ipAddrs = await this.#controller.getNodeAddresses(id);
    
    951
    +      }
    
    952
    +
    
    953
    +      // tor-browser#43116, tor-browser-build#41224: on Android, we do not ship
    
    954
    +      // the GeoIP databases to save some space. So skip it for now.
    
    955
    +      if (node.ipAddrs.length && !TorLauncherUtil.isAndroid) {
    
    956
    +        // Get the country code for the node's IP address.
    
    957
    +        try {
    
    958
    +          // Expect a 2-letter ISO3166-1 code, which should also be a valid
    
    959
    +          // BCP47 Region subtag.
    
    960
    +          const regionCode = await this.#controller.getIPCountry(
    
    961
    +            node.ipAddrs[0]
    
    962
    +          );
    
    963
    +          if (regionCode && regionCode !== "??") {
    
    964
    +            node.regionCode = regionCode.toUpperCase();
    
    965
    +          }
    
    966
    +        } catch (e) {
    
    967
    +          logger.warn(`Cannot get a country for IP ${node.ipAddrs[0]}`, e);
    
    968
    +        }
    
    969
    +      }
    
    970
    +      return node;
    
    971
    +    });
    
    972
    +
    
    973
    +    const MAX_NODES = 300;
    
    974
    +    while (this.#nodeInfo.size > MAX_NODES) {
    
    975
    +      const oldestKey = this.#nodeInfo.keys().next().value;
    
    976
    +      this.#nodeInfo.delete(oldestKey);
    
    946 977
         }
    
    978
    +
    
    979
    +    return info;
    
    947 980
       }
    
    948 981
     
    
    949 982
       // Notification handlers
    
    ... ... @@ -1046,24 +1079,43 @@ export class TorProvider {
    1046 1079
        * If a change of bridge is detected (including a change from bridge to a
    
    1047 1080
        * normal guard), a notification is broadcast.
    
    1048 1081
        *
    
    1049
    -   * @param {CircuitID} id The circuit ID
    
    1050
    -   * @param {NodeFingerprint[]} nodes The nodes that compose the circuit
    
    1082
    +   * @param {CircuitInfo} circuit The information about the circuit
    
    1051 1083
        */
    
    1052
    -  async onCircuitBuilt(id, nodes) {
    
    1053
    -    this.#circuits.set(id, nodes);
    
    1054
    -    logger.debug(`Built tor circuit ${id}`, nodes);
    
    1084
    +  onCircuitBuilt(circuit) {
    
    1085
    +    logger.debug(`Built tor circuit ${circuit.id}`, circuit);
    
    1086
    +
    
    1055 1087
         // Ignore circuits of length 1, that are used, for example, to probe
    
    1056 1088
         // bridges. So, only store them, since we might see streams that use them,
    
    1057 1089
         // but then early-return.
    
    1058
    -    if (nodes.length === 1) {
    
    1090
    +    if (circuit.nodes.length === 1) {
    
    1059 1091
           return;
    
    1060 1092
         }
    
    1061 1093
     
    
    1062
    -    if (this.#currentBridge?.fingerprint !== nodes[0]) {
    
    1063
    -      const nodeInfo = await this.getNodeInfo(nodes[0]);
    
    1094
    +    this.#circuits.set(circuit.id, circuit);
    
    1095
    +
    
    1096
    +    for (const fingerprint of circuit.nodes) {
    
    1097
    +      // To make the pending onStreamSentConnect call for this circuit faster,
    
    1098
    +      // we pre-fetch the node data, which should be cached by the time it is
    
    1099
    +      // called. No need to await here.
    
    1100
    +      this.#getNodeInfo(fingerprint);
    
    1101
    +    }
    
    1102
    +
    
    1103
    +    this.#maybeBridgeChanged(circuit);
    
    1104
    +  }
    
    1105
    +
    
    1106
    +  /**
    
    1107
    +   * Broadcast a bridge change, if needed.
    
    1108
    +   *
    
    1109
    +   * @param {CircuitInfo} circuit The information about the circuit
    
    1110
    +   */
    
    1111
    +  #maybeBridgeChanged(circuit) {
    
    1112
    +    if (this.#currentBridge?.fingerprint === circuit.nodes[0]) {
    
    1113
    +      return;
    
    1114
    +    }
    
    1115
    +    this.#getNodeInfo(circuit.nodes[0]).then(nodeInfo => {
    
    1064 1116
           let notify = false;
    
    1065 1117
           if (nodeInfo?.bridgeType) {
    
    1066
    -        logger.info(`Bridge changed to ${nodes[0]}`);
    
    1118
    +        logger.info(`Bridge changed to ${circuit.nodes[0]}`);
    
    1067 1119
             this.#currentBridge = nodeInfo;
    
    1068 1120
             notify = true;
    
    1069 1121
           } else if (this.#currentBridge) {
    
    ... ... @@ -1074,7 +1126,7 @@ export class TorProvider {
    1074 1126
           if (notify) {
    
    1075 1127
             Services.obs.notifyObservers(null, TorProviderTopics.BridgeChanged);
    
    1076 1128
           }
    
    1077
    -    }
    
    1129
    +    });
    
    1078 1130
       }
    
    1079 1131
     
    
    1080 1132
       /**
    
    ... ... @@ -1091,48 +1143,60 @@ export class TorProvider {
    1091 1143
       /**
    
    1092 1144
        * Handle a notification about a stream switching to the sentconnect status.
    
    1093 1145
        *
    
    1094
    -   * @param {StreamID} streamId The ID of the stream that switched to the
    
    1146
    +   * @param {StreamID} _streamId The ID of the stream that switched to the
    
    1095 1147
        * sentconnect status.
    
    1096 1148
        * @param {CircuitID} circuitId The ID of the circuit used by the stream
    
    1097 1149
        * @param {string} username The SOCKS username
    
    1098 1150
        * @param {string} password The SOCKS password
    
    1099 1151
        */
    
    1100
    -  async onStreamSentConnect(streamId, circuitId, username, password) {
    
    1152
    +  async onStreamSentConnect(_streamId, circuitId, username, password) {
    
    1101 1153
         if (!username || !password) {
    
    1102 1154
           return;
    
    1103 1155
         }
    
    1104 1156
         logger.debug("Stream sentconnect event", username, password, circuitId);
    
    1105
    -    let circuit = this.#circuits.get(circuitId);
    
    1106
    -    if (!circuit) {
    
    1107
    -      circuit = new Promise((resolve, reject) => {
    
    1108
    -        this.#controlConnection.getCircuits().then(circuits => {
    
    1109
    -          for (const { id, nodes } of circuits) {
    
    1110
    -            if (id === circuitId) {
    
    1111
    -              resolve(nodes);
    
    1112
    -              return;
    
    1113
    -            }
    
    1114
    -            // Opportunistically collect circuits, since we are iterating them.
    
    1115
    -            this.#circuits.set(id, nodes);
    
    1116
    -          }
    
    1117
    -          logger.error(
    
    1118
    -            `Seen a STREAM SENTCONNECT with circuit ${circuitId}, but Tor did not send information about it.`
    
    1119
    -          );
    
    1120
    -          reject();
    
    1121
    -        });
    
    1122
    -      });
    
    1123
    -      this.#circuits.set(circuitId, circuit);
    
    1157
    +    if (!this.#circuits.has(circuitId)) {
    
    1158
    +      // tor-browser#42132: When using onion-grater (e.g., in Tails), we might
    
    1159
    +      // not receive the CIRC BUILT event, as it is impossible to know whether
    
    1160
    +      // that circuit will be the browser's at that point. So, we will have to
    
    1161
    +      // poll circuits and wait for that to finish to be able to get the data.
    
    1162
    +      try {
    
    1163
    +        await this.#fetchCircuits();
    
    1164
    +      } catch {
    
    1165
    +        return;
    
    1166
    +      }
    
    1124 1167
         }
    
    1125
    -    try {
    
    1126
    -      circuit = await circuit;
    
    1127
    -    } catch {
    
    1168
    +
    
    1169
    +    const primaryCircuit = this.#circuits.get(circuitId);
    
    1170
    +    if (!primaryCircuit) {
    
    1171
    +      logger.error(
    
    1172
    +        `Seen a STREAM SENTCONNECT with circuit ${circuitId}, but Tor did not send information about it.`
    
    1173
    +      );
    
    1128 1174
           return;
    
    1129 1175
         }
    
    1176
    +
    
    1177
    +    const circuitIds = [circuitId];
    
    1178
    +    if (primaryCircuit.confluxId) {
    
    1179
    +      circuitIds.push(
    
    1180
    +        ...this.#circuits
    
    1181
    +          .entries()
    
    1182
    +          .filter(
    
    1183
    +            ([id, circ]) =>
    
    1184
    +              circ.confluxId === primaryCircuit.confluxId && id != circuitId
    
    1185
    +          )
    
    1186
    +          .map(([id]) => id)
    
    1187
    +      );
    
    1188
    +    }
    
    1189
    +    const circuits = await Promise.all(
    
    1190
    +      circuitIds.map(id =>
    
    1191
    +        Promise.all(this.#circuits.get(id).nodes.map(n => this.#getNodeInfo(n)))
    
    1192
    +      )
    
    1193
    +    );
    
    1130 1194
         Services.obs.notifyObservers(
    
    1131 1195
           {
    
    1132 1196
             wrappedJSObject: {
    
    1133 1197
               username,
    
    1134 1198
               password,
    
    1135
    -          circuit,
    
    1199
    +          circuits,
    
    1136 1200
             },
    
    1137 1201
           },
    
    1138 1202
           TorProviderTopics.CircuitCredentialsMatched
    

  • _______________________________________________
    tor-commits mailing list -- tor-commits@xxxxxxxxxxxxxxxxxxxx
    To unsubscribe send an email to tor-commits-leave@xxxxxxxxxxxxxxxxxxxx