From 7375ceefd3ff5c9445e138d86e618cbb9e16b88a Mon Sep 17 00:00:00 2001 From: boldsuck Date: Mon, 22 Jul 2024 22:16:02 +0200 Subject: [PATCH] Appply externalTorDirectBind3 --- .../java/haveno/common/config/Config.java | 9 ++ .../network/p2p/NetworkNodeProvider.java | 3 +- .../java/haveno/network/p2p/P2PModule.java | 2 + .../network/p2p/network/TorNetworkNode.java | 122 ++++++++---------- .../p2p/network/TorNetworkNodeTest.java | 8 +- 5 files changed, 74 insertions(+), 70 deletions(-) diff --git a/common/src/main/java/haveno/common/config/Config.java b/common/src/main/java/haveno/common/config/Config.java index 9147d535362..71f5436ed94 100644 --- a/common/src/main/java/haveno/common/config/Config.java +++ b/common/src/main/java/haveno/common/config/Config.java @@ -77,6 +77,7 @@ public class Config { public static final String SEED_NODES = "seedNodes"; public static final String BAN_LIST = "banList"; public static final String NODE_PORT = "nodePort"; + public static final String HIDDEN_SERVICE_ADDRESS = "hiddenServiceAddress"; public static final String USE_LOCALHOST_FOR_P2P = "useLocalhostForP2P"; public static final String MAX_CONNECTIONS = "maxConnections"; public static final String SOCKS_5_PROXY_XMR_ADDRESS = "socks5ProxyXmrAddress"; @@ -151,6 +152,7 @@ public enum UseTorForXmr { public final File appDataDir; public final int walletRpcBindPort; public final int nodePort; + public final String hiddenServiceAddress; public final int maxMemory; public final String logLevel; public final List bannedXmrNodes; @@ -286,6 +288,12 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { .ofType(Integer.class) .defaultsTo(9999); + ArgumentAcceptingOptionSpec hiddenServiceAddressOpt = + parser.accepts(HIDDEN_SERVICE_ADDRESS, "Hidden Service Address to listen on") + .withRequiredArg() + .ofType(String.class) + .defaultsTo("placeholder.onion"); + ArgumentAcceptingOptionSpec walletRpcBindPortOpt = parser.accepts(WALLET_RPC_BIND_PORT, "Port to bind the wallet RPC on") .withRequiredArg() @@ -670,6 +678,7 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { this.helpRequested = options.has(helpOpt); this.configFile = configFile; this.nodePort = options.valueOf(nodePortOpt); + this.hiddenServiceAddress = options.valueOf(hiddenServiceAddressOpt); this.walletRpcBindPort = options.valueOf(walletRpcBindPortOpt); this.maxMemory = options.valueOf(maxMemoryOpt); this.logLevel = options.valueOf(logLevelOpt); diff --git a/p2p/src/main/java/haveno/network/p2p/NetworkNodeProvider.java b/p2p/src/main/java/haveno/network/p2p/NetworkNodeProvider.java index 676db83fbd8..a0548b6d320 100644 --- a/p2p/src/main/java/haveno/network/p2p/NetworkNodeProvider.java +++ b/p2p/src/main/java/haveno/network/p2p/NetworkNodeProvider.java @@ -44,6 +44,7 @@ public NetworkNodeProvider(NetworkProtoResolver networkProtoResolver, @Named(Config.MAX_CONNECTIONS) int maxConnections, @Named(Config.USE_LOCALHOST_FOR_P2P) boolean useLocalhostForP2P, @Named(Config.NODE_PORT) int port, + @Named(Config.HIDDEN_SERVICE_ADDRESS) String hiddenServiceAddress, @Named(Config.TOR_DIR) File torDir, @Nullable @Named(Config.TORRC_FILE) File torrcFile, @Named(Config.TORRC_OPTIONS) String torrcOptions, @@ -65,7 +66,7 @@ public NetworkNodeProvider(NetworkProtoResolver networkProtoResolver, password, cookieFile, useSafeCookieAuthentication); - networkNode = new TorNetworkNode(port, networkProtoResolver, streamIsolation, torMode, banFilter, maxConnections, controlHost); + networkNode = new TorNetworkNode(hiddenServiceAddress, port, networkProtoResolver, streamIsolation, torMode, banFilter, maxConnections, controlHost); } } diff --git a/p2p/src/main/java/haveno/network/p2p/P2PModule.java b/p2p/src/main/java/haveno/network/p2p/P2PModule.java index 50ce7d56422..b13c2ccba7c 100644 --- a/p2p/src/main/java/haveno/network/p2p/P2PModule.java +++ b/p2p/src/main/java/haveno/network/p2p/P2PModule.java @@ -26,6 +26,7 @@ import static haveno.common.config.Config.BAN_LIST; import static haveno.common.config.Config.MAX_CONNECTIONS; import static haveno.common.config.Config.NODE_PORT; +import static haveno.common.config.Config.HIDDEN_SERVICE_ADDRESS; import static haveno.common.config.Config.REPUBLISH_MAILBOX_ENTRIES; import static haveno.common.config.Config.SOCKS_5_PROXY_HTTP_ADDRESS; import static haveno.common.config.Config.SOCKS_5_PROXY_XMR_ADDRESS; @@ -87,6 +88,7 @@ protected void configure() { bind(File.class).annotatedWith(named(TOR_DIR)).toInstance(config.torDir); bind(int.class).annotatedWith(named(NODE_PORT)).toInstance(config.nodePort); + bind(String.class).annotatedWith(named(HIDDEN_SERVICE_ADDRESS)).toInstance(config.hiddenServiceAddress); bindConstant().annotatedWith(named(MAX_CONNECTIONS)).to(config.maxConnections); diff --git a/p2p/src/main/java/haveno/network/p2p/network/TorNetworkNode.java b/p2p/src/main/java/haveno/network/p2p/network/TorNetworkNode.java index 58842ed1f6d..5d4b270341b 100644 --- a/p2p/src/main/java/haveno/network/p2p/network/TorNetworkNode.java +++ b/p2p/src/main/java/haveno/network/p2p/network/TorNetworkNode.java @@ -17,28 +17,23 @@ package haveno.network.p2p.network; +import haveno.common.util.Hex; import haveno.network.p2p.NodeAddress; -import haveno.network.utils.Utils; import haveno.common.Timer; import haveno.common.UserThread; import haveno.common.proto.network.NetworkProtoResolver; import haveno.common.util.SingleThreadExecutorUtils; -import org.berndpruenster.netlayer.tor.HiddenServiceSocket; -import org.berndpruenster.netlayer.tor.Tor; -import org.berndpruenster.netlayer.tor.TorCtlException; -import org.berndpruenster.netlayer.tor.TorSocket; - import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; -import java.security.SecureRandom; - import java.net.Socket; +import java.net.InetAddress; +import java.net.ServerSocket; import java.io.IOException; -import java.util.Base64; +import java.nio.charset.StandardCharsets; import java.util.concurrent.ExecutorService; import lombok.extern.slf4j.Slf4j; @@ -52,13 +47,11 @@ public class TorNetworkNode extends NetworkNode { private static final long SHUT_DOWN_TIMEOUT = 2; private final String torControlHost; + private final String serviceAddress; - private HiddenServiceSocket hiddenServiceSocket; private Timer shutDownTimeoutTimer; - private Tor tor; private TorMode torMode; private boolean streamIsolation; - private Socks5Proxy socksProxy; private boolean shutDownInProgress; private boolean shutDownComplete; private final ExecutorService executor; @@ -67,13 +60,14 @@ public class TorNetworkNode extends NetworkNode { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public TorNetworkNode(int servicePort, + public TorNetworkNode(String hiddenServiceAddress, int servicePort, NetworkProtoResolver networkProtoResolver, boolean useStreamIsolation, TorMode torMode, @Nullable BanFilter banFilter, int maxConnections, String torControlHost) { super(servicePort, networkProtoResolver, banFilter, maxConnections); + this.serviceAddress = hiddenServiceAddress; this.torMode = torMode; this.streamIsolation = useStreamIsolation; this.torControlHost = torControlHost; @@ -92,33 +86,50 @@ public void start(@Nullable SetupListener setupListener) { if (setupListener != null) addSetupListener(setupListener); - createTorAndHiddenService(Utils.findFreeSystemPort(), servicePort); + createTorAndHiddenService(servicePort); } @Override protected Socket createSocket(NodeAddress peerNodeAddress) throws IOException { - checkArgument(peerNodeAddress.getHostName().endsWith(".onion"), "PeerAddress is not an onion address"); - // If streamId is null stream isolation gets deactivated. - // Hidden services use stream isolation by default, so we pass null. - return new TorSocket(peerNodeAddress.getHostName(), peerNodeAddress.getPort(), torControlHost, null); + // https://www.ietf.org/rfc1928.txt SOCKS5 Protocol + try { + checkArgument(peerNodeAddress.getHostName().endsWith(".onion"), "PeerAddress is not an onion address"); + Socket sock = new Socket(InetAddress.getLoopbackAddress(), 9050); + sock.getOutputStream().write(Hex.decode("050100")); + String response = Hex.encode(sock.getInputStream().readNBytes(2)); + if (!response.equalsIgnoreCase("0500")) { + return null; + } + String connect_details = "050100033E" + Hex.encode(peerNodeAddress.getHostName().getBytes(StandardCharsets.UTF_8)); + StringBuilder connect_port = new StringBuilder(Integer.toHexString(peerNodeAddress.getPort())); + while (connect_port.length() < 4) connect_port.insert(0, "0"); + connect_details = connect_details + connect_port; + sock.getOutputStream().write(Hex.decode(connect_details)); + response = Hex.encode(sock.getInputStream().readNBytes(10)); + if (response.substring(0, 2).equalsIgnoreCase("05") && response.substring(2, 4).equalsIgnoreCase("00")) { + return sock; // success + } + if (response.substring(2, 4).equalsIgnoreCase("04")) { + log.warn("Host unreachable: {}", peerNodeAddress); + } else { + log.warn("SOCKS error code received {} expected 00", response.substring(2, 4)); + } + if (!response.substring(0, 2).equalsIgnoreCase("05")) { + log.warn("unexpected response, this isn't a SOCKS5 proxy?: {} {}", response, response.substring(0, 2)); + } + } catch (Exception e) { + log.warn(e.toString()); + } + throw new IOException("createSocket failed"); } public Socks5Proxy getSocksProxy() { try { - String stream = null; - if (streamIsolation) { - byte[] bytes = new byte[512]; // tor.getProxy creates a Sha256 hash - new SecureRandom().nextBytes(bytes); - stream = Base64.getEncoder().encodeToString(bytes); - } - - if (socksProxy == null || streamIsolation) { - tor = Tor.getDefault(); - socksProxy = tor != null ? tor.getProxy(torControlHost, stream) : null; - } - return socksProxy; - } catch (Throwable t) { - log.error("Error at getSocksProxy", t); + Socks5Proxy prox = new Socks5Proxy(InetAddress.getLoopbackAddress(), 9050); + prox.resolveAddrLocally(false); + return prox; + } catch (Exception e) { + log.warn(e.toString()); return null; } } @@ -145,12 +156,6 @@ public void shutDown(@Nullable Runnable shutDownCompleteHandler) { super.shutDown(() -> { try { - tor = Tor.getDefault(); - if (tor != null) { - tor.shutdown(); - tor = null; - log.info("Tor shutdown completed"); - } executor.shutdownNow(); } catch (Throwable e) { log.error("Shutdown torNetworkNode failed with exception", e); @@ -166,36 +171,23 @@ public void shutDown(@Nullable Runnable shutDownCompleteHandler) { // Create tor and hidden service /////////////////////////////////////////////////////////////////////////////////////////// - private void createTorAndHiddenService(int localPort, int servicePort) { + private void createTorAndHiddenService(int servicePort) { executor.submit(() -> { try { - Tor.setDefault(torMode.getTor()); - long ts = System.currentTimeMillis(); - hiddenServiceSocket = new HiddenServiceSocket(localPort, torMode.getHiddenServiceDirectory(), servicePort); - nodeAddressProperty.set(new NodeAddress(hiddenServiceSocket.getServiceName() + ":" + hiddenServiceSocket.getHiddenServicePort())); + // listener for incoming messages at the hidden service + ServerSocket socket = new ServerSocket(servicePort); + nodeAddressProperty.set(new NodeAddress(serviceAddress + ":" + servicePort)); + log.info("\n################################################################\n" + + "Tor hidden service published: {} Port: {}\n" + + "################################################################", + serviceAddress, servicePort); UserThread.execute(() -> setupListeners.forEach(SetupListener::onTorNodeReady)); - hiddenServiceSocket.addReadyListener(socket -> { - log.info("\n################################################################\n" + - "Tor hidden service published after {} ms. Socket={}\n" + - "################################################################", - System.currentTimeMillis() - ts, socket); - UserThread.execute(() -> { - nodeAddressProperty.set(new NodeAddress(hiddenServiceSocket.getServiceName() + ":" - + hiddenServiceSocket.getHiddenServicePort())); - startServer(socket); - setupListeners.forEach(SetupListener::onHiddenServicePublished); - }); - return null; - }); - } catch (TorCtlException e) { - log.error("Starting tor node failed", e); - if (e.getCause() instanceof IOException) { - UserThread.execute(() -> setupListeners.forEach(s -> s.onSetupFailed(new RuntimeException(e.getMessage())))); - } else { - UserThread.execute(() -> setupListeners.forEach(SetupListener::onRequestCustomBridges)); - log.warn("We shutdown as starting tor with the default bridges failed. We request user to add custom bridges."); - shutDown(null); - } + UserThread.runAfter(() -> { + nodeAddressProperty.set(new NodeAddress(serviceAddress + ":" + servicePort)); + startServer(socket); + setupListeners.forEach(SetupListener::onHiddenServicePublished); + }, 3); + return null; } catch (IOException e) { log.error("Could not connect to running Tor", e); UserThread.execute(() -> setupListeners.forEach(s -> s.onSetupFailed(new RuntimeException(e.getMessage())))); diff --git a/p2p/src/test/java/haveno/network/p2p/network/TorNetworkNodeTest.java b/p2p/src/test/java/haveno/network/p2p/network/TorNetworkNodeTest.java index f74a9d341a6..fda93464662 100644 --- a/p2p/src/test/java/haveno/network/p2p/network/TorNetworkNodeTest.java +++ b/p2p/src/test/java/haveno/network/p2p/network/TorNetworkNodeTest.java @@ -50,7 +50,7 @@ public class TorNetworkNodeTest { public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException { latch = new CountDownLatch(1); int port = 9001; - TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(), false, + TorNetworkNode node1 = new TorNetworkNode("serviceAddress", port, TestUtils.getNetworkProtoResolver(), false, new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); node1.start(new SetupListener() { @Override @@ -77,7 +77,7 @@ public void onRequestCustomBridges() { latch = new CountDownLatch(1); int port2 = 9002; - TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(), false, + TorNetworkNode node2 = new TorNetworkNode("serviceAddress", port2, TestUtils.getNetworkProtoResolver(), false, new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); node2.start(new SetupListener() { @Override @@ -135,7 +135,7 @@ public void onFailure(@NotNull Throwable throwable) { public void testTorNodeAfterBothReady() throws InterruptedException, IOException { latch = new CountDownLatch(2); int port = 9001; - TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(), false, + TorNetworkNode node1 = new TorNetworkNode("serviceAddress", port, TestUtils.getNetworkProtoResolver(), false, new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); node1.start(new SetupListener() { @Override @@ -161,7 +161,7 @@ public void onRequestCustomBridges() { }); int port2 = 9002; - TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(), false, + TorNetworkNode node2 = new TorNetworkNode("serviceAddress", port2, TestUtils.getNetworkProtoResolver(), false, new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); node2.start(new SetupListener() { @Override