automatically switch between internal and external tor proxy as required when connecting to server

This commit is contained in:
Craig Raw 2021-04-05 14:56:23 +02:00
parent c3ae98f3d1
commit 579b9a685b
11 changed files with 100 additions and 14 deletions

View file

@ -1633,6 +1633,12 @@ public class AppController implements Initializable {
statusUpdated(new StatusEvent(event.getStatus())); statusUpdated(new StatusEvent(event.getStatus()));
} }
@Subscribe
public void torExternalStatus(TorExternalStatusEvent event) {
serverToggle.setDisable(false);
statusUpdated(new StatusEvent(event.getStatus()));
}
@Subscribe @Subscribe
public void newBlock(NewBlockEvent event) { public void newBlock(NewBlockEvent event) {
setServerToggleTooltip(event.getHeight()); setServerToggleTooltip(event.getHeight());

View file

@ -94,7 +94,7 @@ public class AppServices {
@Override @Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean online) { public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean online) {
if(online) { if(online) {
if(Config.get().requiresTor() && !isTorRunning()) { if(Config.get().requiresInternalTor() && !isTorRunning()) {
torService.start(); torService.start();
} else { } else {
restartServices(); restartServices();
@ -122,7 +122,7 @@ public class AppServices {
onlineProperty.addListener(onlineServicesListener); onlineProperty.addListener(onlineServicesListener);
if(config.getMode() == Mode.ONLINE) { if(config.getMode() == Mode.ONLINE) {
if(config.requiresTor()) { if(config.requiresInternalTor()) {
torService.start(); torService.start();
} else { } else {
restartServices(); restartServices();
@ -233,6 +233,12 @@ public class AppServices {
} }
} }
if(failEvent.getSource().getException() instanceof ProxyServerException && Config.get().isUseProxy() && Config.get().requiresTor()) {
Config.get().setUseProxy(false);
Platform.runLater(() -> restartService(torService));
return;
}
onlineProperty.removeListener(onlineServicesListener); onlineProperty.removeListener(onlineServicesListener);
onlineProperty.setValue(false); onlineProperty.setValue(false);
onlineProperty.addListener(onlineServicesListener); onlineProperty.addListener(onlineServicesListener);
@ -314,6 +320,23 @@ public class AppServices {
EventManager.get().post(new TorReadyStatusEvent()); EventManager.get().post(new TorReadyStatusEvent());
}); });
torService.setOnFailed(workerStateEvent -> { 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())); EventManager.get().post(new TorFailedStatusEvent(workerStateEvent.getSource().getException()));
}); });

View file

@ -0,0 +1,7 @@
package com.sparrowwallet.sparrow.event;
public class TorExternalStatusEvent extends TorStatusEvent {
public TorExternalStatusEvent() {
super("Tor is already running, using external instance...");
}
}

View file

@ -1,12 +1,13 @@
package com.sparrowwallet.sparrow.event; package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.net.TorServerAlreadyBoundException;
public class TorFailedStatusEvent extends TorStatusEvent { public class TorFailedStatusEvent extends TorStatusEvent {
private final Throwable exception; private final Throwable exception;
public TorFailedStatusEvent(Throwable exception) { public TorFailedStatusEvent(Throwable exception) {
super("Tor failed to start: " + (exception.getCause() != null ? super("Tor failed to start: " + (exception instanceof TorServerAlreadyBoundException ? exception.getCause().getMessage() + " Is a Tor proxy already running?" :
(exception.getCause().getMessage().contains("Failed to bind") ? exception.getCause().getMessage() + " Is a Tor proxy already running?" : exception.getCause().getMessage() ) : (exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage())));
exception.getMessage()));
this.exception = exception; this.exception = exception;
} }

View file

@ -303,8 +303,16 @@ public class Config {
return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : (getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? getPublicElectrumServer() : getElectrumServer()); return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : (getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? getPublicElectrumServer() : getElectrumServer());
} }
public boolean requiresInternalTor() {
if(isUseProxy()) {
return false;
}
return requiresTor();
}
public boolean requiresTor() { public boolean requiresTor() {
if(isUseProxy() || !hasServerAddress()) { if(!hasServerAddress()) {
return false; return false;
} }

View file

@ -0,0 +1,7 @@
package com.sparrowwallet.sparrow.net;
public class ProxyServerException extends ServerException {
public ProxyServerException(Throwable cause) {
super(cause);
}
}

View file

@ -196,6 +196,10 @@ public class TcpTransport implements Transport, Closeable {
} catch(SSLHandshakeException e) { } catch(SSLHandshakeException e) {
throw new TlsServerException(server, e); throw new TlsServerException(server, e);
} catch(IOException e) { } catch(IOException e) {
if(e.getStackTrace().length > 0 && e.getStackTrace()[0].getClassName().contains("SocksSocketImpl")) {
throw new ProxyServerException(e);
}
throw new ServerException(e); throw new ServerException(e);
} }
} }

View file

@ -0,0 +1,11 @@
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);
}
}

View file

@ -0,0 +1,11 @@
package com.sparrowwallet.sparrow.net;
public class TorServerException extends ServerException {
public TorServerException(Throwable cause) {
super(cause);
}
public TorServerException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -32,7 +32,7 @@ public class TorService extends ScheduledService<NativeTor> {
@Override @Override
protected Task<NativeTor> createTask() { protected Task<NativeTor> createTask() {
return new Task<>() { return new Task<>() {
protected NativeTor call() throws IOException { protected NativeTor call() throws IOException, TorServerException {
if(Tor.getDefault() == null) { if(Tor.getDefault() == null) {
Path path = Files.createTempDirectory(TOR_DIR_PREFIX); Path path = Files.createTempDirectory(TOR_DIR_PREFIX);
File torInstallDir = path.toFile(); File torInstallDir = path.toFile();
@ -45,11 +45,15 @@ public class TorService extends ScheduledService<NativeTor> {
return new NativeTor(torInstallDir, Collections.emptyList(), override); return new NativeTor(torInstallDir, Collections.emptyList(), override);
} catch(TorCtlException e) { } catch(TorCtlException e) {
log.error("Failed to start Tor", e);
if(e.getCause() instanceof TorControlError) { if(e.getCause() instanceof TorControlError) {
throw new IOException("Failed to start Tor", e.getCause()); if(e.getCause().getMessage().contains("Failed to bind")) {
throw new TorServerAlreadyBoundException("Tor server already bound", e.getCause());
}
log.error("Failed to start Tor", e);
throw new TorServerException("Failed to start Tor", e.getCause());
} else { } else {
throw new IOException("Failed to start Tor", e); log.error("Failed to start Tor", e);
throw new TorServerException("Failed to start Tor", e);
} }
} }
} }

View file

@ -306,7 +306,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null)); testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null));
testResults.setText("Connecting to " + config.getServerAddress() + "..."); testResults.setText("Connecting to " + config.getServerAddress() + "...");
if(Config.get().requiresTor() && Tor.getDefault() == null) { if(Config.get().requiresInternalTor() && Tor.getDefault() == null) {
startTor(); startTor();
} else { } else {
startElectrumConnection(); startElectrumConnection();
@ -430,8 +430,10 @@ public class ServerPreferencesController extends PreferencesDetailController {
Throwable exception = workerStateEvent.getSource().getException(); Throwable exception = workerStateEvent.getSource().getException();
if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER && if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER &&
exception.getCause() != null && exception.getCause() instanceof TorControlError && exception.getCause().getMessage().contains("Failed to bind") && exception instanceof TorServerAlreadyBoundException &&
useProxyOriginal == null && !useProxy.isSelected() && proxyHost.getText().isEmpty() && proxyPort.getText().isEmpty()) { 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); useProxy.setSelected(true);
proxyHost.setText("localhost"); proxyHost.setText("localhost");
proxyPort.setText("9050"); proxyPort.setText("9050");
@ -566,7 +568,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
reason = tlsServerException.getMessage() + "\n\n" + reason; reason = tlsServerException.getMessage() + "\n\n" + reason;
} else if(exception.getCause() != null && exception.getCause() instanceof TorControlError && exception.getCause().getMessage().contains("Failed to bind")) { } 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 + "?"; reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?";
} }