Support tor w/either Netlayer or direct bind to SOCKS5 data port

This commit is contained in:
fa2a5qj3 2024-07-29 07:54:53 -04:00 committed by GitHub
parent 84a2828e90
commit 3d44f3777c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 377 additions and 154 deletions

View file

@ -77,6 +77,7 @@ public class Config {
public static final String SEED_NODES = "seedNodes"; public static final String SEED_NODES = "seedNodes";
public static final String BAN_LIST = "banList"; public static final String BAN_LIST = "banList";
public static final String NODE_PORT = "nodePort"; 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 USE_LOCALHOST_FOR_P2P = "useLocalhostForP2P";
public static final String MAX_CONNECTIONS = "maxConnections"; public static final String MAX_CONNECTIONS = "maxConnections";
public static final String SOCKS_5_PROXY_XMR_ADDRESS = "socks5ProxyXmrAddress"; public static final String SOCKS_5_PROXY_XMR_ADDRESS = "socks5ProxyXmrAddress";
@ -122,6 +123,7 @@ public class Config {
public static final String DEFAULT_REGTEST_HOST = "none"; public static final String DEFAULT_REGTEST_HOST = "none";
public static final int DEFAULT_NUM_CONNECTIONS_FOR_BTC = 9; // down from BitcoinJ default of 12 public static final int DEFAULT_NUM_CONNECTIONS_FOR_BTC = 9; // down from BitcoinJ default of 12
static final String DEFAULT_CONFIG_FILE_NAME = "haveno.properties"; static final String DEFAULT_CONFIG_FILE_NAME = "haveno.properties";
public static final String UNSPECIFIED_HIDDENSERVICE_ADDRESS = "placeholder.onion";
// Static fields that provide access to Config properties in locations where injecting // Static fields that provide access to Config properties in locations where injecting
// a Config instance is not feasible. See Javadoc for corresponding static accessors. // a Config instance is not feasible. See Javadoc for corresponding static accessors.
@ -151,6 +153,7 @@ public class Config {
public final File appDataDir; public final File appDataDir;
public final int walletRpcBindPort; public final int walletRpcBindPort;
public final int nodePort; public final int nodePort;
public final String hiddenServiceAddress;
public final int maxMemory; public final int maxMemory;
public final String logLevel; public final String logLevel;
public final List<String> bannedXmrNodes; public final List<String> bannedXmrNodes;
@ -286,6 +289,12 @@ public class Config {
.ofType(Integer.class) .ofType(Integer.class)
.defaultsTo(9999); .defaultsTo(9999);
ArgumentAcceptingOptionSpec<String> hiddenServiceAddressOpt =
parser.accepts(HIDDEN_SERVICE_ADDRESS, "Hidden Service Address to listen on")
.withRequiredArg()
.ofType(String.class)
.defaultsTo(UNSPECIFIED_HIDDENSERVICE_ADDRESS);
ArgumentAcceptingOptionSpec<Integer> walletRpcBindPortOpt = ArgumentAcceptingOptionSpec<Integer> walletRpcBindPortOpt =
parser.accepts(WALLET_RPC_BIND_PORT, "Port to bind the wallet RPC on") parser.accepts(WALLET_RPC_BIND_PORT, "Port to bind the wallet RPC on")
.withRequiredArg() .withRequiredArg()
@ -670,6 +679,7 @@ public class Config {
this.helpRequested = options.has(helpOpt); this.helpRequested = options.has(helpOpt);
this.configFile = configFile; this.configFile = configFile;
this.nodePort = options.valueOf(nodePortOpt); this.nodePort = options.valueOf(nodePortOpt);
this.hiddenServiceAddress = options.valueOf(hiddenServiceAddressOpt);
this.walletRpcBindPort = options.valueOf(walletRpcBindPortOpt); this.walletRpcBindPort = options.valueOf(walletRpcBindPortOpt);
this.maxMemory = options.valueOf(maxMemoryOpt); this.maxMemory = options.valueOf(maxMemoryOpt);
this.logLevel = options.valueOf(logLevelOpt); this.logLevel = options.valueOf(logLevelOpt);

View file

@ -28,8 +28,10 @@ import haveno.network.p2p.network.LocalhostNetworkNode;
import haveno.network.p2p.network.NetworkNode; import haveno.network.p2p.network.NetworkNode;
import haveno.network.p2p.network.NewTor; import haveno.network.p2p.network.NewTor;
import haveno.network.p2p.network.RunningTor; import haveno.network.p2p.network.RunningTor;
import haveno.network.p2p.network.DirectBindTor;
import haveno.network.p2p.network.TorMode; import haveno.network.p2p.network.TorMode;
import haveno.network.p2p.network.TorNetworkNode; import haveno.network.p2p.network.TorNetworkNodeDirectBind;
import haveno.network.p2p.network.TorNetworkNodeNetlayer;
import java.io.File; import java.io.File;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -44,6 +46,7 @@ public class NetworkNodeProvider implements Provider<NetworkNode> {
@Named(Config.MAX_CONNECTIONS) int maxConnections, @Named(Config.MAX_CONNECTIONS) int maxConnections,
@Named(Config.USE_LOCALHOST_FOR_P2P) boolean useLocalhostForP2P, @Named(Config.USE_LOCALHOST_FOR_P2P) boolean useLocalhostForP2P,
@Named(Config.NODE_PORT) int port, @Named(Config.NODE_PORT) int port,
@Named(Config.HIDDEN_SERVICE_ADDRESS) String hiddenServiceAddress,
@Named(Config.TOR_DIR) File torDir, @Named(Config.TOR_DIR) File torDir,
@Nullable @Named(Config.TORRC_FILE) File torrcFile, @Nullable @Named(Config.TORRC_FILE) File torrcFile,
@Named(Config.TORRC_OPTIONS) String torrcOptions, @Named(Config.TORRC_OPTIONS) String torrcOptions,
@ -62,10 +65,15 @@ public class NetworkNodeProvider implements Provider<NetworkNode> {
torrcOptions, torrcOptions,
controlHost, controlHost,
controlPort, controlPort,
hiddenServiceAddress,
password, password,
cookieFile, cookieFile,
useSafeCookieAuthentication); useSafeCookieAuthentication);
networkNode = new TorNetworkNode(port, networkProtoResolver, streamIsolation, torMode, banFilter, maxConnections, controlHost); if (torMode instanceof NewTor || torMode instanceof RunningTor) {
networkNode = new TorNetworkNodeNetlayer(port, networkProtoResolver, torMode, banFilter, maxConnections, streamIsolation, controlHost);
} else {
networkNode = new TorNetworkNodeDirectBind(port, networkProtoResolver, banFilter, maxConnections, hiddenServiceAddress);
}
} }
} }
@ -75,12 +83,19 @@ public class NetworkNodeProvider implements Provider<NetworkNode> {
String torrcOptions, String torrcOptions,
String controlHost, String controlHost,
int controlPort, int controlPort,
String hiddenServiceAddress,
String password, String password,
@Nullable File cookieFile, @Nullable File cookieFile,
boolean useSafeCookieAuthentication) { boolean useSafeCookieAuthentication) {
return controlPort != Config.UNSPECIFIED_PORT ? if (!hiddenServiceAddress.equals(Config.UNSPECIFIED_HIDDENSERVICE_ADDRESS)) {
new RunningTor(torDir, controlHost, controlPort, password, cookieFile, useSafeCookieAuthentication) : return new DirectBindTor();
new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider); }
else if (controlPort != Config.UNSPECIFIED_PORT) {
return new RunningTor(torDir, controlHost, controlPort, password, cookieFile, useSafeCookieAuthentication);
}
else {
return new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider);
}
} }
@Override @Override

View file

@ -26,6 +26,7 @@ import haveno.common.config.Config;
import static haveno.common.config.Config.BAN_LIST; import static haveno.common.config.Config.BAN_LIST;
import static haveno.common.config.Config.MAX_CONNECTIONS; import static haveno.common.config.Config.MAX_CONNECTIONS;
import static haveno.common.config.Config.NODE_PORT; 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.REPUBLISH_MAILBOX_ENTRIES;
import static haveno.common.config.Config.SOCKS_5_PROXY_HTTP_ADDRESS; import static haveno.common.config.Config.SOCKS_5_PROXY_HTTP_ADDRESS;
import static haveno.common.config.Config.SOCKS_5_PROXY_XMR_ADDRESS; import static haveno.common.config.Config.SOCKS_5_PROXY_XMR_ADDRESS;
@ -87,6 +88,7 @@ public class P2PModule extends AppModule {
bind(File.class).annotatedWith(named(TOR_DIR)).toInstance(config.torDir); bind(File.class).annotatedWith(named(TOR_DIR)).toInstance(config.torDir);
bind(int.class).annotatedWith(named(NODE_PORT)).toInstance(config.nodePort); 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); bindConstant().annotatedWith(named(MAX_CONNECTIONS)).to(config.maxConnections);

View file

@ -0,0 +1,22 @@
package haveno.network.p2p.network;
import lombok.extern.slf4j.Slf4j;
import org.berndpruenster.netlayer.tor.Tor;
@Slf4j
public class DirectBindTor extends TorMode {
public DirectBindTor() {
super(null);
}
@Override
public Tor getTor() {
return null;
}
@Override
public String getHiddenServiceDirectory() {
return null;
}
}

View file

@ -18,50 +18,26 @@
package haveno.network.p2p.network; package haveno.network.p2p.network;
import haveno.network.p2p.NodeAddress; 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.proto.network.NetworkProtoResolver;
import haveno.common.util.SingleThreadExecutorUtils; 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 com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import java.security.SecureRandom;
import java.net.Socket; import java.net.Socket;
import java.io.IOException; import java.io.IOException;
import java.util.Base64;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j @Slf4j
public class TorNetworkNode extends NetworkNode { public abstract class TorNetworkNode extends NetworkNode {
private static final long SHUT_DOWN_TIMEOUT = 2;
private final String torControlHost; protected final ExecutorService executor;
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;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -69,15 +45,9 @@ public class TorNetworkNode extends NetworkNode {
public TorNetworkNode(int servicePort, public TorNetworkNode(int servicePort,
NetworkProtoResolver networkProtoResolver, NetworkProtoResolver networkProtoResolver,
boolean useStreamIsolation,
TorMode torMode,
@Nullable BanFilter banFilter, @Nullable BanFilter banFilter,
int maxConnections, String torControlHost) { int maxConnections) {
super(servicePort, networkProtoResolver, banFilter, maxConnections); super(servicePort, networkProtoResolver, banFilter, maxConnections);
this.torMode = torMode;
this.streamIsolation = useStreamIsolation;
this.torControlHost = torControlHost;
executor = SingleThreadExecutorUtils.getSingleThreadExecutor("StartTor"); executor = SingleThreadExecutorUtils.getSingleThreadExecutor("StartTor");
} }
@ -87,121 +57,19 @@ public class TorNetworkNode extends NetworkNode {
@Override @Override
public void start(@Nullable SetupListener setupListener) { public void start(@Nullable SetupListener setupListener) {
torMode.doRollingBackup();
if (setupListener != null) if (setupListener != null)
addSetupListener(setupListener); addSetupListener(setupListener);
createTorAndHiddenService(Utils.findFreeSystemPort(), servicePort); createTorAndHiddenService();
}
@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);
}
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);
return null;
}
} }
public void shutDown(@Nullable Runnable shutDownCompleteHandler) { public void shutDown(@Nullable Runnable shutDownCompleteHandler) {
log.info("TorNetworkNode shutdown started"); super.shutDown(shutDownCompleteHandler);
if (shutDownComplete) {
log.info("TorNetworkNode shutdown already completed");
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
return;
}
if (shutDownInProgress) {
log.warn("Ignoring request to shut down because shut down is in progress");
return;
}
shutDownInProgress = true;
shutDownTimeoutTimer = UserThread.runAfter(() -> {
log.error("A timeout occurred at shutDown");
shutDownComplete = true;
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
executor.shutdownNow();
}, SHUT_DOWN_TIMEOUT);
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);
} finally {
shutDownTimeoutTimer.stop();
shutDownComplete = true;
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
}
});
} }
/////////////////////////////////////////////////////////////////////////////////////////// public abstract Socks5Proxy getSocksProxy();
// Create tor and hidden service
///////////////////////////////////////////////////////////////////////////////////////////
private void createTorAndHiddenService(int localPort, int servicePort) { protected abstract Socket createSocket(NodeAddress peerNodeAddress) throws IOException;
executor.submit(() -> {
try { protected abstract void createTorAndHiddenService();
Tor.setDefault(torMode.getTor());
long ts = System.currentTimeMillis();
hiddenServiceSocket = new HiddenServiceSocket(localPort, torMode.getHiddenServiceDirectory(), servicePort);
nodeAddressProperty.set(new NodeAddress(hiddenServiceSocket.getServiceName() + ":" + hiddenServiceSocket.getHiddenServicePort()));
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);
}
} catch (IOException e) {
log.error("Could not connect to running Tor", e);
UserThread.execute(() -> setupListeners.forEach(s -> s.onSetupFailed(new RuntimeException(e.getMessage()))));
} catch (Throwable ignore) {
}
return null;
});
}
} }

View file

@ -0,0 +1,113 @@
package haveno.network.p2p.network;
import haveno.common.util.Hex;
import haveno.network.p2p.NodeAddress;
import haveno.common.UserThread;
import haveno.common.proto.network.NetworkProtoResolver;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import java.net.Socket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class TorNetworkNodeDirectBind extends TorNetworkNode {
private static final int TOR_DATA_PORT = 9050; // TODO: config option?
private final String serviceAddress;
public TorNetworkNodeDirectBind(int servicePort,
NetworkProtoResolver networkProtoResolver,
@Nullable BanFilter banFilter,
int maxConnections, String hiddenServiceAddress) {
super(servicePort, networkProtoResolver, banFilter, maxConnections);
this.serviceAddress = hiddenServiceAddress;
}
@Override
public void shutDown(@Nullable Runnable shutDownCompleteHandler) {
super.shutDown(() -> {
log.info("TorNetworkNode shutdown already completed");
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
});
}
@Override
public Socks5Proxy getSocksProxy() {
Socks5Proxy proxy = new Socks5Proxy(InetAddress.getLoopbackAddress(), TOR_DATA_PORT);
proxy.resolveAddrLocally(false);
return proxy;
}
@Override
protected Socket createSocket(NodeAddress peerNodeAddress) throws IOException {
// 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(), TOR_DATA_PORT);
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");
}
@Override
protected void createTorAndHiddenService() {
executor.submit(() -> {
try {
// 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));
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 external Tor", e);
UserThread.execute(() -> setupListeners.forEach(s -> s.onSetupFailed(new RuntimeException(e.getMessage()))));
}
return null;
});
}
}

View file

@ -0,0 +1,174 @@
package haveno.network.p2p.network;
import haveno.common.Timer;
import haveno.network.p2p.NodeAddress;
import haveno.common.UserThread;
import haveno.common.proto.network.NetworkProtoResolver;
import haveno.network.utils.Utils;
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.io.IOException;
import java.util.Base64;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class TorNetworkNodeNetlayer extends TorNetworkNode {
private static final long SHUT_DOWN_TIMEOUT = 2;
private HiddenServiceSocket hiddenServiceSocket;
private boolean streamIsolation;
private Socks5Proxy socksProxy;
protected TorMode torMode;
private Tor tor;
private final String torControlHost;
private Timer shutDownTimeoutTimer;
private boolean shutDownInProgress;
private boolean shutDownComplete;
public TorNetworkNodeNetlayer(int servicePort,
NetworkProtoResolver networkProtoResolver,
TorMode torMode,
@Nullable BanFilter banFilter,
int maxConnections,
boolean useStreamIsolation,
String torControlHost) {
super(servicePort, networkProtoResolver, banFilter, maxConnections);
this.torControlHost = torControlHost;
this.streamIsolation = useStreamIsolation;
this.torMode = torMode;
}
@Override
public void start(@Nullable SetupListener setupListener) {
torMode.doRollingBackup();
super.start(setupListener);
}
@Override
public void shutDown(@Nullable Runnable shutDownCompleteHandler) {
log.info("TorNetworkNode shutdown started");
if (shutDownComplete) {
log.info("TorNetworkNode shutdown already completed");
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
return;
}
if (shutDownInProgress) {
log.warn("Ignoring request to shut down because shut down is in progress");
return;
}
shutDownInProgress = true;
shutDownTimeoutTimer = UserThread.runAfter(() -> {
log.error("A timeout occurred at shutDown");
shutDownComplete = true;
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
executor.shutdownNow();
}, SHUT_DOWN_TIMEOUT);
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);
} finally {
shutDownTimeoutTimer.stop();
shutDownComplete = true;
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
}
});
}
@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);
}
@Override
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);
return null;
}
}
@Override
protected void createTorAndHiddenService() {
int localPort = Utils.findFreeSystemPort();
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()));
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);
}
} catch (IOException e) {
log.error("Could not connect to running Tor", e);
UserThread.execute(() -> setupListeners.forEach(s -> s.onSetupFailed(new RuntimeException(e.getMessage()))));
} catch (Throwable ignore) {
}
return null;
});
}
}

View file

@ -50,8 +50,13 @@ public class TorNetworkNodeTest {
public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException { public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException {
latch = new CountDownLatch(1); latch = new CountDownLatch(1);
int port = 9001; int port = 9001;
TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(), false, TorNetworkNode node1 = new TorNetworkNodeNetlayer(port,
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses),
null,
12,
false,
"127.0.0.1");
node1.start(new SetupListener() { node1.start(new SetupListener() {
@Override @Override
public void onTorNodeReady() { public void onTorNodeReady() {
@ -77,8 +82,13 @@ public class TorNetworkNodeTest {
latch = new CountDownLatch(1); latch = new CountDownLatch(1);
int port2 = 9002; int port2 = 9002;
TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(), false, TorNetworkNode node2 = new TorNetworkNodeNetlayer(port2,
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses),
null,
12,
false,
"127.0.0.1");
node2.start(new SetupListener() { node2.start(new SetupListener() {
@Override @Override
public void onTorNodeReady() { public void onTorNodeReady() {
@ -135,8 +145,13 @@ public class TorNetworkNodeTest {
public void testTorNodeAfterBothReady() throws InterruptedException, IOException { public void testTorNodeAfterBothReady() throws InterruptedException, IOException {
latch = new CountDownLatch(2); latch = new CountDownLatch(2);
int port = 9001; int port = 9001;
TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(), false, TorNetworkNode node1 = new TorNetworkNodeNetlayer(port,
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses),
null,
12,
false,
"127.0.0.1");
node1.start(new SetupListener() { node1.start(new SetupListener() {
@Override @Override
public void onTorNodeReady() { public void onTorNodeReady() {
@ -161,8 +176,12 @@ public class TorNetworkNodeTest {
}); });
int port2 = 9002; int port2 = 9002;
TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(), false, TorNetworkNode node2 = new TorNetworkNodeNetlayer(port2, TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses), null, 12, "127.0.0.1"); new NewTor(new File("torNode_" + port), null, "", this::getBridgeAddresses),
null,
12,
false,
"127.0.0.1");
node2.start(new SetupListener() { node2.start(new SetupListener() {
@Override @Override
public void onTorNodeReady() { public void onTorNodeReady() {