Skip to content

Commit

Permalink
Apply externalTorDirectBind3.patch & update seednode config
Browse files Browse the repository at this point in the history
  • Loading branch information
boldsuck committed Jul 21, 2024
1 parent b61f1fa commit a970520
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 89 deletions.
9 changes: 9 additions & 0 deletions common/src/main/java/haveno/common/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<String> bannedXmrNodes;
Expand Down Expand Up @@ -286,6 +288,12 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) {
.ofType(Integer.class)
.defaultsTo(9999);

ArgumentAcceptingOptionSpec<String> hiddenServiceAddressOpt =
parser.accepts(HIDDEN_SERVICE_ADDRESS, "Hidden Service Address to listen on")
.withRequiredArg()
.ofType(String.class)
.defaultsTo("placeholder.onion");

ArgumentAcceptingOptionSpec<Integer> walletRpcBindPortOpt =
parser.accepts(WALLET_RPC_BIND_PORT, "Port to bind the wallet RPC on")
.withRequiredArg()
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}
}

Expand Down
2 changes: 2 additions & 0 deletions p2p/src/main/java/haveno/network/p2p/P2PModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
122 changes: 57 additions & 65 deletions p2p/src/main/java/haveno/network/p2p/network/TorNetworkNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -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);
Expand All @@ -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()))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
18 changes: 13 additions & 5 deletions seednode/haveno-seednode.service
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ User=haveno
Group=haveno
SyslogIdentifier=Haveno-Seednode

# TODO: Insert .onion address with Tor setup script
# HIDDEN_SERVICE="$(sudo cat /var/lib/tor/haveno-seednode/hostname)"
# --hiddenServiceAddress=${HIDDEN_SERVICE}\

# Uncomment & insert --hiddenServiceAddress= 'sudo cat /var/lib/tor/haveno-seednode/hostname'
# $PATH is a placeholder
ExecStart=/bin/sh $PATH/haveno-seednode --baseCurrencyNetwork=XMR_STAGENET\
ExecStart=/bin/sh $PATH/haveno-seednode --baseCurrencyNetwork=XMR_MAINNET\
--useLocalhostForP2P=false\
--useDevPrivilegeKeys=false\
--nodePort=2002\
--appName=haveno-XMR_STAGENET_Seed_2002
--xmrNode=[::1]:38088
# --hiddenServiceAddress=PLACEHOLDER.onion\
--nodePort=9992\
--appName=haveno_seednode
--xmrNode=http://127.0.0.1:18081\
--xmrNodeUsername=admin\
--xmrNodePassword=password

ExecStop=/bin/kill ${MAINPID} ; sleep 5
ExecStop=/bin/kill ${MAINPID}
Restart=always

# Hardening
Expand Down
Loading

0 comments on commit a970520

Please sign in to comment.