Pier Angelo Vendrame pushed to branch tor-browser-128.4.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits: 82705e37 by Pier Angelo Vendrame at 2024-11-12T17:32:04+01:00 fixup! Bug 40933: Add tor-launcher functionality
Make optional members in JSDoc comments more consistent with Firefox's codebase.
- - - - - 163c91b6 by Pier Angelo Vendrame at 2024-11-12T17:32:05+01:00 fixup! Bug 40933: Add tor-launcher functionality
Bug 10439: Ask the the SOCKS port to the tor process.
Allow specifying a negative port as TOR_SOCKS_PORT to let the tor process choose one for us.
This does not play too well with DisableNetwork, which we use to start and stop the bootstrap, so we have to query the port every time we change this setting.
Also, currently we use Firefox's preferences for SOCKS port. This prevents us from keeping a negative number saved in them. See also tor-browser#42062.
- - - - - f7e4e221 by Pier Angelo Vendrame at 2024-11-12T17:32:06+01:00 fixup! Bug 40933: Add tor-launcher functionality
Bug 42714: Allow to optionally use a TCP listener on Android.
- - - - - bb3cd92e by Pier Angelo Vendrame at 2024-11-12T17:32:06+01:00 fixup! Bug 42247: Android helpers for the TorProvider
Bug 42714: Allow to optionally use a TCP listener on Android.
- - - - -
6 changed files:
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java - toolkit/components/tor-launcher/TorControlPort.sys.mjs - toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs - toolkit/components/tor-launcher/TorProcess.sys.mjs - toolkit/components/tor-launcher/TorProcessAndroid.sys.mjs - toolkit/components/tor-launcher/TorProvider.sys.mjs
Changes:
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java ===================================== @@ -220,12 +220,18 @@ public class TorIntegrationAndroid implements BundleEventListener { if (previousProcess != null) { Log.w(TAG, "We still have a running process: " + previousProcess.getHandle()); } - mTorProcess = new TorProcess(handle); + + boolean tcpSocks = message.getBoolean("tcpSocks", false); + mTorProcess = new TorProcess(handle, tcpSocks);
GeckoBundle bundle = new GeckoBundle(3); bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE); - bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE); bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE); + if (tcpSocks) { + bundle.putInt("socksPort", 0); + } else { + bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE); + } callback.sendSuccess(bundle); }
@@ -254,10 +260,12 @@ public class TorIntegrationAndroid implements BundleEventListener { private static final String EVENT_TOR_START_FAILED = "GeckoView:Tor:TorStartFailed"; private static final String EVENT_TOR_EXITED = "GeckoView:Tor:TorExited"; private final String mHandle; + private final boolean mTcpSocks; private Process mProcess = null;
- TorProcess(String handle) { + TorProcess(String handle, boolean tcpSocks) { mHandle = handle; + mTcpSocks = tcpSocks; setName("tor-process-" + handle); start(); } @@ -273,8 +281,13 @@ public class TorIntegrationAndroid implements BundleEventListener { args.add("1"); args.add("+__ControlPort"); args.add("unix:" + ipcDir + CONTROL_PORT_FILE); + final String socksFlags = " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth"; args.add("+__SocksPort"); - args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth"); + args.add("unix:" + ipcDir + SOCKS_FILE + socksFlags); + if (mTcpSocks) { + args.add("+__SocksPort"); + args.add("auto " + socksFlags); + } args.add("CookieAuthentication"); args.add("1"); args.add("CookieAuthFile");
===================================== toolkit/components/tor-launcher/TorControlPort.sys.mjs ===================================== @@ -298,6 +298,12 @@ class AsyncSocket { * @property {string} [options] Optional options passed to the binary (only for * exec) */ +/** + * @typedef {object} SocksListener + * @property {string} [ipcPath] path to a Unix socket to use for an IPC proxy + * @property {string} [host] The host to connect for a TCP proxy + * @property {number} [port] The port number to use for a TCP proxy + */ /** * @typedef {object} OnionAuthKeyInfo * @property {string} address The address of the onion service @@ -746,6 +752,32 @@ export class TorController { return this.#getInfo(`ip-to-country/${ip}`); }
+ /** + * Ask tor which ports it is listening to for SOCKS connections. + * + * @returns {Promise<SocksListener[]>} An array of addresses. It might be + * empty (e.g., when DisableNetwork is set) + */ + async getSocksListeners() { + const listeners = await this.#getInfo("net/listeners/socks"); + return Array.from( + listeners.matchAll(/\s*("(?:[^"\]|\.)*"|\S+)\s*/g), + m => { + const listener = TorParsers.unescapeString(m[1]); + if (listener.startsWith("unix:/")) { + return { ipcPath: listener.substring(5) }; + } + const idx = listener.lastIndexOf(":"); + const host = listener.substring(0, idx); + const port = parseInt(listener.substring(idx + 1)); + if (isNaN(port) || port <= 0 || port > 65535 || !host || !port) { + throw new Error(`Could not parse the SOCKS listener ${listener}.`); + } + return { host, port }; + } + ); + } + /** * Ask Tor a list of circuits. *
===================================== toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs ===================================== @@ -449,7 +449,7 @@ export const TorLauncherUtil = Object.freeze({ * If network.proxy.socks contains a file: URL, a default value of * "127.0.0.1" is used instead. * If the network.proxy.socks_port value is not valid (outside the - * (0; 65535] range), a default value of 9150 is used instead. + * (0; 65535] range), we will let the tor daemon choose a port. * * The SOCKS configuration will not influence the launch of a tor daemon and * the configuration of the control port in any way. @@ -458,13 +458,6 @@ export const TorLauncherUtil = Object.freeze({ * This also applies to TOR_TRANSPROXY (at least for now): tor will be * launched with its defaults. * - * TODO: add a preference to ignore the current configuration, and let tor - * listen on any free port. Then, the browser will prompt the daemon the port - * to use through the control port (even though this is quite dangerous at the - * moment, because with network disabled tor will disable also the SOCKS - * listeners, so it means that we will have to check it every time we change - * the network status). - * * @returns {SocksSettings} */ getPreferredSocksConfiguration() { @@ -491,7 +484,7 @@ export const TorLauncherUtil = Object.freeze({ } if (Services.env.exists("TOR_SOCKS_PORT")) { const port = parseInt(Services.env.get("TOR_SOCKS_PORT"), 10); - if (Number.isInteger(port) && port > 0 && port <= 65535) { + if (Number.isInteger(port) && port >= 0 && port <= 65535) { socksPortInfo.port = port; useIPC = false; } @@ -522,20 +515,32 @@ export const TorLauncherUtil = Object.freeze({ socksPortInfo.host = socksAddrHasHost ? socksAddr : "127.0.0.1"; }
- if (!socksPortInfo.port) { + if (socksPortInfo.port === undefined) { let socksPort = Services.prefs.getIntPref( "network.proxy.socks_port", - 0 + 9150 ); - // This pref is set as 0 by default in Firefox, use 9150 if we get 0. - socksPortInfo.port = - socksPort > 0 && socksPort <= 65535 ? socksPort : 9150; + if (socksPort > 0 && socksPort <= 65535) { + socksPortInfo.port = socksPort; + } else { + // Automatic port number, we have to query tor over the control port + // every time we change DisableNetwork. + socksPortInfo.port = 0; + } } }
return socksPortInfo; },
+ /** + * Apply our proxy configuration to the browser. + * + * Currently, we try to configure the Tor daemon to match the browser's + * configuration, but this might change in the future (tor-browser#42062). + * + * @param {SocksSettings} socksPortInfo The configuration to apply + */ setProxyConfiguration(socksPortInfo) { if (socksPortInfo.transproxy) { Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false); @@ -556,7 +561,7 @@ export const TorLauncherUtil = Object.freeze({ if (socksPortInfo.host) { Services.prefs.setCharPref("network.proxy.socks", socksPortInfo.host); } - if (socksPortInfo.port) { + if (socksPortInfo.port > 0 && socksPortInfo.port <= 65535) { Services.prefs.setIntPref( "network.proxy.socks_port", socksPortInfo.port
===================================== toolkit/components/tor-launcher/TorProcess.sys.mjs ===================================== @@ -53,13 +53,16 @@ export class TorProcess { throw new Error("Unauthenticated control port is not supported"); }
- const checkPort = port => + const checkPort = (port, allowZero) => port === undefined || - (Number.isInteger(port) && port > 0 && port < 65535); - if (!checkPort(controlSettings?.port)) { + (Number.isInteger(port) && + port < 65535 && + (port > 0 || (allowZero && port === 0))); + if (!checkPort(controlSettings?.port, false)) { throw new Error("Invalid control port"); } - if (!checkPort(socksSettings.port)) { + // Port 0 for SOCKS means automatic port. + if (!checkPort(socksSettings.port, true)) { throw new Error("Invalid port specified for the SOCKS port"); }
@@ -296,10 +299,12 @@ export class TorProcess { let socksPortArg; if (this.#socksSettings.ipcFile) { socksPortArg = this.#socksSettings.ipcFile; - } else if (this.#socksSettings.port != 0) { + } else if (this.#socksSettings.port > 0) { socksPortArg = this.#socksSettings.host ? `${this.#socksSettings.host}:${this.#socksSettings.port}` : this.#socksSettings.port.toString(); + } else { + socksPortArg = "auto"; } if (socksPortArg) { const socksPortFlags = Services.prefs.getCharPref(
===================================== toolkit/components/tor-launcher/TorProcessAndroid.sys.mjs ===================================== @@ -77,6 +77,10 @@ export class TorProcessAndroid { config = await lazy.EventDispatcher.instance.sendRequestForResult({ type: TorOutgoingEvents.start, handle: this.#processHandle, + tcpSocks: Services.prefs.getBoolPref( + "extensions.torlauncher.socks_port_use_tcp", + false + ), }); logger.debug("Sent the start event."); } catch (e) {
===================================== toolkit/components/tor-launcher/TorProvider.sys.mjs ===================================== @@ -27,23 +27,23 @@ const logger = console.createInstance({ * @typedef {object} ControlPortSettings An object with the settings to use for * the control port. All the entries are optional, but an authentication * mechanism and a communication method must be specified. - * @property {Uint8Array=} password The clear text password as an array of + * @property {Uint8Array} [password] The clear text password as an array of * bytes. It must always be defined, unless cookieFilePath is - * @property {string=} cookieFilePath The path to the cookie file to use for + * @property {string} [cookieFilePath] The path to the cookie file to use for * authentication - * @property {nsIFile=} ipcFile The nsIFile object with the path to a Unix + * @property {nsIFile} [ipcFile] The nsIFile object with the path to a Unix * socket to use for control socket - * @property {string=} host The host to connect for a TCP control port - * @property {number=} port The port number to use for a TCP control port + * @property {string} [host] The host to connect for a TCP control port + * @property {number} [port] The port number to use for a TCP control port */ /** * @typedef {object} SocksSettings An object that includes the proxy settings to * be configured in the browser. - * @property {boolean=} transproxy If true, no proxy is configured - * @property {nsIFile=} ipcFile The nsIFile object with the path to a Unix + * @property {boolean} [transproxy] If true, no proxy is configured + * @property {nsIFile} [ipcFile] The nsIFile object with the path to a Unix * socket to use for an IPC proxy - * @property {string=} host The host to connect for a TCP proxy - * @property {number=} port The port number to use for a TCP proxy + * @property {string} [host] The host to connect for a TCP proxy + * @property {number} [port] The port number to use for a TCP proxy */ /** * @typedef {object} LogEntry An object with a log message @@ -345,6 +345,25 @@ export class TorProvider { */ async connect() { await this.#controller.setNetworkEnabled(true); + if (this.#socksSettings.port === 0) { + // Enablign/disabling network resets also the SOCKS listener. + // So, every time we do it, we need to update the browser's configuration + // to use the updated port. + const settings = structuredClone(this.#socksSettings); + for (const listener of await this.#controller.getSocksListeners()) { + // When set to automatic port, ignore any IPC listener, as the intention + // was to use TCP. + if (listener.ipcPath) { + continue; + } + // The tor daemon can have any number of SOCKS listeners (see SocksPort + // in man 1 tor). We take for granted that any TCP one will work for us. + settings.host = listener.host; + settings.port = listener.port; + break; + } + TorLauncherUtil.setProxyConfiguration(settings); + } this.#lastWarning = {}; this.retrieveBootstrapStatus(); } @@ -569,14 +588,24 @@ export class TorProvider { logger.debug("Trying to start the tor process."); const res = await this.#torProcess.start(); if (TorLauncherUtil.isAndroid) { + logger.debug("Configuration from TorProcessAndriod", res); this.#controlPortSettings = { ipcFile: new lazy.FileUtils.File(res.controlPortPath), cookieFilePath: res.cookieFilePath, }; this.#socksSettings = { transproxy: false, - ipcFile: new lazy.FileUtils.File(res.socksPath), }; + if (res.socksPath) { + this.#socksSettings.ipcFile = new lazy.FileUtils.File(res.socksPath); + } else if (res.socksPort !== undefined) { + this.#socksSettings.host = res.socksHost ?? "127.0.0.1"; + this.#socksSettings.port = res.socksPort; + } else { + throw new Error( + "TorProcessAndroid did not return a valid SOCKS configuration." + ); + } } logger.info("Started a tor process"); }
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/d4097f9...