mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 08:37:05 +00:00
update tor to 0.4.7.13 using kmp-tor library
This commit is contained in:
@@ -50,7 +50,6 @@ import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -252,7 +251,7 @@ public class AppServices {
|
||||
}
|
||||
|
||||
if(Tor.getDefault() != null) {
|
||||
Tor.getDefault().shutdown();
|
||||
Tor.getDefault().getTorManager().destroy(true, success -> {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,23 +413,6 @@ public class AppServices {
|
||||
EventManager.get().post(new TorReadyStatusEvent());
|
||||
});
|
||||
torService.setOnFailed(workerStateEvent -> {
|
||||
Throwable exception = workerStateEvent.getSource().getException();
|
||||
if(exception instanceof TorServerAlreadyBoundException) {
|
||||
String proxyServer = Config.get().getProxyServer();
|
||||
if(proxyServer == null || proxyServer.equals("")) {
|
||||
proxyServer = "localhost:9050";
|
||||
Config.get().setProxyServer(proxyServer);
|
||||
}
|
||||
|
||||
if(proxyServer.equals("localhost:9050") || proxyServer.equals("127.0.0.1:9050")) {
|
||||
Config.get().setUseProxy(true);
|
||||
torService.cancel();
|
||||
restartServices();
|
||||
EventManager.get().post(new TorExternalStatusEvent());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EventManager.get().post(new TorFailedStatusEvent(workerStateEvent.getSource().getException()));
|
||||
});
|
||||
|
||||
@@ -490,8 +472,7 @@ public class AppServices {
|
||||
InetSocketAddress proxyAddress = new InetSocketAddress(proxyHostAndPort.getHost(), proxyHostAndPort.getPortOrDefault(ProxyTcpOverTlsTransport.DEFAULT_PROXY_PORT));
|
||||
proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress);
|
||||
} else if(AppServices.isTorRunning()) {
|
||||
InetSocketAddress proxyAddress = new InetSocketAddress("localhost", TorService.PROXY_PORT);
|
||||
proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress);
|
||||
proxy = Tor.getDefault().getProxy();
|
||||
}
|
||||
|
||||
//Setting new proxy authentication credentials will force a new Tor circuit to be created
|
||||
@@ -546,7 +527,7 @@ public class AppServices {
|
||||
|
||||
public static HostAndPort getTorProxy() {
|
||||
return AppServices.isTorRunning() ?
|
||||
HostAndPort.fromParts("localhost", TorService.PROXY_PORT) :
|
||||
Tor.getDefault().getProxyHostAndPort() :
|
||||
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.sparrowwallet.sparrow;
|
||||
|
||||
import com.sparrowwallet.drongo.LogHandler;
|
||||
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
public class TorLogHandler implements LogHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(TorLogHandler.class);
|
||||
|
||||
@Override
|
||||
public void handleLog(String threadName, Level level, String message, String loggerName, long timestamp, StackTraceElement[] callerData) {
|
||||
log.debug(message);
|
||||
EventManager.get().post(new TorStatusEvent(message));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
@@ -33,15 +34,15 @@ public class TorStatusLabel extends Label {
|
||||
}
|
||||
|
||||
public void update() {
|
||||
boolean proxyInUse = AppServices.isUsingProxy();
|
||||
boolean internal = AppServices.isTorRunning();
|
||||
|
||||
if(!proxyInUse || internal) {
|
||||
if(!Config.get().isUseProxy()) {
|
||||
torConnectionTest.cancel();
|
||||
if(internal) {
|
||||
if(AppServices.isTorRunning()) {
|
||||
setTooltip(new Tooltip("Internal Tor proxy enabled"));
|
||||
}
|
||||
} else if(!torConnectionTest.isRunning()) {
|
||||
if(torConnectionTest.getState() == Worker.State.CANCELLED || torConnectionTest.getState() == Worker.State.FAILED) {
|
||||
torConnectionTest.reset();
|
||||
}
|
||||
torConnectionTest.setPeriod(Duration.seconds(20.0));
|
||||
torConnectionTest.setBackoffStrategy(null);
|
||||
torConnectionTest.setOnSucceeded(workerStateEvent -> {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.sparrow.net.TorServerAlreadyBoundException;
|
||||
|
||||
public class TorFailedStatusEvent extends TorStatusEvent {
|
||||
private final Throwable exception;
|
||||
|
||||
public TorFailedStatusEvent(Throwable exception) {
|
||||
super("Tor failed to start: " + (exception instanceof TorServerAlreadyBoundException ? exception.getCause().getMessage() + " Is a Tor proxy already running?" :
|
||||
(exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage())));
|
||||
super("Tor failed to start: " + (exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage()));
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ public class Auth47 {
|
||||
}
|
||||
|
||||
Proxy proxy = AppServices.getProxy();
|
||||
if(proxy == null && callback.getHost().toLowerCase(Locale.ROOT).endsWith(TorService.TOR_ADDRESS_SUFFIX)) {
|
||||
if(proxy == null && callback.getHost().toLowerCase(Locale.ROOT).endsWith(Tor.TOR_ADDRESS_SUFFIX)) {
|
||||
throw new Auth47Exception("A Tor proxy must be configured to authenticate this resource.");
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ public class Bwt {
|
||||
|
||||
private HostAndPort getTorProxy() {
|
||||
return AppServices.isTorRunning() ?
|
||||
HostAndPort.fromParts("127.0.0.1", TorService.PROXY_PORT) :
|
||||
Tor.getDefault().getProxyHostAndPort() :
|
||||
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer().replace("localhost", "127.0.0.1")));
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ public class LnurlAuth {
|
||||
}
|
||||
|
||||
Proxy proxy = AppServices.getProxy();
|
||||
if(proxy == null && callback.getHost().toLowerCase(Locale.ROOT).endsWith(TorService.TOR_ADDRESS_SUFFIX)) {
|
||||
if(proxy == null && callback.getHost().toLowerCase(Locale.ROOT).endsWith(Tor.TOR_ADDRESS_SUFFIX)) {
|
||||
throw new LnurlAuthException("A Tor proxy must be configured to authenticate this resource.");
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ public enum Protocol {
|
||||
}
|
||||
|
||||
public static boolean isOnionHost(String host) {
|
||||
return host != null && host.toLowerCase(Locale.ROOT).endsWith(TorService.TOR_ADDRESS_SUFFIX);
|
||||
return host != null && host.toLowerCase(Locale.ROOT).endsWith(Tor.TOR_ADDRESS_SUFFIX);
|
||||
}
|
||||
|
||||
public static boolean isOnionAddress(Server server) {
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ProxyTcpOverTlsTransport extends TcpOverTlsTransport {
|
||||
protected void createSocket() throws IOException {
|
||||
InetSocketAddress proxyAddr = new InetSocketAddress(proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT));
|
||||
socket = new Socket(new Proxy(Proxy.Type.SOCKS, proxyAddr));
|
||||
socket.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(Protocol.SSL.getDefaultPort())));
|
||||
socket.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(getDefaultPort())));
|
||||
socket = sslSocketFactory.createSocket(socket, proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT), true);
|
||||
startHandshake((SSLSocket)socket);
|
||||
}
|
||||
|
||||
@@ -125,4 +125,9 @@ public class TcpOverTlsTransport extends TcpTransport {
|
||||
|
||||
return Storage.getCertificateFile(server.getHost()) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultPort() {
|
||||
return Protocol.SSL.getDefaultPort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +254,11 @@ public class TcpTransport implements CloseableTransport, TimeoutCounter {
|
||||
|
||||
protected void createSocket() throws IOException {
|
||||
socket = socketFactory.createSocket();
|
||||
socket.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(Protocol.TCP.getDefaultPort())));
|
||||
socket.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(getDefaultPort())));
|
||||
}
|
||||
|
||||
protected int getDefaultPort() {
|
||||
return Protocol.TCP.getDefaultPort();
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import io.matthewnelson.kmp.tor.KmpTorLoaderJvm;
|
||||
import io.matthewnelson.kmp.tor.PlatformInstaller;
|
||||
import io.matthewnelson.kmp.tor.TorConfigProviderJvm;
|
||||
import io.matthewnelson.kmp.tor.binary.extract.TorBinaryResource;
|
||||
import io.matthewnelson.kmp.tor.common.address.Port;
|
||||
import io.matthewnelson.kmp.tor.common.address.ProxyAddress;
|
||||
import io.matthewnelson.kmp.tor.controller.common.config.TorConfig;
|
||||
import io.matthewnelson.kmp.tor.controller.common.file.Path;
|
||||
import io.matthewnelson.kmp.tor.ext.callback.manager.CallbackTorManager;
|
||||
import io.matthewnelson.kmp.tor.manager.TorManager;
|
||||
import io.matthewnelson.kmp.tor.manager.common.event.TorManagerEvent;
|
||||
import io.matthewnelson.kmp.tor.manager.util.PortUtil;
|
||||
import org.controlsfx.tools.Platform;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.List;
|
||||
|
||||
public class Tor {
|
||||
private static final Logger log = LoggerFactory.getLogger(Tor.class);
|
||||
|
||||
public static final String TOR_DIR = "tor";
|
||||
public static final String TOR_ADDRESS_SUFFIX = ".onion";
|
||||
|
||||
private static Tor tor;
|
||||
|
||||
private final Path path = Path.invoke(Storage.getSparrowHome().getAbsolutePath()).builder().addSegment(TOR_DIR).build();
|
||||
private final CallbackTorManager instance;
|
||||
private ProxyAddress socksAddress;
|
||||
|
||||
public Tor() {
|
||||
Platform platform = Platform.getCurrent();
|
||||
String arch = System.getProperty("os.arch");
|
||||
PlatformInstaller installer;
|
||||
PlatformInstaller.InstallOption installOption = PlatformInstaller.InstallOption.CleanInstallIfMissing;
|
||||
|
||||
if(platform == Platform.OSX) {
|
||||
if(arch.equals("aarch64")) {
|
||||
installer = PlatformInstaller.macosArm64(installOption);
|
||||
} else {
|
||||
installer = PlatformInstaller.macosX64(installOption);
|
||||
}
|
||||
} else if(platform == Platform.WINDOWS) {
|
||||
installer = PlatformInstaller.mingwX64(installOption);
|
||||
} else if(platform == Platform.UNIX) {
|
||||
if(arch.equals("aarch64")) {
|
||||
TorBinaryResource linuxArm64 = TorBinaryResource.from(TorBinaryResource.OS.Linux, "arm64",
|
||||
"588496f3164d52b91f17e4db3372d8dfefa6366a8df265eebd4a28d4128992aa",
|
||||
List.of("libevent-2.1.so.7.gz", "libstdc++.so.6.gz", "libcrypto.so.1.1.gz", "tor.gz", "libssl.so.1.1.gz"));
|
||||
installer = PlatformInstaller.custom(installOption, linuxArm64);
|
||||
} else {
|
||||
installer = PlatformInstaller.linuxX64(installOption);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Sparrow's bundled Tor is not supported on " + platform + " " + arch);
|
||||
}
|
||||
|
||||
TorConfigProviderJvm torConfigProviderJvm = new TorConfigProviderJvm() {
|
||||
@NotNull
|
||||
@Override
|
||||
public Path getWorkDir() {
|
||||
return path.builder().addSegment("work").build();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Path getCacheDir() {
|
||||
return path.builder().addSegment("cache").build();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected TorConfig provide() {
|
||||
TorConfig.Builder builder = new TorConfig.Builder();
|
||||
|
||||
TorConfig.Setting.DormantCanceledByStartup dormantCanceledByStartup = new TorConfig.Setting.DormantCanceledByStartup();
|
||||
dormantCanceledByStartup.set(TorConfig.Option.AorTorF.getTrue());
|
||||
builder.put(dormantCanceledByStartup);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
|
||||
KmpTorLoaderJvm jvmLoader = new KmpTorLoaderJvm(installer, torConfigProviderJvm);
|
||||
TorManager torManager = TorManager.newInstance(jvmLoader);
|
||||
|
||||
torManager.debug(true);
|
||||
torManager.addListener(new TorManagerEvent.Listener() {
|
||||
@Override
|
||||
public void managerEventWarn(@NotNull String message) {
|
||||
log.warn(message);
|
||||
EventManager.get().post(new TorStatusEvent(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void managerEventInfo(@NotNull String message) {
|
||||
log.info(message);
|
||||
EventManager.get().post(new TorStatusEvent(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void managerEventDebug(@NotNull String message) {
|
||||
log.debug(message);
|
||||
EventManager.get().post(new TorStatusEvent(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void managerEventAddressInfo(@NotNull TorManagerEvent.AddressInfo info) {
|
||||
if(info.isNull) {
|
||||
socksAddress = null;
|
||||
} else {
|
||||
socksAddress = info.socksInfoToProxyAddress().iterator().next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.instance = new CallbackTorManager(torManager, uncaughtException -> {
|
||||
log.error("Uncaught exception from CallbackTorManager", uncaughtException);
|
||||
});
|
||||
}
|
||||
|
||||
public static Tor getDefault() {
|
||||
return Tor.tor;
|
||||
}
|
||||
|
||||
public static void setDefault(Tor tor) {
|
||||
Tor.tor = tor;
|
||||
}
|
||||
|
||||
public CallbackTorManager getTorManager() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Proxy getProxy() {
|
||||
if(socksAddress == null) {
|
||||
throw new IllegalStateException("Tor socks proxy is not yet instantiated");
|
||||
}
|
||||
|
||||
return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(socksAddress.address.getValue(), socksAddress.port.getValue()));
|
||||
}
|
||||
|
||||
public HostAndPort getProxyHostAndPort() {
|
||||
return HostAndPort.fromParts(socksAddress.address.getValue(), socksAddress.port.getValue());
|
||||
}
|
||||
|
||||
public static boolean isRunningExternally() {
|
||||
return !PortUtil.isTcpPortAvailable(Port.invoke(9050));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
public class TorServerAlreadyBoundException extends TorServerException {
|
||||
public TorServerAlreadyBoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public TorServerAlreadyBoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ public class TorServerException extends ServerException {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public TorServerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TorServerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@@ -1,86 +1,85 @@
|
||||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import io.matthewnelson.kmp.tor.ext.callback.manager.CallbackTorManager;
|
||||
import io.matthewnelson.kmp.tor.manager.common.event.TorManagerEvent;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import net.freehaven.tor.control.TorControlError;
|
||||
import org.berndpruenster.netlayer.tor.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Service to start internal Tor (including a Tor proxy running on localhost:9050)
|
||||
* Service to start internal Tor (including a Tor proxy running on localhost:9050, or another port if unavailable)
|
||||
*
|
||||
* This is a ScheduledService to take advantage of the retry on failure behaviour
|
||||
*/
|
||||
public class TorService extends ScheduledService<NativeTor> {
|
||||
public class TorService extends ScheduledService<Tor> {
|
||||
private static final Logger log = LoggerFactory.getLogger(TorService.class);
|
||||
|
||||
public static final int PROXY_PORT = 9050;
|
||||
public static final String TOR_DIR_PREFIX = "tor";
|
||||
public static final String TOR_ADDRESS_SUFFIX = ".onion";
|
||||
private final ReentrantLock startupLock = new ReentrantLock();
|
||||
private final Condition startupCondition = startupLock.newCondition();
|
||||
|
||||
@Override
|
||||
protected Task<NativeTor> createTask() {
|
||||
protected Task<Tor> createTask() {
|
||||
return new Task<>() {
|
||||
protected NativeTor call() throws IOException, TorServerException {
|
||||
if(Tor.getDefault() == null) {
|
||||
Path path = Files.createTempDirectory(TOR_DIR_PREFIX);
|
||||
File torInstallDir = path.toFile();
|
||||
torInstallDir.deleteOnExit();
|
||||
try {
|
||||
LinkedHashMap<String, String> torrcOptionsMap = new LinkedHashMap<>();
|
||||
torrcOptionsMap.put("SocksPort", Integer.toString(PROXY_PORT));
|
||||
torrcOptionsMap.put("HashedControlPassword", "16:D780432418F09B06609940000924317D3B9DF522A3191F8F4E597E9329");
|
||||
torrcOptionsMap.put("DisableNetwork", "0");
|
||||
Torrc override = new Torrc(torrcOptionsMap);
|
||||
private Exception startupException;
|
||||
|
||||
return new NativeTor(torInstallDir, Collections.emptyList(), override);
|
||||
} catch(TorCtlException e) {
|
||||
if(e.getCause() instanceof TorControlError) {
|
||||
if(e.getCause().getMessage().contains("Failed to bind")) {
|
||||
throw new TorServerAlreadyBoundException("Tor server already bound", e.getCause());
|
||||
@Override
|
||||
protected Tor call() throws Exception {
|
||||
Tor tor = Tor.getDefault();
|
||||
if(tor == null) {
|
||||
tor = new Tor();
|
||||
CallbackTorManager callbackTorManager = tor.getTorManager();
|
||||
callbackTorManager.addListener(new TorManagerEvent.Listener() {
|
||||
@Override
|
||||
public void managerEventAddressInfo(@NotNull TorManagerEvent.AddressInfo info) {
|
||||
if(!info.isNull) {
|
||||
try {
|
||||
startupLock.lock();
|
||||
startupCondition.signalAll();
|
||||
} finally {
|
||||
startupLock.unlock();
|
||||
}
|
||||
}
|
||||
log.error("Failed to start Tor", e);
|
||||
throw new TorServerException("Failed to start Tor", e.getCause());
|
||||
} else {
|
||||
log.error("Failed to start Tor", e);
|
||||
throw new TorServerException("Failed to start Tor", e);
|
||||
}
|
||||
});
|
||||
callbackTorManager.start(throwable -> {
|
||||
if(throwable instanceof Exception exception) {
|
||||
startupException = exception;
|
||||
} else {
|
||||
startupException = new Exception(throwable);
|
||||
}
|
||||
log.error("Error", throwable);
|
||||
try {
|
||||
startupLock.lock();
|
||||
startupCondition.signalAll();
|
||||
} finally {
|
||||
startupLock.unlock();
|
||||
}
|
||||
}, success -> {
|
||||
log.info("Tor daemon started successfully");
|
||||
});
|
||||
|
||||
try {
|
||||
startupLock.lock();
|
||||
if(!startupCondition.await(5, TimeUnit.MINUTES)) {
|
||||
throw new TorStartupException("Tor failed to start after 5 minutes, giving up");
|
||||
}
|
||||
|
||||
if(startupException != null) {
|
||||
throw startupException;
|
||||
}
|
||||
} finally {
|
||||
startupLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return tor;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Socket getControlSocket() {
|
||||
Tor tor = Tor.getDefault();
|
||||
if(tor != null) {
|
||||
try {
|
||||
Class<?> torClass = Class.forName("org.berndpruenster.netlayer.tor.Tor");
|
||||
Field torControllerField = torClass.getDeclaredField("torController");
|
||||
torControllerField.setAccessible(true);
|
||||
TorController torController = (TorController)torControllerField.get(tor);
|
||||
|
||||
Class<?> torControllerClass = Class.forName("org.berndpruenster.netlayer.tor.TorController");
|
||||
Field socketField = torControllerClass.getDeclaredField("socket");
|
||||
socketField.setAccessible(true);
|
||||
return (Socket)socketField.get(torController);
|
||||
} catch(Exception e) {
|
||||
log.error("Error retrieving Tor control socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
public class TorStartupException extends TorServerException {
|
||||
public TorStartupException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public TorStartupException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TorStartupException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -27,21 +27,15 @@ public class TorTcpOverTlsTransport extends TcpOverTlsTransport {
|
||||
|
||||
@Override
|
||||
protected void createSocket() throws IOException {
|
||||
TorTcpTransport torTcpTransport = new TorTcpTransport(server);
|
||||
TorTcpTransport torTcpTransport = new TorTcpTransport(server) {
|
||||
@Override
|
||||
protected int getDefaultPort() {
|
||||
return Protocol.SSL.getDefaultPort();
|
||||
}
|
||||
};
|
||||
torTcpTransport.createSocket();
|
||||
socket = torTcpTransport.socket;
|
||||
|
||||
try {
|
||||
Field socketField = socket.getClass().getDeclaredField("socket");
|
||||
socketField.setAccessible(true);
|
||||
Socket innerSocket = (Socket)socketField.get(socket);
|
||||
Field connectedField = innerSocket.getClass().getSuperclass().getDeclaredField("connected");
|
||||
connectedField.setAccessible(true);
|
||||
connectedField.set(innerSocket, true);
|
||||
} catch(Exception e) {
|
||||
log.error("Could not set socket connected status", e);
|
||||
}
|
||||
|
||||
socket = sslSocketFactory.createSocket(socket, server.getHost(), server.getPortOrDefault(Protocol.SSL.getDefaultPort()), true);
|
||||
startHandshake((SSLSocket)socket);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import org.berndpruenster.netlayer.tor.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
public class TorTcpTransport extends TcpTransport {
|
||||
public static final String TOR_DIR_PREFIX = "tor";
|
||||
|
||||
public TorTcpTransport(HostAndPort server) {
|
||||
super(server);
|
||||
}
|
||||
@@ -24,6 +22,7 @@ public class TorTcpTransport extends TcpTransport {
|
||||
throw new IllegalStateException("Can't create Tor socket, Tor is not running");
|
||||
}
|
||||
|
||||
socket = new TorSocket(server.getHost(), server.getPortOrDefault(Protocol.TCP.getDefaultPort()), "sparrow");
|
||||
socket = new Socket(Tor.getDefault().getProxy());
|
||||
socket.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(getDefaultPort())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
@@ -494,20 +492,6 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||
torService.cancel();
|
||||
testResults.appendText("\nTor failed to start");
|
||||
showConnectionFailure(workerStateEvent.getSource().getException());
|
||||
|
||||
Throwable exception = workerStateEvent.getSource().getException();
|
||||
if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER &&
|
||||
exception instanceof TorServerAlreadyBoundException &&
|
||||
useProxyOriginal == null && !useProxy.isSelected() &&
|
||||
(proxyHost.getText().isEmpty() || proxyHost.getText().equals("localhost") || proxyHost.getText().equals("127.0.0.1")) &&
|
||||
(proxyPort.getText().isEmpty() || proxyPort.getText().equals("9050"))) {
|
||||
useProxy.setSelected(true);
|
||||
proxyHost.setText("localhost");
|
||||
proxyPort.setText("9050");
|
||||
useProxyOriginal = false;
|
||||
testResults.appendText("\n\nAssuming Tor proxy is running on port 9050 and trying again...");
|
||||
startElectrumConnection();
|
||||
}
|
||||
});
|
||||
|
||||
torService.start();
|
||||
@@ -658,8 +642,6 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||
reason = tlsServerException.getMessage() + "\n\n" + reason;
|
||||
} else if(exception instanceof ProxyServerException) {
|
||||
reason += ". Check if the proxy server is running.";
|
||||
} else if(exception instanceof TorServerAlreadyBoundException) {
|
||||
reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?";
|
||||
} else if(reason != null && (reason.contains("Check if Bitcoin Core is running") || reason.contains("Could not connect to Bitcoin Core RPC"))) {
|
||||
reason += "\n\nSee https://sparrowwallet.com/docs/connect-node.html";
|
||||
} else if(reason != null && (reason.startsWith("Cannot connect to hidden service"))) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.util.Duration;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
@@ -203,8 +202,6 @@ public class ServerTestDialog extends DialogWindow {
|
||||
reason = tlsServerException.getMessage() + "\n\n" + reason;
|
||||
} else if(exception instanceof ProxyServerException) {
|
||||
reason += ". Check if the proxy server is running.";
|
||||
} else if(exception instanceof TorServerAlreadyBoundException) {
|
||||
reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?";
|
||||
} else if(reason != null && reason.contains("Check if Bitcoin Core is running")) {
|
||||
reason += "\n\nSee https://sparrowwallet.com/docs/connect-node.html";
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.sparrowwallet.sparrow.whirlpool.tor;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.samourai.tor.client.TorClientService;
|
||||
import com.sparrowwallet.sparrow.net.TorService;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.net.Tor;
|
||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||
import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -23,13 +25,12 @@ public class SparrowTorClientService extends TorClientService {
|
||||
public void changeIdentity() {
|
||||
HostAndPort proxy = whirlpool.getTorProxy();
|
||||
if(proxy != null) {
|
||||
Socket controlSocket = TorService.getControlSocket();
|
||||
if(controlSocket != null) {
|
||||
try {
|
||||
writeNewNym(controlSocket);
|
||||
} catch(Exception e) {
|
||||
log.warn("Error sending NEWNYM to " + controlSocket, e);
|
||||
}
|
||||
if(AppServices.isTorRunning()) {
|
||||
Tor.getDefault().getTorManager().signal(TorControlSignal.Signal.NewNym, throwable -> {
|
||||
log.warn("Failed to signal newnym");
|
||||
}, successEvent -> {
|
||||
log.info("Signalled newnym for new Tor circuit");
|
||||
});
|
||||
} else {
|
||||
HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1);
|
||||
try(Socket socket = new Socket(control.getHost(), control.getPort())) {
|
||||
|
||||
Reference in New Issue
Block a user