diff --git a/network/tor-local-network/src/integrationTest/java/bisq/tor/local_network/PrivateTorNetworkTests.java b/network/tor-local-network/src/integrationTest/java/bisq/tor/local_network/PrivateTorNetworkTests.java new file mode 100644 index 0000000000..f4c3f35af3 --- /dev/null +++ b/network/tor-local-network/src/integrationTest/java/bisq/tor/local_network/PrivateTorNetworkTests.java @@ -0,0 +1,47 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.tor.local_network; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Path; + +public class PrivateTorNetworkTests { + private static final String PASSPHRASE = "my_passphrase"; + + @Test + public void createTestNetwork(@TempDir Path tempDir) throws IOException, InterruptedException { + new TorNetwork(tempDir) + .addDirAuth(PASSPHRASE) + .addDirAuth(PASSPHRASE) + .addDirAuth(PASSPHRASE) + + .addRelay() + .addRelay() + .addRelay() + .addRelay() + .addRelay() + + .addClient() + .addClient() + + .start(); + } +} diff --git a/network/tor-local-network/src/main/java/bisq/tor/local_network/TorNetwork.java b/network/tor-local-network/src/main/java/bisq/tor/local_network/TorNetwork.java new file mode 100644 index 0000000000..5085aac577 --- /dev/null +++ b/network/tor-local-network/src/main/java/bisq/tor/local_network/TorNetwork.java @@ -0,0 +1,171 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.tor.local_network; + +import bisq.common.util.NetworkUtils; +import bisq.tor.local_network.da.DirectoryAuthorityFactory; +import bisq.tor.local_network.torrc.ClientTorrcGenerator; +import bisq.tor.local_network.torrc.DirectoryAuthorityTorrcGenerator; +import bisq.tor.local_network.torrc.RelayTorrcGenerator; +import bisq.tor.local_network.torrc.TorrcFileGenerator; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +public class TorNetwork { + + private final Path rootDataDir; + private final DirectoryAuthorityFactory dirAuthFactory = new DirectoryAuthorityFactory(); + private final Set directoryAuthorities = new HashSet<>(); + private final Set relays = new HashSet<>(); + private final Set clients = new HashSet<>(); + + private final Set allTorProcesses = new HashSet<>(); + + private int dirAuthIndex; + private int relayIndex; + private int clientIndex; + + public TorNetwork(Path rootDataDir) { + this.rootDataDir = rootDataDir; + } + + public TorNetwork addDirAuth(String passphrase) throws IOException, InterruptedException { + String nickname = "da" + dirAuthIndex++; + + Path nodeDataDir = rootDataDir.resolve(nickname); + boolean isSuccess = nodeDataDir.toFile().mkdir(); + if (!isSuccess) { + throw new IllegalStateException("Couldn't create data directory for " + nickname); + } + + var dirAuth = TorNode.builder() + .type(TorNode.Type.DIRECTORY_AUTHORITY) + .nickname(nickname) + .dataDir(nodeDataDir) + .controlPort(NetworkUtils.findFreeSystemPort()) + .orPort(NetworkUtils.findFreeSystemPort()) + .dirPort(NetworkUtils.findFreeSystemPort()) + .build(); + dirAuthFactory.createDirectoryAuthority(dirAuth, passphrase); + directoryAuthorities.add(dirAuth); + + return this; + } + + public TorNetwork addRelay() { + String nickname = "relay" + relayIndex++; + + Path nodeDataDir = rootDataDir.resolve(nickname); + boolean isSuccess = nodeDataDir.toFile().mkdir(); + if (!isSuccess) { + throw new IllegalStateException("Couldn't create data directory for " + nickname); + } + + TorNode firstRelay = TorNode.builder() + .type(TorNode.Type.RELAY) + .nickname(nickname) + .dataDir(nodeDataDir) + + .controlPort(NetworkUtils.findFreeSystemPort()) + .orPort(NetworkUtils.findFreeSystemPort()) + .dirPort(NetworkUtils.findFreeSystemPort()) + + .build(); + relays.add(firstRelay); + return this; + } + + public TorNetwork addClient() { + String nickname = "client" + clientIndex++; + + Path nodeDataDir = rootDataDir.resolve(nickname); + boolean isSuccess = nodeDataDir.toFile().mkdir(); + if (!isSuccess) { + throw new IllegalStateException("Couldn't create data directory for " + nickname); + } + + TorNode firstClient = TorNode.builder() + .type(TorNode.Type.CLIENT) + .nickname(nickname) + .dataDir(nodeDataDir) + + .controlPort(NetworkUtils.findFreeSystemPort()) + .orPort(NetworkUtils.findFreeSystemPort()) + .dirPort(NetworkUtils.findFreeSystemPort()) + + .build(); + clients.add(firstClient); + return this; + } + + public void start() throws IOException { + generateTorrcFiles(); + startProcesses(); + } + + private void generateTorrcFiles() throws IOException { + Set allDAs = dirAuthFactory.getAllDirectoryAuthorities(); + for (TorNode da : allDAs) { + var torDaTorrcGenerator = new DirectoryAuthorityTorrcGenerator(da); + var torrcFileGenerator = new TorrcFileGenerator(torDaTorrcGenerator, allDAs); + torrcFileGenerator.generate(); + } + + for (TorNode relay : relays) { + var relayTorrcGenerator = new RelayTorrcGenerator(relay); + var torrcFileGenerator = new TorrcFileGenerator(relayTorrcGenerator, allDAs); + torrcFileGenerator.generate(); + } + + for (TorNode client : clients) { + var clientTorrcGenerator = new ClientTorrcGenerator(client); + var torrcFileGenerator = new TorrcFileGenerator(clientTorrcGenerator, allDAs); + torrcFileGenerator.generate(); + } + } + + private void startProcesses() throws IOException { + for (TorNode directoryAuthority : directoryAuthorities) { + Process process = createAndStartTorProcess(directoryAuthority); + allTorProcesses.add(process); + } + + for (TorNode relay : relays) { + Process process = createAndStartTorProcess(relay); + allTorProcesses.add(process); + } + + for (TorNode client : clients) { + Process process = createAndStartTorProcess(client); + allTorProcesses.add(process); + } + } + + private Process createAndStartTorProcess(TorNode torNode) throws IOException { + String absoluteTorrcPathAsString = torNode.getTorrcPath() + .toAbsolutePath() + .toString(); + var processBuilder = new ProcessBuilder("tor", "-f", absoluteTorrcPathAsString); + processBuilder.redirectError(ProcessBuilder.Redirect.DISCARD); + processBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD); + return processBuilder.start(); + } +}