From 05dad1331050c6840cdca1f46a83088f0b0c14dc Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Tue, 11 Jun 2024 00:53:55 +0000 Subject: [PATCH 1/6] TorControlProtocol: Wrap IOException into CannotSendCommandToTorException --- .../tor/controller/TorControlProtocol.java | 29 ++++---- .../bisq/tor/controller/TorController.java | 67 ++++++++----------- .../CannotSendCommandToTorException.java | 7 ++ 3 files changed, 52 insertions(+), 51 deletions(-) create mode 100644 network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java index 5b33d0011c..3e2140784e 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java @@ -4,6 +4,7 @@ import bisq.security.keys.TorKeyPair; import bisq.tor.controller.events.listener.BootstrapEventListener; import bisq.tor.controller.events.listener.HsDescEventListener; +import bisq.tor.controller.exceptions.CannotSendCommandToTorException; import net.freehaven.tor.control.PasswordDigest; import java.io.IOException; @@ -39,7 +40,7 @@ public void initialize() { whonixTorControlReader.start(); } - public void authenticate(PasswordDigest passwordDigest) throws IOException { + public void authenticate(PasswordDigest passwordDigest) { byte[] secret = passwordDigest.getSecret(); String secretHex = Hex.encode(secret); String command = "AUTHENTICATE " + secretHex + "\r\n"; @@ -56,7 +57,7 @@ public void authenticate(PasswordDigest passwordDigest) throws IOException { } } - public void addOnion(TorKeyPair torKeyPair, int onionPort, int localPort) throws IOException { + public void addOnion(TorKeyPair torKeyPair, int onionPort, int localPort) { String base64SecretScalar = torKeyPair.getBase64SecretScalar(); String command = "ADD_ONION " + "ED25519-V3:" + base64SecretScalar + " Port=" + onionPort + "," + localPort + "\r\n"; @@ -65,14 +66,14 @@ public void addOnion(TorKeyPair torKeyPair, int onionPort, int localPort) throws assertTwoLineOkReply(replyStream, "ADD_ONION"); } - public String getInfo(String keyword) throws IOException { + public String getInfo(String keyword) { String command = "GETINFO " + keyword + "\r\n"; sendCommand(command); Stream replyStream = receiveReply(); return assertTwoLineOkReply(replyStream, "GETINFO"); } - public void hsFetch(String hsAddress) throws IOException { + public void hsFetch(String hsAddress) { String command = "HSFETCH " + hsAddress + "\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -81,7 +82,7 @@ public void hsFetch(String hsAddress) throws IOException { } } - public void resetConf(String configName) throws IOException { + public void resetConf(String configName) { String command = "RESETCONF " + configName + "\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -90,7 +91,7 @@ public void resetConf(String configName) throws IOException { } } - public void setConfig(String configName, String configValue) throws IOException { + public void setConfig(String configName, String configValue) { String command = "SETCONF " + configName + "=" + configValue + "\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -99,7 +100,7 @@ public void setConfig(String configName, String configValue) throws IOException } } - public void setEvents(List events) throws IOException { + public void setEvents(List events) { var stringBuilder = new StringBuffer("SETEVENTS"); events.forEach(event -> stringBuilder.append(" ").append(event)); stringBuilder.append("\r\n"); @@ -112,7 +113,7 @@ public void setEvents(List events) throws IOException { } } - public void takeOwnership() throws IOException { + public void takeOwnership() { String command = "TAKEOWNERSHIP\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -137,10 +138,14 @@ public void removeHsDescEventListener(HsDescEventListener listener) { whonixTorControlReader.removeHsDescEventListener(listener); } - private void sendCommand(String command) throws IOException { - byte[] commandBytes = command.getBytes(StandardCharsets.US_ASCII); - outputStream.write(commandBytes); - outputStream.flush(); + private void sendCommand(String command) { + try { + byte[] commandBytes = command.getBytes(StandardCharsets.US_ASCII); + outputStream.write(commandBytes); + outputStream.flush(); + } catch (IOException e) { + throw new CannotSendCommandToTorException(e); + } } private Stream receiveReply() { diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java index 9c6612fd10..d9c8752bd6 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java @@ -22,7 +22,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @Slf4j public class TorController implements BootstrapEventListener, HsDescEventListener { @@ -61,14 +64,14 @@ public void shutdown() { }); } - public void bootstrapTor() throws IOException { + public void bootstrapTor() { bindToBisq(); subscribeToBootstrapEvents(); enableNetworking(); waitUntilBootstrapped(); } - public CompletableFuture isOnionServiceOnline(String onionAddress) throws IOException, ExecutionException, InterruptedException, TimeoutException { + public CompletableFuture isOnionServiceOnline(String onionAddress) { var onionServiceLookupCompletableFuture = new CompletableFuture(); pendingIsOnionServiceOnlineLookupFutureMap.put(onionAddress, onionServiceLookupCompletableFuture); subscribeToHsDescEvents(); @@ -79,17 +82,13 @@ public CompletableFuture isOnionServiceOnline(String onionAddress) thro onionServiceLookupCompletableFuture.thenRun(() -> { torControlProtocol.removeHsDescEventListener(this); - try { - torControlProtocol.setEvents(Collections.emptyList()); - } catch (IOException e) { - throw new RuntimeException(e); - } + torControlProtocol.setEvents(Collections.emptyList()); }); return onionServiceLookupCompletableFuture; } - public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) throws IOException, InterruptedException { + public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) throws InterruptedException { String onionAddress = torKeyPair.getOnionAddress(); var onionServicePublishedLatch = new CountDownLatch(1); pendingOnionServicePublishLatchMap.put(onionAddress, onionServicePublishedLatch); @@ -107,28 +106,22 @@ public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) torControlProtocol.setEvents(Collections.emptyList()); } - public Optional getSocksPort() { - try { - TorControlProtocol torControlProtocol = getTorControlProtocol(); - String socksListenersString = torControlProtocol.getInfo("net/listeners/socks"); - - String socksListener; - if (socksListenersString.contains(" ")) { - String[] socksPorts = socksListenersString.split(" "); - socksListener = socksPorts[0]; - } else { - socksListener = socksListenersString; - } - - // "127.0.0.1:12345" - socksListener = socksListener.replace("\"", ""); - String portString = socksListener.split(":")[1]; - - int port = Integer.parseInt(portString); - return Optional.of(port); - } catch (IOException e) { - return Optional.empty(); + public int getSocksPort() { + TorControlProtocol torControlProtocol = getTorControlProtocol(); + String socksListenersString = torControlProtocol.getInfo("net/listeners/socks"); + + String socksListener; + if (socksListenersString.contains(" ")) { + String[] socksPorts = socksListenersString.split(" "); + socksListener = socksPorts[0]; + } else { + socksListener = socksListenersString; } + + // "127.0.0.1:12345" + socksListener = socksListener.replace("\"", ""); + String portString = socksListener.split(":")[1]; + return Integer.parseInt(portString); } @Override @@ -179,30 +172,28 @@ private void initialize(int controlPort, Optional hashedControlP this.torControlProtocol = Optional.of(torControlProtocol); torControlProtocol.initialize(); - if (hashedControlPassword.isPresent()) { - torControlProtocol.authenticate(hashedControlPassword.get()); - } + hashedControlPassword.ifPresent(torControlProtocol::authenticate); } - private void bindToBisq() throws IOException { + private void bindToBisq() { TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.takeOwnership(); torControlProtocol.resetConf(NativeTorProcess.ARG_OWNER_PID); } - private void subscribeToBootstrapEvents() throws IOException { + private void subscribeToBootstrapEvents() { TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.addBootstrapEventListener(this); torControlProtocol.setEvents(List.of("STATUS_CLIENT")); } - private void subscribeToHsDescEvents() throws IOException { + private void subscribeToHsDescEvents() { TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.addHsDescEventListener(this); torControlProtocol.setEvents(List.of("HS_DESC")); } - private void enableNetworking() throws IOException { + private void enableNetworking() { TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.setConfig(TorrcClientConfigFactory.DISABLE_NETWORK_CONFIG_KEY, "0"); } @@ -227,8 +218,6 @@ private void waitUntilBootstrapped() { } } catch (InterruptedException e) { throw new TorBootstrapFailedException(e); - } catch (IOException e) { - throw new RuntimeException(e); } } diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java new file mode 100644 index 0000000000..42b26f7d92 --- /dev/null +++ b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java @@ -0,0 +1,7 @@ +package bisq.tor.controller.exceptions; + +public class CannotSendCommandToTorException extends RuntimeException { + public CannotSendCommandToTorException(Throwable cause) { + super(cause); + } +} From d8db1fedea63d6f44ce38546dced3c4358afe62a Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Tue, 11 Jun 2024 00:55:07 +0000 Subject: [PATCH 2/6] TorControlProtocol: Move init code from constructor to initialize method --- .../tor/controller/TorControlProtocol.java | 28 +++++++++++++------ .../bisq/tor/controller/TorController.java | 10 +++---- .../controller/WhonixTorControlReader.java | 9 ++---- .../CannotConnectWithTorException.java | 7 +++++ 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java index 3e2140784e..771893233d 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java @@ -4,14 +4,17 @@ import bisq.security.keys.TorKeyPair; import bisq.tor.controller.events.listener.BootstrapEventListener; import bisq.tor.controller.events.listener.HsDescEventListener; +import bisq.tor.controller.exceptions.CannotConnectWithTorException; import bisq.tor.controller.exceptions.CannotSendCommandToTorException; import net.freehaven.tor.control.PasswordDigest; import java.io.IOException; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -19,16 +22,26 @@ public class TorControlProtocol implements AutoCloseable { private final Socket controlSocket; private final WhonixTorControlReader whonixTorControlReader; - private final OutputStream outputStream; + private Optional outputStream = Optional.empty(); // MidReplyLine = StatusCode "-" ReplyLine // DataReplyLine = StatusCode "+" ReplyLine CmdData private final Pattern multiLineReplyPattern = Pattern.compile("^\\d+[-+].+"); - public TorControlProtocol(int port) throws IOException { - controlSocket = new Socket("127.0.0.1", port); - whonixTorControlReader = new WhonixTorControlReader(controlSocket.getInputStream()); - outputStream = controlSocket.getOutputStream(); + public TorControlProtocol() { + controlSocket = new Socket(); + whonixTorControlReader = new WhonixTorControlReader(); + } + + public void initialize(int port) { + try { + var socketAddress = new InetSocketAddress("127.0.0.1", port); + controlSocket.connect(socketAddress); + whonixTorControlReader.start(controlSocket.getInputStream()); + outputStream = Optional.of(controlSocket.getOutputStream()); + } catch (IOException e) { + throw new CannotConnectWithTorException(e); + } } @Override @@ -36,10 +49,6 @@ public void close() throws IOException { controlSocket.close(); } - public void initialize() { - whonixTorControlReader.start(); - } - public void authenticate(PasswordDigest passwordDigest) { byte[] secret = passwordDigest.getSecret(); String secretHex = Hex.encode(secret); @@ -140,6 +149,7 @@ public void removeHsDescEventListener(HsDescEventListener listener) { private void sendCommand(String command) { try { + @SuppressWarnings("resource") OutputStream outputStream = this.outputStream.orElseThrow(); byte[] commandBytes = command.getBytes(StandardCharsets.US_ASCII); outputStream.write(commandBytes); outputStream.flush(); diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java index d9c8752bd6..3719abff87 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java @@ -46,11 +46,11 @@ public TorController(int bootstrapTimeout, int hsUploadTimeout) { this.hsUploadTimeout = hsUploadTimeout; } - public void initialize(int controlPort) throws IOException { + public void initialize(int controlPort) { initialize(controlPort, Optional.empty()); } - public void initialize(int controlPort, PasswordDigest hashedControlPassword) throws IOException { + public void initialize(int controlPort, PasswordDigest hashedControlPassword) { initialize(controlPort, Optional.of(hashedControlPassword)); } @@ -167,11 +167,11 @@ public void onHsDescEvent(HsDescEvent hsDescEvent) { } } - private void initialize(int controlPort, Optional hashedControlPassword) throws IOException { - var torControlProtocol = new TorControlProtocol(controlPort); + private void initialize(int controlPort, Optional hashedControlPassword) { + var torControlProtocol = new TorControlProtocol(); this.torControlProtocol = Optional.of(torControlProtocol); - torControlProtocol.initialize(); + torControlProtocol.initialize(controlPort); hashedControlPassword.ifPresent(torControlProtocol::authenticate); } diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java b/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java index 8899ff6747..b6580dd33b 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java @@ -19,20 +19,17 @@ @Slf4j public class WhonixTorControlReader implements AutoCloseable { - private final BufferedReader bufferedReader; private final BlockingQueue replies = new LinkedBlockingQueue<>(); private final List bootstrapEventListeners = new CopyOnWriteArrayList<>(); private final List hsDescEventListeners = new CopyOnWriteArrayList<>(); private Optional workerThread = Optional.empty(); - public WhonixTorControlReader(InputStream inputStream) { - bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.US_ASCII)); - } - - public void start() { + public void start(InputStream inputStream) { Thread thread = new Thread(() -> { try { + var bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.US_ASCII)); + String line; while ((line = bufferedReader.readLine()) != null) { diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java new file mode 100644 index 0000000000..dea197dab9 --- /dev/null +++ b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java @@ -0,0 +1,7 @@ +package bisq.tor.controller.exceptions; + +public class CannotConnectWithTorException extends RuntimeException { + public CannotConnectWithTorException(Throwable cause) { + super(cause); + } +} From c390bef9b3ca615b8334610c67593864ad164869 Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Tue, 11 Jun 2024 00:56:01 +0000 Subject: [PATCH 3/6] TorController: Move shutdown exception handling to TorControlProtocol --- .../main/java/bisq/tor/controller/TorControlProtocol.java | 8 ++++++-- .../src/main/java/bisq/tor/controller/TorController.java | 8 +------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java index 771893233d..b4911b9084 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java @@ -45,8 +45,12 @@ public void initialize(int port) { } @Override - public void close() throws IOException { - controlSocket.close(); + public void close() { + try { + controlSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } } public void authenticate(PasswordDigest passwordDigest) { diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java index 3719abff87..0d1d8bfa95 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java @@ -55,13 +55,7 @@ public void initialize(int controlPort, PasswordDigest hashedControlPassword) { } public void shutdown() { - torControlProtocol.ifPresent(torControlProtocol -> { - try { - torControlProtocol.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + torControlProtocol.ifPresent(TorControlProtocol::close); } public void bootstrapTor() { From 51a8fcde3d43120e9ed08d4e4d4ce5ae9b712118 Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Tue, 11 Jun 2024 00:57:00 +0000 Subject: [PATCH 4/6] TorController: Make TorControlProtocol field final --- .../bisq/tor/controller/TorController.java | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java index 0d1d8bfa95..df78fa1a23 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java @@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j; import net.freehaven.tor.control.PasswordDigest; -import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; @@ -29,6 +28,8 @@ @Slf4j public class TorController implements BootstrapEventListener, HsDescEventListener { + private final TorControlProtocol torControlProtocol = new TorControlProtocol(); + private final int bootstrapTimeout; // in ms private final int hsUploadTimeout; // in ms private final CountDownLatch isBootstrappedCountdownLatch = new CountDownLatch(1); @@ -39,8 +40,6 @@ public class TorController implements BootstrapEventListener, HsDescEventListene private final Map> pendingIsOnionServiceOnlineLookupFutureMap = new ConcurrentHashMap<>(); - private Optional torControlProtocol = Optional.empty(); - public TorController(int bootstrapTimeout, int hsUploadTimeout) { this.bootstrapTimeout = bootstrapTimeout; this.hsUploadTimeout = hsUploadTimeout; @@ -55,7 +54,7 @@ public void initialize(int controlPort, PasswordDigest hashedControlPassword) { } public void shutdown() { - torControlProtocol.ifPresent(TorControlProtocol::close); + torControlProtocol.close(); } public void bootstrapTor() { @@ -70,7 +69,6 @@ public CompletableFuture isOnionServiceOnline(String onionAddress) { pendingIsOnionServiceOnlineLookupFutureMap.put(onionAddress, onionServiceLookupCompletableFuture); subscribeToHsDescEvents(); - TorControlProtocol torControlProtocol = getTorControlProtocol(); String serviceId = onionAddress.replace(".onion", ""); torControlProtocol.hsFetch(serviceId); @@ -88,7 +86,6 @@ public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) pendingOnionServicePublishLatchMap.put(onionAddress, onionServicePublishedLatch); subscribeToHsDescEvents(); - TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.addOnion(torKeyPair, onionServicePort, localPort); boolean isSuccess = onionServicePublishedLatch.await(hsUploadTimeout, TimeUnit.MILLISECONDS); @@ -101,9 +98,7 @@ public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) } public int getSocksPort() { - TorControlProtocol torControlProtocol = getTorControlProtocol(); String socksListenersString = torControlProtocol.getInfo("net/listeners/socks"); - String socksListener; if (socksListenersString.contains(" ")) { String[] socksPorts = socksListenersString.split(" "); @@ -162,47 +157,34 @@ public void onHsDescEvent(HsDescEvent hsDescEvent) { } private void initialize(int controlPort, Optional hashedControlPassword) { - var torControlProtocol = new TorControlProtocol(); - this.torControlProtocol = Optional.of(torControlProtocol); - torControlProtocol.initialize(controlPort); hashedControlPassword.ifPresent(torControlProtocol::authenticate); } private void bindToBisq() { - TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.takeOwnership(); torControlProtocol.resetConf(NativeTorProcess.ARG_OWNER_PID); } private void subscribeToBootstrapEvents() { - TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.addBootstrapEventListener(this); torControlProtocol.setEvents(List.of("STATUS_CLIENT")); } private void subscribeToHsDescEvents() { - TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.addHsDescEventListener(this); torControlProtocol.setEvents(List.of("HS_DESC")); } private void enableNetworking() { - TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.setConfig(TorrcClientConfigFactory.DISABLE_NETWORK_CONFIG_KEY, "0"); } private void waitUntilBootstrapped() { try { while (true) { - if (torControlProtocol.isEmpty()) { - throw new TorBootstrapFailedException("Tor is not initializing."); - } - boolean isSuccess = isBootstrappedCountdownLatch.await(bootstrapTimeout, TimeUnit.MILLISECONDS); - if (isSuccess) { - TorControlProtocol torControlProtocol = this.torControlProtocol.get(); torControlProtocol.removeBootstrapEventListener(this); torControlProtocol.setEvents(Collections.emptyList()); break; @@ -221,8 +203,4 @@ private boolean isBootstrapTimeoutTriggered() { Instant bootstrapTimeoutAgo = Instant.now().minus(bootstrapTimeout, ChronoUnit.MILLIS); return bootstrapTimeoutAgo.isAfter(timestamp); } - - private TorControlProtocol getTorControlProtocol() { - return this.torControlProtocol.orElseThrow(); - } } From f148cd9977e032fa852f7c1a614741fc42663cbd Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Tue, 11 Jun 2024 00:58:05 +0000 Subject: [PATCH 5/6] TorService: Migrate to new TorController Ref: #1894 --- .../node/transport/TorTransportService.java | 14 ++-- .../src/main/java/bisq/tor/TorService.java | 68 ++++++++++--------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java b/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java index bc54d3b944..0ac6ebf04a 100644 --- a/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java +++ b/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java @@ -7,15 +7,16 @@ import bisq.network.identity.NetworkId; import bisq.network.p2p.node.ConnectionException; import bisq.security.keys.KeyBundle; +import bisq.security.keys.TorKeyPair; import bisq.tor.TorService; import bisq.tor.TorTransportConfig; -import bisq.tor.onionservice.CreateOnionServiceResponse; import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.net.Socket; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -81,14 +82,19 @@ public ServerSocketResult getServerSocket(NetworkId networkId, KeyBundle keyBund bootstrapInfo.getBootstrapProgress().set(0.25); bootstrapInfo.getBootstrapDetails().set("Create Onion service for node ID '" + networkId + "'"); - CreateOnionServiceResponse response = torService.createOnionService(port, keyBundle.getTorKeyPair()) + TorKeyPair torKeyPair = keyBundle.getTorKeyPair(); + ServerSocket serverSocket = torService.createOnionService(port, torKeyPair) .get(2, TimeUnit.MINUTES); bootstrapInfo.getBootstrapState().set(BootstrapState.SERVICE_PUBLISHED); bootstrapInfo.getBootstrapProgress().set(0.5); - bootstrapInfo.getBootstrapDetails().set("My Onion service address: " + response.getOnionAddress().toString()); - return new ServerSocketResult(response); + String onionAddress = torKeyPair.getOnionAddress(); + bootstrapInfo.getBootstrapDetails().set("My Onion service address: " + onionAddress); + + Address address = new Address(onionAddress); + return new ServerSocketResult(serverSocket, address); + } catch (InterruptedException | ExecutionException | TimeoutException e) { e.printStackTrace(); throw new ConnectionException(e); diff --git a/network/tor/tor/src/main/java/bisq/tor/TorService.java b/network/tor/tor/src/main/java/bisq/tor/TorService.java index 0ddc6895db..5f090326c9 100644 --- a/network/tor/tor/src/main/java/bisq/tor/TorService.java +++ b/network/tor/tor/src/main/java/bisq/tor/TorService.java @@ -23,11 +23,9 @@ import bisq.network.tor.common.torrc.BaseTorrcGenerator; import bisq.network.tor.common.torrc.TorrcFileGenerator; import bisq.security.keys.TorKeyPair; -import bisq.tor.controller.NativeTorController; +import bisq.tor.controller.TorController; import bisq.tor.controller.events.events.BootstrapEvent; import bisq.tor.installer.TorInstaller; -import bisq.tor.onionservice.CreateOnionServiceResponse; -import bisq.tor.onionservice.OnionServicePublishService; import bisq.tor.process.NativeTorProcess; import bisq.tor.process.control_port.ControlPortFilePoller; import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; @@ -40,7 +38,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; +import java.util.Set; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @@ -49,8 +48,8 @@ public class TorService implements Service { private final TorTransportConfig transportConfig; private final Path torDataDirPath; - private final NativeTorController nativeTorController; - private final OnionServicePublishService onionServicePublishService; + private final TorController torController; + private final Set publishedOnionServices = new CopyOnWriteArraySet<>(); private final AtomicBoolean isRunning = new AtomicBoolean(); @@ -60,9 +59,7 @@ public class TorService implements Service { public TorService(TorTransportConfig transportConfig) { this.transportConfig = transportConfig; this.torDataDirPath = transportConfig.getDataDir(); - nativeTorController = new NativeTorController(transportConfig.getBootstrapTimeout(), - transportConfig.getHsUploadTimeout()); - this.onionServicePublishService = new OnionServicePublishService(nativeTorController); + torController = new TorController(transportConfig.getBootstrapTimeout(), transportConfig.getHsUploadTimeout()); } @Override @@ -89,19 +86,16 @@ public CompletableFuture initialize() { return new ControlPortFilePoller(controlPortFilePath) .parsePort() .thenAccept(controlPort -> { - nativeTorController.connect(controlPort, Optional.of(hashedControlPassword)); - nativeTorController.bindTorToConnection(); + torController.initialize(controlPort, hashedControlPassword); + torController.bootstrapTor(); - nativeTorController.enableTorNetworking(); - nativeTorController.waitUntilBootstrapped(); - - int port = nativeTorController.getSocksPort().orElseThrow(); + int port = torController.getSocksPort(); torSocksProxyFactory = Optional.of(new TorSocksProxyFactory(port)); }) .thenApply(unused -> true); } else { return CompletableFuture.supplyAsync(() -> { - nativeTorController.connect(9051, Optional.empty()); + torController.initialize(9051); torSocksProxyFactory = Optional.of(new TorSocksProxyFactory(9050)); return true; }); @@ -112,38 +106,46 @@ public CompletableFuture initialize() { public CompletableFuture shutdown() { log.info("shutdown"); return CompletableFuture.supplyAsync(() -> { - nativeTorController.shutdown(); + torController.shutdown(); torProcess.ifPresent(NativeTorProcess::waitUntilExited); return true; }); } - public Observable getBootstrapEvent() { - return nativeTorController.getBootstrapEvent(); - } - - public CompletableFuture createOnionService(int port, TorKeyPair torKeyPair) { + public CompletableFuture createOnionService(int port, TorKeyPair torKeyPair) { log.info("Start hidden service with port {}", port); long ts = System.currentTimeMillis(); try { - @SuppressWarnings("resource") ServerSocket localServerSocket = new ServerSocket(RANDOM_PORT); + var localServerSocket = new ServerSocket(RANDOM_PORT); int localPort = localServerSocket.getLocalPort(); - return onionServicePublishService.publish(torKeyPair, port, localPort) - .thenApply(onionAddress -> { - log.info("Tor hidden service Ready. Took {} ms. Onion address={}", - System.currentTimeMillis() - ts, onionAddress); - return new CreateOnionServiceResponse(localServerSocket, onionAddress); - } - ); - - } catch (IOException e) { + + String onionAddress = torKeyPair.getOnionAddress(); + if (!publishedOnionServices.contains(onionAddress)) { + torController.publish(torKeyPair, port, localPort); + publishedOnionServices.add(onionAddress); + } + + log.info("Tor hidden service Ready. Took {} ms. Onion address={}", + System.currentTimeMillis() - ts, onionAddress); + + return CompletableFuture.completedFuture(localServerSocket); + + } catch (IOException | InterruptedException e) { log.error("Can't create onion service", e); return CompletableFuture.failedFuture(e); } } public boolean isOnionServiceOnline(String onionUrl) { - return nativeTorController.isHiddenServiceAvailable(onionUrl); + try { + return torController.isOnionServiceOnline(onionUrl).get(1, TimeUnit.MINUTES); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new RuntimeException(e); + } + } + + public Observable getBootstrapEvent() { + return torController.getBootstrapEvent(); } public Socket getSocket(String streamId) throws IOException { From b120b1d80e3857303c0c266a60c3d9f5451942ea Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Tue, 11 Jun 2024 00:59:10 +0000 Subject: [PATCH 6/6] Remove deprecated OnionServicePublishService --- .../OnionServicePublishService.java | 80 ------------------- 1 file changed, 80 deletions(-) delete mode 100644 network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java diff --git a/network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java b/network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java deleted file mode 100644 index 0d52b0e46a..0000000000 --- a/network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.onionservice; - -import bisq.security.keys.TorKeyPair; -import bisq.tor.controller.NativeTorController; -import lombok.extern.slf4j.Slf4j; -import net.freehaven.tor.control.TorControlConnection; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -@Slf4j -public class OnionServicePublishService { - private final NativeTorController nativeTorController; - private final Map> onionAddressMap = new HashMap<>(); - - public OnionServicePublishService(NativeTorController nativeTorController) { - this.nativeTorController = nativeTorController; - } - - public synchronized CompletableFuture publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) { - String onionAddressString = torKeyPair.getOnionAddress(); - if (onionAddressMap.containsKey(onionAddressString)) { - return onionAddressMap.get(onionAddressString); - } - - CompletableFuture completableFuture = new CompletableFuture<>(); - onionAddressMap.put(onionAddressString, completableFuture); - - try { - TorControlConnection.CreateHiddenServiceResult jTorResult = - nativeTorController.createHiddenService(onionServicePort, localPort, torKeyPair); - - var onionAddress = new OnionAddress(jTorResult.serviceID + ".onion", onionServicePort); - completableFuture.complete(onionAddress); - - } catch (IOException e) { - log.error("Couldn't create hidden service"); - completableFuture.completeExceptionally(e); - } - - return completableFuture; - } - - public synchronized Optional findOnionAddress(String onionAddressString) { - try { - CompletableFuture completableFuture = onionAddressMap.get(onionAddressString); - if (completableFuture == null) { - return Optional.empty(); - } - - OnionAddress onionAddress = completableFuture.get(); - return Optional.of(onionAddress); - - } catch (ExecutionException | InterruptedException e) { - throw new RuntimeException(e); - } - } -}