Merge pull request #1935 from freimair/externalTor

Bisq can now use an external Tor service
This commit is contained in:
Manfred Karrer 2018-11-17 14:02:11 -05:00 committed by GitHub
commit e37813c658
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 366 additions and 80 deletions

View File

@ -125,7 +125,10 @@ configure(project(':common')) {
configure(project(':p2p')) {
dependencies {
compile project(':common')
compile('com.github.JesusMcCloud.netlayer:tor.native:0.4.7.1.1') {
compile('com.github.JesusMcCloud.netlayer:tor.native:0.6') {
exclude(module: 'slf4j-api')
}
compile('com.github.JesusMcCloud.netlayer:tor.external:0.6') {
exclude(module: 'slf4j-api')
}
compile('org.apache.httpcomponents:httpclient:4.5.3') {

View File

@ -193,9 +193,10 @@ public class BisqEnvironment extends StandardEnvironment {
protected final String btcNodes, seedNodes, ignoreDevMsg, useDevPrivilegeKeys, useDevMode, useTorForBtc, rpcUser, rpcPassword,
rpcPort, rpcBlockNotificationPort, dumpBlockchainData, fullDaoNode,
myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress,
torRcFile, torRcOptions,
torRcFile, torRcOptions, externalTorControlPort, externalTorPassword, externalTorCookieFile,
socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight, referralId, daoActivated;
protected final boolean externalTorUseSafeCookieAuthentication;
public BisqEnvironment(OptionSet options) {
this(new JOptCommandLinePropertySource(BISQ_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(
@ -274,6 +275,18 @@ public class BisqEnvironment extends StandardEnvironment {
torRcOptions = commandLineProperties.containsProperty(NetworkOptionKeys.TORRC_OPTIONS) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.TORRC_OPTIONS) :
"";
externalTorControlPort = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT) :
"";
externalTorPassword = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD) :
"";
externalTorCookieFile = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE) :
"";
externalTorUseSafeCookieAuthentication = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE) ?
true :
false;
//RpcOptionKeys
rpcUser = commandLineProperties.containsProperty(DaoOptionKeys.RPC_USER) ?
@ -444,6 +457,11 @@ public class BisqEnvironment extends StandardEnvironment {
setProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS, socks5ProxyHttpAddress);
setProperty(NetworkOptionKeys.TORRC_FILE, torRcFile);
setProperty(NetworkOptionKeys.TORRC_OPTIONS, torRcOptions);
setProperty(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT, externalTorControlPort);
setProperty(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD, externalTorPassword);
setProperty(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE, externalTorCookieFile);
if (externalTorUseSafeCookieAuthentication)
setProperty(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE, "true");
setProperty(AppOptionKeys.APP_DATA_DIR_KEY, appDataDir);
setProperty(AppOptionKeys.DESKTOP_WITH_HTTP_API, desktopWithHttpApi);

View File

@ -363,6 +363,24 @@ public abstract class BisqExecutable implements GracefulShutDownHandler {
description("A list of torrc-entries to amend to Bisqs torrc. Note that torrc-entries, which are critical to Bisqs flawless operation, cannot be overwritten. [torrc options line, torrc option, ...]", ""))
.withRequiredArg()
.withValuesConvertedBy(RegexMatcher.regex("^([^\\s,]+\\s[^,]+,?\\s*)+$"));
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT,
description("The control port of an already running Tor service to be used by Bisq [port].", ""))
.availableUnless(NetworkOptionKeys.TORRC_FILE, NetworkOptionKeys.TORRC_OPTIONS)
.withRequiredArg()
.ofType(int.class);
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD,
description("The password for controlling the already running Tor service.", ""))
.availableIf(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT)
.withRequiredArg();
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE,
description("The cookie file for authenticating against the already running Tor service. Use in conjunction with --" + NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE, ""))
.availableIf(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT)
.availableUnless(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD)
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING, PathProperties.READABLE));
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE,
description("Use the SafeCookie method when authenticating to the already running Tor service.", ""))
.availableIf(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE);
//AppOptionKeys
parser.accepts(AppOptionKeys.USER_DATA_DIR_KEY,

View File

@ -20,7 +20,8 @@ dependencyVerification {
'de.jensd:fontawesomefx-commons:5539bb3335ecb822dbf928546f57766eeb9f1516cc1417a064b5709629612149',
'com.googlecode.jcsv:jcsv:73ca7d715e90c8d2c2635cc284543b038245a34f70790660ed590e157b8714a2',
'com.github.sarxos:webcam-capture:d960b7ea8ec3ddf2df0725ef214c3fccc9699ea7772df37f544e1f8e4fd665f6',
'com.github.JesusMcCloud.netlayer:tor.native:0ad92f93c509a200a61cedbe0010d014f35ab57bcf131a4e268e1914e66be2e0',
'com.github.JesusMcCloud.netlayer:tor.native:f1bf0096f9eb6020645a65d91aa530d15aef97e69cc5a79d7b2405421f74700a',
'com.github.JesusMcCloud.netlayer:tor.external:cfba681398c191a1906d6d023a3be28a8fa9b1f4eee52e966daf7b1ae630414f',
'org.apache.httpcomponents:httpclient:db3d1b6c2d6a5e5ad47577ad61854e2f0e0936199b8e05eb541ed52349263135',
'net.sf.jopt-simple:jopt-simple:6f45c00908265947c39221035250024f2caec9a15c1c8cf553ebeecee289f342',
'org.fxmisc.easybind:easybind:666af296dda6de68751668a62661571b5238ac6f1c07c8a204fc6f902b222aaf',
@ -37,11 +38,11 @@ dependencyVerification {
'com.google.code.findbugs:jsr305:c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd',
'com.google.guava:guava:36a666e3b71ae7f0f0dca23654b67e086e6c93d192f60ba5dfd5519db6c288c8',
'com.google.inject:guice:9b9df27a5b8c7864112b4137fd92b36c3f1395bfe57be42fedf2f520ead1a93e',
'com.github.JesusMcCloud.netlayer:tor:4a6a6102331c35e7ad2a574cf81ddab89fc1256305805e82c5af1f542f336629',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:b306e0e6735841e31e320bf3260c71d60fc35057cfa87895f23251ee260a64a8',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:169ee5879cba8444499243ceea5e6a2cb6ecea5424211cc819f0704501154b35',
'com.github.JesusMcCloud.netlayer:tor:ac8465b7dda30ea920ec31a6bde42df7e88bee0282e805ce2797628938e3cf0b',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:193ab7813e4d249f2ea4fc1b968fea8c2126bcbeeb5d6127050ce1b93dbaa7c2',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:877b59bbe466b24a88275a71fd06cd97359d2085420f6f1ac1d766afa8116001',
'io.github.microutils:kotlin-logging:4992504fd3c6ecdf9ed10874b9508e758bb908af9e9d7af19a61e9afb6b7e27a',
'org.jetbrains.kotlin:kotlin-stdlib:f0595b9ed88ddc6fd66bddf68c56c6f2f6c4b17faa51e43e478acad32b05303e',
'org.jetbrains.kotlin:kotlin-stdlib:4ff0fcb97f4983b4aaba12668c24ad21b08460915db1b021d8f1d8bee687f21c',
'org.jetbrains:annotations:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.bouncycastle:bcpg-jdk15on:de3355b821fc81dd32e1f3f560d5b3eca1c678fd2400011d0bfc69fb91bcde85',
'commons-io:commons-io:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
@ -63,14 +64,13 @@ dependencyVerification {
'com.lambdaworks:scrypt:9a82d218099fb14c10c0e86e7eefeebd8c104de920acdc47b8b4b7a686fb73b4',
'com.google.zxing:core:11aae8fd974ab25faa8208be50468eb12349cd239e93e7c797377fa13e381729',
'com.cedricwalter:tor-binary-geoip:fbd7656a262607e5a73016e048d5270cbabcd4639a1795b4b4e762df8877429d',
'com.github.JesusMcCloud:jtorctl:c6ef92e46074d8d26db718ce0fe4b64b8cf7b934b7377d164c5d613b4cd7b847',
'org.apache.commons:commons-compress:a778bbd659722889245fc52a0ec2873fbbb89ec661bc1ad3dc043c0757c784c4',
'com.github.JesusMcCloud:jtorctl:ba71601cbe50474ccc39a17bc6f7880c1412d8d19b94d37aee69ea2917f72046',
'org.apache.commons:commons-compress:5f2df1e467825e4cac5996d44890c4201c000b43c0b23cffc0782d28a0beb9b0',
'org.tukaani:xz:a594643d73cc01928cf6ca5ce100e094ea9d73af760a5d4fb6b75fa673ecec96',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
'net.jcip:jcip-annotations:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'org.bitcoinj:orchid:f836325cfa0466a011cb755c9b0fee6368487a2352eb45f4306ad9e4c18de080',
'com.squareup.okhttp:okhttp:b4c943138fcef2bcc9d2006b2250c4aabbedeafc5947ed7c0af7fd103ceb2707',
'org.objenesis:objenesis:5e168368fbc250af3c79aa5fef0c3467a2d64e5a7bd74005f25d8399aeb0708d',
'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266',
]
}

View File

@ -31,4 +31,8 @@ public class NetworkOptionKeys {
public static final String SOCKS_5_PROXY_HTTP_ADDRESS = "socks5ProxyHttpAddress";
public static final String TORRC_OPTIONS = "torrcOptions";
public static final String TORRC_FILE = "torrcFile";
public static final String EXTERNAL_TOR_CONTROL_PORT = "torControlPort";
public static final String EXTERNAL_TOR_PASSWORD = "torControlPassword";
public static final String EXTERNAL_TOR_COOKIE_FILE = "torControlCookieFile";
public static final String EXTERNAL_TOR_USE_SAFECOOKIE = "torControlUseSafeCookieAuth";
}

View File

@ -21,6 +21,8 @@ import bisq.network.NetworkOptionKeys;
import bisq.network.p2p.network.BridgeAddressProvider;
import bisq.network.p2p.network.LocalhostNetworkNode;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.network.NewTor;
import bisq.network.p2p.network.RunningTor;
import bisq.network.p2p.network.TorNetworkNode;
import bisq.common.proto.network.NetworkProtoResolver;
@ -44,10 +46,17 @@ public class NetworkNodeProvider implements Provider<NetworkNode> {
@Named(NetworkOptionKeys.PORT_KEY) int port,
@Named(NetworkOptionKeys.TOR_DIR) File torDir,
@Named(NetworkOptionKeys.TORRC_FILE) String torrcFile,
@Named(NetworkOptionKeys.TORRC_OPTIONS) String torrcOptions) {
@Named(NetworkOptionKeys.TORRC_OPTIONS) String torrcOptions,
@Named(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT) String controlPort,
@Named(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD) String password,
@Named(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE) String cookieFile,
@Named(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE) boolean useSafeCookieAuthentication ) {
networkNode = useLocalhostForP2P ?
new LocalhostNetworkNode(address, port, networkProtoResolver) :
new TorNetworkNode(port, torDir, networkProtoResolver, bridgeAddressProvider, torrcFile, torrcOptions);
new TorNetworkNode(port, torDir, networkProtoResolver,
!controlPort.isEmpty() ?
new RunningTor(torDir, Integer.parseInt(controlPort), password, cookieFile, useSafeCookieAuthentication) :
new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider.getBridgeAddresses()));
}
@Override

View File

@ -90,5 +90,9 @@ public class P2PModule extends AppModule {
bindConstant().annotatedWith(named(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS)).to(environment.getRequiredProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS));
bindConstant().annotatedWith(named(NetworkOptionKeys.TORRC_FILE)).to(environment.getRequiredProperty(NetworkOptionKeys.TORRC_FILE));
bindConstant().annotatedWith(named(NetworkOptionKeys.TORRC_OPTIONS)).to(environment.getRequiredProperty(NetworkOptionKeys.TORRC_OPTIONS));
bindConstant().annotatedWith(named(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT)).to(environment.getRequiredProperty(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT));
bindConstant().annotatedWith(named(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD)).to(environment.getRequiredProperty(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD));
bindConstant().annotatedWith(named(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE)).to(environment.getRequiredProperty(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE));
bindConstant().annotatedWith(named(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE)).to(environment.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE) ? true : false);
}
}

View File

@ -0,0 +1,121 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.network.p2p.network;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import org.berndpruenster.netlayer.tor.Torrc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class creates a brand new instance of the Tor onion router.
*
* When asked, the class checks, whether command line parameters such as
* --torrcFile and --torrcOptions are set and if so, takes these settings into
* account. Then, a fresh set of Tor binaries is installed and Tor is launched.
* Finally, a {@link Tor} instance is returned for further use.
*
* @author Florian Reimair
*
*/
public class NewTor extends TorMode {
private static final Logger log = LoggerFactory.getLogger(NewTor.class);
private final String torrcFile;
private final String torrcOptions;
private final Collection<String> bridgeEntries;
private final File torWorkikngDirectory;
public NewTor(File torWorkingDirectory, String torrcFile, String torrcOptions, Collection<String> bridgeEntries) {
this.torrcFile = torrcFile;
this.torrcOptions = torrcOptions;
this.bridgeEntries = bridgeEntries;
this.torWorkikngDirectory = torWorkingDirectory;
}
@Override
public Tor getTor() throws IOException, TorCtlException {
long ts1 = new Date().getTime();
if (bridgeEntries != null)
log.info("Using bridges: {}", bridgeEntries.stream().collect(Collectors.joining(",")));
Torrc override = null;
// check if the user wants to provide his own torrc file
if (!"".equals(torrcFile)) {
try {
override = new Torrc(new FileInputStream(new File(torrcFile)));
} catch (IOException e) {
log.error("custom torrc file not found ('{}'). Proceeding with defaults.", torrcFile);
}
}
// check if the user wants to temporarily add to the default torrc file
LinkedHashMap<String, String> torrcOptionsMap = new LinkedHashMap<>();
if (!"".equals(torrcOptions)) {
Arrays.asList(torrcOptions.split(",")).forEach(line -> {
line = line.trim();
if (line.matches("^[^\\s]+\\s.+")) {
String[] tmp = line.split("\\s", 2);
torrcOptionsMap.put(tmp[0].trim(), tmp[1].trim());
} else {
log.error("custom torrc override parse error ('{}'). Proceeding without custom overrides.", line);
torrcOptionsMap.clear();
}
});
}
// assemble final override options
if (!torrcOptionsMap.isEmpty())
// check for custom torrcFile
if (override != null)
// and merge the contents
override = new Torrc(override.getInputStream$tor_native(), torrcOptionsMap);
else
override = new Torrc(torrcOptionsMap);
log.info("Starting tor");
NativeTor result = new NativeTor(torWorkikngDirectory, bridgeEntries, override);
log.info(
"\n################################################################\n"
+ "Tor started after {} ms. Start publishing hidden service.\n"
+ "################################################################",
(new Date().getTime() - ts1)); // takes usually a few seconds
return result;
}
@Override
public String getHiddenServiceDirectory() {
return "";
}
}

View File

@ -0,0 +1,87 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.network.p2p.network;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import org.berndpruenster.netlayer.tor.ExternalTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class creates a brand new instance of the Tor onion router.
*
* When asked, the class checks for the authentication method selected and
* connects to the given control port. Finally, a {@link Tor} instance is
* returned for further use.
*
* @author Florian Reimair
*
*/
public class RunningTor extends TorMode {
private static final Logger log = LoggerFactory.getLogger(RunningTor.class);
private final int controlPort;
private final String password;
private final String torDir;
private final File cookieFile;
private final boolean useSafeCookieAuthentication;
public RunningTor(final File torDir, final int controlPort, final String password, final String cookieFile,
final boolean useSafeCookieAuthentication) {
this.torDir = torDir.getAbsolutePath();
this.controlPort = controlPort;
this.password = password;
this.cookieFile = new File(cookieFile);
this.useSafeCookieAuthentication = useSafeCookieAuthentication;
}
@Override
public Tor getTor() throws IOException, TorCtlException {
long ts1 = new Date().getTime();
log.info("Connecting to running tor");
Tor result;
if (!password.isEmpty())
result = new ExternalTor(controlPort, password);
else if (cookieFile.exists())
result = new ExternalTor(controlPort, cookieFile, useSafeCookieAuthentication);
else
result = new ExternalTor(controlPort);
log.info(
"\n################################################################\n"
+ "Tor started after {} ms. Start publishing hidden service.\n"
+ "################################################################",
(new Date().getTime() - ts1)); // takes usually a few seconds
return result;
}
@Override
public String getHiddenServiceDirectory() {
return torDir + File.separator + "externalTorHiddenService";
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.network.p2p.network;
import java.io.File;
import java.io.IOException;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
/**
* Holds information on how tor should be created and delivers a respective
* {@link Tor} object when asked.
*
* @author Florian Reimair
*
*/
public abstract class TorMode {
/**
* Returns a fresh {@link Tor} object.
*
* @param torDir points to the place, where we will persist private key and
* address data
* @return a fresh instance of {@link Tor}
* @throws IOException
* @throws TorCtlException
*/
public abstract Tor getTor() throws IOException, TorCtlException;
/**
* {@link NativeTor}'s inner workings prepend its Tor installation path and some
* other stuff to the hiddenServiceDir, thus, selecting nothing (i.e.
* <code>""</code>) as a hidden service directory is fine. {@link ExternalTor},
* however, does not have a Tor installation path and thus, takes the hidden
* service path literally. Hence, we set
* <code>"torDir/ephemeralHiddenService"</code> as the hidden service directory.
*
* @return <code>""</code> in {@link NewTor} Mode,
* <code>"torDir/ephemeralHiddenService"</code> in {@link RunningTor}
* mode
*/
public abstract String getHiddenServiceDirectory();
}

View File

@ -28,11 +28,9 @@ import bisq.common.storage.FileUtil;
import bisq.common.util.Utilities;
import org.berndpruenster.netlayer.tor.HiddenServiceSocket;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import org.berndpruenster.netlayer.tor.TorSocket;
import org.berndpruenster.netlayer.tor.Torrc;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
@ -52,11 +50,8 @@ import java.net.Socket;
import java.nio.file.Paths;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -80,27 +75,22 @@ public class TorNetworkNode extends NetworkNode {
private HiddenServiceSocket hiddenServiceSocket;
private final File torDir;
private final BridgeAddressProvider bridgeAddressProvider;
private Timer shutDownTimeoutTimer;
private int restartCounter;
@SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> allShutDown;
private Tor tor;
private String torrcFile = "";
private String torrcOptions = "";
private TorMode torMode;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TorNetworkNode(int servicePort, File torDir, NetworkProtoResolver networkProtoResolver, BridgeAddressProvider bridgeAddressProvider, String torrcFile, String torrcOptions) {
public TorNetworkNode(int servicePort, File torDir, NetworkProtoResolver networkProtoResolver, TorMode torMode) {
super(servicePort, networkProtoResolver);
this.torDir = torDir;
this.bridgeAddressProvider = bridgeAddressProvider;
this.torrcFile = torrcFile;
this.torrcOptions = torrcOptions;
this.torMode = torMode;
}
@ -119,7 +109,7 @@ public class TorNetworkNode extends NetworkNode {
createExecutorService();
// Create the tor node (takes about 6 sec.)
createTorAndHiddenService(torDir, Utils.findFreeSystemPort(), servicePort, bridgeAddressProvider.getBridgeAddresses());
createTorAndHiddenService(Utils.findFreeSystemPort(), servicePort);
}
@Override
@ -244,62 +234,18 @@ public class TorNetworkNode extends NetworkNode {
// create tor
///////////////////////////////////////////////////////////////////////////////////////////
private void createTorAndHiddenService(File torDir, int localPort, int servicePort, @Nullable List<String> bridgeEntries) {
private void createTorAndHiddenService(int localPort, int servicePort) {
Log.traceCall();
if (bridgeEntries != null)
log.info("Using bridges: {}", bridgeEntries.stream().collect(Collectors.joining(",")));
ListenableFuture<Void> future = executorService.submit(() -> {
try {
long ts1 = new Date().getTime();
Torrc override = null;
// check if the user wants to provide his own torrc file
if(!"".equals(torrcFile)) {
try {
override = new Torrc(new FileInputStream(new File(torrcFile)));
} catch(IOException e) {
log.error("custom torrc file not found ('{}'). Proceeding with defaults.", torrcFile);
}
}
// check if the user wants to temporarily add to the default torrc file
LinkedHashMap<String, String> torrcOptionsMap = new LinkedHashMap<>();
if(!"".equals(torrcOptions)) {
Arrays.asList(torrcOptions.split(",")).forEach(line -> {
line = line.trim();
if(line.matches("^[^\\s]+\\s.+")) {
String[] tmp = line.split("\\s", 2);
torrcOptionsMap.put(tmp[0].trim(), tmp[1].trim());
}
else {
log.error("custom torrc override parse error ('{}'). Proceeding without custom overrides.", line);
torrcOptionsMap.clear();
}
});
}
// assemble final override options
if(!torrcOptionsMap.isEmpty())
// check for custom torrcFile
if(override != null)
// and merge the contents
override = new Torrc(override.getInputStream$tor(), torrcOptionsMap);
else
override = new Torrc(torrcOptionsMap);
log.info("Starting tor");
Tor.setDefault(new NativeTor(torDir, bridgeEntries, override));
log.info("\n################################################################\n" +
"Tor started after {} ms. Start publishing hidden service.\n" +
"################################################################",
(new Date().getTime() - ts1)); // takes usually a few seconds
// get tor
Tor.setDefault(torMode.getTor());
UserThread.execute(() -> setupListeners.stream().forEach(SetupListener::onTorNodeReady));
// start hidden service
long ts2 = new Date().getTime();
hiddenServiceSocket = new HiddenServiceSocket(localPort, "", servicePort);
hiddenServiceSocket = new HiddenServiceSocket(localPort, torMode.getHiddenServiceDirectory(), servicePort);
hiddenServiceSocket.addReadyListener(socket -> {
try {
log.info("\n################################################################\n" +
@ -330,6 +276,14 @@ public class TorNetworkNode extends NetworkNode {
} catch (TorCtlException e) {
log.error("Tor node creation failed: " + (e.getCause() != null ? e.getCause().toString() : e.toString()));
restartTor(e.getMessage());
} catch (IOException e) {
log.error("Could not connect to running Tor: "
+ e.getMessage());
// Seems a bit harsh, but since we cannot connect to Tor, we cannot do nothing
// furthermore, we have no hidden services started yet, so there is no graceful
// shutdown needed either
System.exit(1);
} catch (Throwable ignore) {
}

View File

@ -26,7 +26,7 @@ import com.google.common.util.concurrent.SettableFuture;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import org.slf4j.Logger;
@ -53,7 +53,9 @@ public class TorNetworkNodeTest {
public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException {
latch = new CountDownLatch(1);
int port = 9001;
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port), TestUtils.getNetworkProtoResolver(), null, "", "");
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port),
TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
node1.start(new SetupListener() {
@Override
public void onTorNodeReady() {
@ -79,7 +81,9 @@ public class TorNetworkNodeTest {
latch = new CountDownLatch(1);
int port2 = 9002;
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port2), TestUtils.getNetworkProtoResolver(), null, "", "");
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port2),
TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
node2.start(new SetupListener() {
@Override
public void onTorNodeReady() {
@ -136,7 +140,9 @@ public class TorNetworkNodeTest {
public void testTorNodeAfterBothReady() throws InterruptedException, IOException {
latch = new CountDownLatch(2);
int port = 9001;
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port), TestUtils.getNetworkProtoResolver(), null, "", "");
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port),
TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
node1.start(new SetupListener() {
@Override
public void onTorNodeReady() {
@ -161,7 +167,9 @@ public class TorNetworkNodeTest {
});
int port2 = 9002;
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port), TestUtils.getNetworkProtoResolver(), null, "", "");
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port),
TestUtils.getNetworkProtoResolver(),
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
node2.start(new SetupListener() {
@Override
public void onTorNodeReady() {