Skip to content

Commit

Permalink
Let Tor pick control port
Browse files Browse the repository at this point in the history
To select the Tor control port, we bind to a random port, close it and
pass the port number to Tor. It can happen that Tor tries to bind to the
port before it is closed.  In this case, the Tor startup will fail
because Tor will never print a line containing "[notice] Opened Control
listener connection (ready) on ". Up until now, Bisq monitored the Tor
log file to know when the control port is ready. Now Tor writes the
control port to a file and Bisq reads that file.

Ref: bisq-network#1798
  • Loading branch information
alvasw committed Apr 15, 2024
1 parent 60a7412 commit 22238c9
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@

@Builder
public class BaseTorrcGenerator implements TorrcConfigGenerator {
private static final String CONTROL_PORT_WRITE_TO_FILE_CONFIG_KEY = "ControlPortWriteToFile";

private final Path dataDirPath;
private final int controlPort;
private final Path controlPortWriteFile;
private final String hashedControlPassword;
private final boolean isTestNetwork;

public BaseTorrcGenerator(Path dataDirPath, int controlPort, String hashedControlPassword, boolean isTestNetwork) {
public BaseTorrcGenerator(Path dataDirPath, Path controlPortWriteFile, String hashedControlPassword, boolean isTestNetwork) {
this.dataDirPath = dataDirPath;
this.controlPort = controlPort;
this.controlPortWriteFile = controlPortWriteFile;
this.hashedControlPassword = hashedControlPassword;
this.isTestNetwork = isTestNetwork;
}
Expand All @@ -43,7 +44,8 @@ public Map<String, String> generate() {
Map<String, String> torConfigMap = new HashMap<>();
torConfigMap.put("DataDirectory", dataDirPath.toAbsolutePath().toString());

torConfigMap.put("ControlPort", "127.0.0.1:" + controlPort);
torConfigMap.put("ControlPort", "127.0.0.1:auto");
torConfigMap.put(CONTROL_PORT_WRITE_TO_FILE_CONFIG_KEY, controlPortWriteFile.toAbsolutePath().toString());
torConfigMap.put("HashedControlPassword", hashedControlPassword);

String logLevel = isTestNetwork ? "debug" : "notice";
Expand Down
32 changes: 18 additions & 14 deletions network/tor/tor/src/main/java/bisq/tor/TorService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import bisq.tor.installer.TorInstaller;
import bisq.tor.onionservice.CreateOnionServiceResponse;
import bisq.tor.onionservice.OnionServicePublishService;
import bisq.tor.process.ControlPortFilePoller;
import bisq.tor.process.NativeTorProcess;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -73,9 +74,8 @@ public CompletableFuture<Boolean> initialize() {

installTorIfNotUpToDate();

int controlPort = NetworkUtils.findFreeSystemPort();
PasswordDigest hashedControlPassword = PasswordDigest.generateDigest();
createTorrcConfigFile(torDataDirPath, controlPort, hashedControlPassword);
createTorrcConfigFile(torDataDirPath, hashedControlPassword);

var nativeTorProcess = new NativeTorProcess(torDataDirPath);
torProcess = Optional.of(nativeTorProcess);
Expand All @@ -89,18 +89,23 @@ public CompletableFuture<Boolean> initialize() {
}

nativeTorProcess.start();
nativeTorProcess.waitUntilControlPortReady();

nativeTorController.connect(controlPort, hashedControlPassword);
nativeTorController.bindTorToConnection();
Path controlDirPath = torDataDirPath.resolve(NativeTorProcess.CONTROL_DIR_NAME);
Path controlPortFilePath = controlDirPath.resolve("control");

nativeTorController.enableTorNetworking();
nativeTorController.waitUntilBootstrapped();
return new ControlPortFilePoller(controlPortFilePath)
.parsePort()
.thenAccept(controlPort -> {
nativeTorController.connect(controlPort, hashedControlPassword);
nativeTorController.bindTorToConnection();

int socksPort = this.socksPort.orElseThrow();
torSocksProxyFactory = Optional.of(new TorSocksProxyFactory(socksPort));
nativeTorController.enableTorNetworking();
nativeTorController.waitUntilBootstrapped();

return CompletableFuture.completedFuture(true);
int port = socksPort.orElseThrow();
torSocksProxyFactory = Optional.of(new TorSocksProxyFactory(port));
})
.thenApply(unused -> true);
}

@Override
Expand All @@ -125,8 +130,8 @@ public CompletableFuture<CreateOnionServiceResponse> createOnionService(int port
int localPort = localServerSocket.getLocalPort();
return onionServicePublishService.publish(privateOpenSshKey, onionAddressString, port, localPort)
.thenApply(onionAddress -> {
log.info("Tor hidden service Ready. Took {} ms. Onion address={}",
System.currentTimeMillis() - ts, onionAddress);
log.info("Tor hidden service Ready. Took {} ms. Onion address={}",
System.currentTimeMillis() - ts, onionAddress);
return new CreateOnionServiceResponse(localServerSocket, onionAddress);
}
);
Expand Down Expand Up @@ -157,14 +162,13 @@ private void installTorIfNotUpToDate() {
torInstaller.installIfNotUpToDate();
}

private void createTorrcConfigFile(Path dataDir, int controlPort, PasswordDigest hashedControlPassword) {
private void createTorrcConfigFile(Path dataDir, PasswordDigest hashedControlPassword) {
int socksPort = NetworkUtils.findFreeSystemPort();
this.socksPort = Optional.of(socksPort);

TorrcClientConfigFactory torrcClientConfigFactory = TorrcClientConfigFactory.builder()
.isTestNetwork(transportConfig.isTestNetwork())
.dataDir(dataDir)
.controlPort(controlPort)
.socksPort(socksPort)
.hashedControlPassword(hashedControlPassword)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import bisq.network.tor.common.torrc.ClientTorrcGenerator;
import bisq.network.tor.common.torrc.TestNetworkTorrcGenerator;
import bisq.network.tor.common.torrc.TorrcConfigGenerator;
import bisq.tor.process.NativeTorProcess;
import lombok.Builder;
import net.freehaven.tor.control.PasswordDigest;

Expand All @@ -33,18 +34,15 @@ public class TorrcClientConfigFactory {

private final boolean isTestNetwork;
private final Path dataDir;
private final int controlPort;
private final int socksPort;
private final PasswordDigest hashedControlPassword;

public TorrcClientConfigFactory(boolean isTestNetwork,
Path dataDir,
int controlPort,
int socksPort,
PasswordDigest hashedControlPassword) {
this.isTestNetwork = isTestNetwork;
this.dataDir = dataDir;
this.controlPort = controlPort;
this.socksPort = socksPort;
this.hashedControlPassword = hashedControlPassword;
}
Expand All @@ -71,7 +69,7 @@ private TorrcConfigGenerator clientTorrcGenerator() {
private TorrcConfigGenerator baseTorrcGenerator() {
return BaseTorrcGenerator.builder()
.dataDirPath(dataDir)
.controlPort(controlPort)
.controlPortWriteFile(dataDir.resolve(NativeTorProcess.CONTROL_DIR_NAME).resolve("control"))
.hashedControlPassword(hashedControlPassword.getHashedPassword())
.isTestNetwork(isTestNetwork)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

@Slf4j
public class NativeTorProcess {
public static final String ARG_OWNER_PID = "__OwningControllerProcess";
public static final String CONTROL_DIR_NAME = "control";

private final Path torDataDirPath;
private final Path torBinaryPath;
Expand All @@ -48,6 +50,7 @@ public NativeTorProcess(Path torDataDirPath) {
}

public void start() {
createTorControlDirectory();
String absoluteTorrcPathAsString = torrcPath.toAbsolutePath().toString();

String ownerPid = Pid.getMyPid();
Expand Down Expand Up @@ -107,14 +110,14 @@ public void waitUntilExited() {
});
}

private String computeLdPreloadVariable() {
File[] sharedLibraries = torDataDirPath.toFile()
.listFiles((file, fileName) -> fileName.contains(".so."));
Objects.requireNonNull(sharedLibraries);

return Arrays.stream(sharedLibraries)
.map(File::getAbsolutePath)
.collect(Collectors.joining(":"));
private void createTorControlDirectory() {
File controlDirFile = torDataDirPath.resolve(CONTROL_DIR_NAME).toFile();
if (!controlDirFile.exists()) {
boolean isSuccess = controlDirFile.mkdirs();
if (!isSuccess) {
throw new TorStartupFailedException("Couldn't create Tor control directory.");
}
}
}

private Future<Path> createLogFileCreationWaiter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
package bisq.tor.process;

public class TorStartupFailedException extends RuntimeException {
public TorStartupFailedException(String message) {
super(message);
}

public TorStartupFailedException(Throwable cause) {
super(cause);
}
Expand Down

0 comments on commit 22238c9

Please sign in to comment.