mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Merge pull request #730 from dan-da/hiddenservices_pr
DNS lookups over Tor and connecting bitcoinj to .onion addresses.
This commit is contained in:
commit
a1e7ccc1bf
@ -1,175 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2011 Micheal Swiggs
|
|
||||||
* <p>
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* <p>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* <p>
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package io.bitsquare.btc;
|
|
||||||
|
|
||||||
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
|
||||||
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
|
||||||
import org.bitcoinj.net.discovery.PeerDiscovery;
|
|
||||||
import org.bitcoinj.net.discovery.PeerDiscoveryException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SeedPeersSocks5Dns resolves peers via Proxy (Socks5) remote DNS.
|
|
||||||
*/
|
|
||||||
public class SeedPeersSocks5Dns implements PeerDiscovery {
|
|
||||||
private Socks5Proxy proxy;
|
|
||||||
private NetworkParameters params;
|
|
||||||
private InetSocketAddress[] seedAddrs;
|
|
||||||
private InetSocketAddress[] seedAddrsIP;
|
|
||||||
private int pnseedIndex;
|
|
||||||
|
|
||||||
private final InetSocketAddress[] seedAddrsResolved;
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SeedPeersSocks5Dns.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supports finding peers by hostname over a socks5 proxy.
|
|
||||||
*
|
|
||||||
* @param Socks5Proxy proxy the socks5 proxy to connect over.
|
|
||||||
* @param NetworkParameters param to be used for seed and port information.
|
|
||||||
*/
|
|
||||||
public SeedPeersSocks5Dns(Socks5Proxy proxy, NetworkParameters params) {
|
|
||||||
|
|
||||||
this.proxy = proxy;
|
|
||||||
this.params = params;
|
|
||||||
this.seedAddrs = convertAddrsString(params.getDnsSeeds(), params.getPort());
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
// This is an example of how .onion servers could be used. Unfortunately there is presently no way
|
|
||||||
// to hand the onion address (or a connected socket) back to bitcoinj without it crashing in PeerAddress.
|
|
||||||
// note: the onion addresses should be added into bitcoinj NetworkParameters classes, eg for mainnet, testnet
|
|
||||||
// not here!
|
|
||||||
this.seedAddrs = new InetSocketAddress[]{InetSocketAddress.createUnresolved("cajrifqkvalh2ooa.onion", 8333),
|
|
||||||
InetSocketAddress.createUnresolved("bk7yp6epnmcllq72.onion", 8333)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
seedAddrsResolved = new InetSocketAddress[seedAddrs.length];
|
|
||||||
for (int idx = seedAddrs.length; idx < seedAddrsResolved.length; idx++) {
|
|
||||||
seedAddrsResolved[idx] = seedAddrsIP[idx - seedAddrs.length];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Acts as an iterator, returning the address of each node in the list sequentially.
|
|
||||||
* Once all the list has been iterated, null will be returned for each subsequent query.
|
|
||||||
*
|
|
||||||
* @return InetSocketAddress - The address/port of the next node.
|
|
||||||
* @throws PeerDiscoveryException
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public InetSocketAddress getPeer() throws PeerDiscoveryException {
|
|
||||||
try {
|
|
||||||
return nextPeer();
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new PeerDiscoveryException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* worker for getPeer()
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private InetSocketAddress nextPeer() throws UnknownHostException, PeerDiscoveryException {
|
|
||||||
if (seedAddrs == null || seedAddrs.length == 0) {
|
|
||||||
throw new PeerDiscoveryException("No IP address seeds configured; unable to find any peers");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pnseedIndex >= seedAddrsResolved.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (seedAddrsResolved[pnseedIndex] == null) {
|
|
||||||
seedAddrsResolved[pnseedIndex] = lookup(proxy, seedAddrs[pnseedIndex]);
|
|
||||||
}
|
|
||||||
log.error("SeedPeersSocks5Dns::nextPeer: " + seedAddrsResolved[pnseedIndex]);
|
|
||||||
|
|
||||||
return seedAddrsResolved[pnseedIndex++];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array containing all the Bitcoin nodes within the list.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
|
|
||||||
try {
|
|
||||||
return allPeers();
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new PeerDiscoveryException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns all seed peers, performs hostname lookups if necessary.
|
|
||||||
*/
|
|
||||||
private InetSocketAddress[] allPeers() throws UnknownHostException {
|
|
||||||
for (int i = 0; i < seedAddrsResolved.length; ++i) {
|
|
||||||
if (seedAddrsResolved[i] == null) {
|
|
||||||
seedAddrsResolved[i] = lookup(proxy, seedAddrs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return seedAddrsResolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a hostname via remote DNS over socks5 proxy.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static InetSocketAddress lookup(Socks5Proxy proxy, InetSocketAddress addr) {
|
|
||||||
if (!addr.isUnresolved()) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
SocksSocket proxySocket = new SocksSocket(proxy, addr.getHostString(), addr.getPort());
|
|
||||||
InetAddress addrResolved = proxySocket.getInetAddress();
|
|
||||||
proxySocket.close();
|
|
||||||
if (addrResolved != null) {
|
|
||||||
log.debug("Resolved " + addr.getHostString() + " to " + addrResolved.getHostAddress());
|
|
||||||
return new InetSocketAddress(addrResolved, addr.getPort());
|
|
||||||
} else {
|
|
||||||
// note: .onion nodes fall in here when proxy is Tor. But they have no IP address.
|
|
||||||
// Unfortunately bitcoinj crashes in PeerAddress if it finds an unresolved address.
|
|
||||||
log.error("Connected to " + addr.getHostString() + ". But did not resolve to address.");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Error resolving " + addr.getHostString() + ". Exception:\n" + e.toString());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an array of hostnames to array of unresolved InetSocketAddress
|
|
||||||
*/
|
|
||||||
private InetSocketAddress[] convertAddrsString(String[] addrs, int port) {
|
|
||||||
InetSocketAddress[] list = new InetSocketAddress[addrs.length];
|
|
||||||
for (int i = 0; i < addrs.length; i++) {
|
|
||||||
list[i] = InetSocketAddress.createUnresolved(addrs[i], port);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,7 +32,9 @@ import io.bitsquare.common.UserThread;
|
|||||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||||
import io.bitsquare.common.handlers.ExceptionHandler;
|
import io.bitsquare.common.handlers.ExceptionHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
|
import io.bitsquare.network.DnsLookupTor;
|
||||||
import io.bitsquare.network.Socks5ProxyProvider;
|
import io.bitsquare.network.Socks5ProxyProvider;
|
||||||
|
import io.bitsquare.network.Socks5MultiDiscovery;
|
||||||
import io.bitsquare.storage.FileUtil;
|
import io.bitsquare.storage.FileUtil;
|
||||||
import io.bitsquare.storage.Storage;
|
import io.bitsquare.storage.Storage;
|
||||||
import io.bitsquare.user.Preferences;
|
import io.bitsquare.user.Preferences;
|
||||||
@ -266,9 +268,7 @@ public class WalletService {
|
|||||||
// Pass custom seed nodes if set in options
|
// Pass custom seed nodes if set in options
|
||||||
if (!btcNodes.isEmpty()) {
|
if (!btcNodes.isEmpty()) {
|
||||||
|
|
||||||
// TODO: this parsing should be more robust,
|
String[] nodes = parseCSV(btcNodes);
|
||||||
// give validation error if needed.
|
|
||||||
String[] nodes = btcNodes.replace(", ", ",").split(",");
|
|
||||||
List<PeerAddress> peerAddressList = new ArrayList<>();
|
List<PeerAddress> peerAddressList = new ArrayList<>();
|
||||||
for (String node : nodes) {
|
for (String node : nodes) {
|
||||||
String[] parts = node.split(":");
|
String[] parts = node.split(":");
|
||||||
@ -279,19 +279,24 @@ public class WalletService {
|
|||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
// note: this will cause a DNS request if hostname used.
|
// note: this will cause a DNS request if hostname used.
|
||||||
// note: DNS requests are routed over socks5 proxy, if used.
|
// note: DNS requests are routed over socks5 proxy, if used.
|
||||||
// fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
|
// note: .onion hostnames will be unresolved.
|
||||||
InetSocketAddress addr;
|
InetSocketAddress addr;
|
||||||
if (socks5Proxy != null) {
|
if (socks5Proxy != null) {
|
||||||
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
|
try {
|
||||||
// proxy remote DNS request happens here.
|
// proxy remote DNS request happens here. blocking.
|
||||||
addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
|
addr = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, parts[0]), Integer.parseInt(parts[1]));
|
||||||
|
}
|
||||||
|
catch(Exception e) {
|
||||||
|
log.warn("Dns lookup failed for host: {}", parts[0]);
|
||||||
|
addr = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// DNS request happens here. if it fails, addr.isUnresolved() == true.
|
// DNS request happens here. if it fails, addr.isUnresolved() == true.
|
||||||
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
|
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
|
||||||
}
|
}
|
||||||
// note: isUnresolved check should be removed once we fix PeerAddress
|
if (addr != null && !addr.isUnresolved()) {
|
||||||
if (addr != null && !addr.isUnresolved())
|
|
||||||
peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
|
peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (peerAddressList.size() > 0) {
|
if (peerAddressList.size() > 0) {
|
||||||
@ -343,9 +348,10 @@ public class WalletService {
|
|||||||
// could become outdated, so it is important that the user be able to
|
// could become outdated, so it is important that the user be able to
|
||||||
// disable it, but should be made aware of the reduced privacy.
|
// disable it, but should be made aware of the reduced privacy.
|
||||||
if (socks5Proxy != null && !usePeerNodes) {
|
if (socks5Proxy != null && !usePeerNodes) {
|
||||||
// SeedPeersSocks5Dns should replace SeedPeers once working reliably.
|
|
||||||
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
|
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
|
||||||
walletAppKit.setDiscovery(new SeedPeers(params));
|
// TODO: the discovery mode should come from command-line args, and default to ALL if not present.
|
||||||
|
int discoveryMode = Socks5MultiDiscovery.SOCKS5_DISCOVER_ALL;
|
||||||
|
walletAppKit.setDiscovery(new Socks5MultiDiscovery(socks5Proxy, params, discoveryMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
walletAppKit.setDownloadListener(downloadListener)
|
walletAppKit.setDownloadListener(downloadListener)
|
||||||
@ -1130,6 +1136,19 @@ public class WalletService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parses a comma separated string into String array.
|
||||||
|
*
|
||||||
|
* all spaces are stripped from the string, so this
|
||||||
|
* method is not suitable for CSV with values that contain spaces.
|
||||||
|
*/
|
||||||
|
private String[] parseCSV(String buf) {
|
||||||
|
// todo: improve parsing to handle multiple spaces, etc.
|
||||||
|
return buf.replace(" ", "").split(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Inner classes
|
// Inner classes
|
||||||
|
@ -206,7 +206,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
|
|||||||
peerList.stream().forEach(e -> {
|
peerList.stream().forEach(e -> {
|
||||||
if (bitcoinPeersTextArea.getText().length() > 0)
|
if (bitcoinPeersTextArea.getText().length() > 0)
|
||||||
bitcoinPeersTextArea.appendText("\n");
|
bitcoinPeersTextArea.appendText("\n");
|
||||||
bitcoinPeersTextArea.appendText(e.getAddress().getSocketAddress().toString());
|
bitcoinPeersTextArea.appendText(e.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
143
network/src/main/java/io/bitsquare/network/DnsLookupTor.java
Normal file
143
network/src/main/java/io/bitsquare/network/DnsLookupTor.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Bitsquare.
|
||||||
|
*
|
||||||
|
* Bitsquare 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.
|
||||||
|
*
|
||||||
|
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bitsquare.network;
|
||||||
|
|
||||||
|
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs DNS lookup over Socks5 proxy that implements the RESOLVE extension.
|
||||||
|
* At this time, Tor is only known Socks5 proxy that supports it.
|
||||||
|
*
|
||||||
|
* Adapted from https://github.com/btcsuite/btcd/blob/master/connmgr/tor.go
|
||||||
|
*/
|
||||||
|
public class DnsLookupTor {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DnsLookupTor.class);
|
||||||
|
private static final Map<Byte, String> torStatusErrors = DnsLookupTor.createMap();
|
||||||
|
|
||||||
|
private static Map<Byte, String> createMap() {
|
||||||
|
HashMap<Byte, String> map = new HashMap<Byte, String>();
|
||||||
|
map.put(Byte.valueOf(b('\u0000')), "tor succeeded");
|
||||||
|
map.put(Byte.valueOf(b('\u0001')), "tor general error");
|
||||||
|
map.put(Byte.valueOf(b('\u0002')), "tor not allowed");
|
||||||
|
map.put(Byte.valueOf(b('\u0003')), "tor network is unreachable");
|
||||||
|
map.put(Byte.valueOf(b('\u0004')), "tor host is unreachable");
|
||||||
|
map.put(Byte.valueOf(b('\u0005')), "tor connection refused");
|
||||||
|
map.put(Byte.valueOf(b('\u0006')), "tor TTL expired");
|
||||||
|
map.put(Byte.valueOf(b('\u0007')), "tor command not supported");
|
||||||
|
map.put(Byte.valueOf(b('\u0008')), "tor address type not supported");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs DNS lookup and returns a single InetAddress
|
||||||
|
*/
|
||||||
|
public static InetAddress lookup(Socks5Proxy proxy, String host) throws Exception {
|
||||||
|
Logger log = LoggerFactory.getLogger(DnsLookupTor.class);
|
||||||
|
try {
|
||||||
|
log.debug("Resolving {} over tor.", (Object)host);
|
||||||
|
return DnsLookupTor.doLookup(proxy, host);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.warn("Error resolving " + host + ". Exception:\n" + e.toString());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual lookup is performed here.
|
||||||
|
*/
|
||||||
|
private static InetAddress doLookup(Socks5Proxy proxy, String host) throws Exception {
|
||||||
|
|
||||||
|
// note: This is creating a new connection to our proxy, without any authentication.
|
||||||
|
// This works fine when connecting to bitsquare's internal Tor proxy, but
|
||||||
|
// would fail if user has configured an external proxy that requires auth.
|
||||||
|
// It would be much better to use the already connected proxy socket, but when I
|
||||||
|
// tried that I get weird errors and the lookup fails.
|
||||||
|
//
|
||||||
|
// So this is an area for future improvement.
|
||||||
|
Socket proxySocket = new Socket(proxy.getInetAddress(), proxy.getPort());
|
||||||
|
|
||||||
|
proxySocket.getOutputStream().write(new byte[]{b('\u0005'), b('\u0001'), b('\u0000')});
|
||||||
|
byte[] buf = new byte[2];
|
||||||
|
proxySocket.getInputStream().read(buf);
|
||||||
|
|
||||||
|
if (buf[0] != b('\u0005')) {
|
||||||
|
throw new Exception("Invalid Proxy Response");
|
||||||
|
}
|
||||||
|
if (buf[1] != b('\u0000')) {
|
||||||
|
throw new Exception("Unrecognized Tor Auth Method");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] hostBytes = host.getBytes();
|
||||||
|
buf = new byte[7 + hostBytes.length];
|
||||||
|
buf[0] = b('\u0005');
|
||||||
|
buf[1] = b('\u00f0');
|
||||||
|
buf[2] = b('\u0000');
|
||||||
|
buf[3] = b('\u0003');
|
||||||
|
buf[4] = (byte)hostBytes.length;
|
||||||
|
System.arraycopy(hostBytes, 0, buf, 5, hostBytes.length);
|
||||||
|
buf[5 + hostBytes.length] = 0;
|
||||||
|
|
||||||
|
proxySocket.getOutputStream().write(buf);
|
||||||
|
|
||||||
|
buf = new byte[4];
|
||||||
|
proxySocket.getInputStream().read(buf);
|
||||||
|
|
||||||
|
if (buf[0] != b('\u0005')) {
|
||||||
|
throw new Exception("Invalid Tor Proxy Response");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[1] != b('\u0000')) {
|
||||||
|
if (!torStatusErrors.containsKey(Byte.valueOf(buf[1]))) {
|
||||||
|
throw new Exception("Invalid Tor Proxy Response");
|
||||||
|
}
|
||||||
|
throw new Exception(torStatusErrors.get(Byte.valueOf(buf[1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[3] != b('\u0001')) {
|
||||||
|
throw new Exception(torStatusErrors.get(Byte.valueOf(b('\u0001'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = new byte[4];
|
||||||
|
int bytesRead = proxySocket.getInputStream().read(buf);
|
||||||
|
|
||||||
|
if (bytesRead != 4) {
|
||||||
|
throw new Exception("Invalid Tor Address Response");
|
||||||
|
}
|
||||||
|
|
||||||
|
InetAddress addr = InetAddress.getByAddress(buf);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* so we can have prettier code without a bunch of casts.
|
||||||
|
*/
|
||||||
|
private static byte b(char c) {
|
||||||
|
return (byte)c;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Bitsquare.
|
||||||
|
*
|
||||||
|
* Bitsquare 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.
|
||||||
|
*
|
||||||
|
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bitsquare.network;
|
||||||
|
|
||||||
|
import com.runjva.sourceforge.jsocks.protocol.*;
|
||||||
|
|
||||||
|
import org.bitcoinj.net.discovery.*;
|
||||||
|
import org.bitcoinj.core.*;
|
||||||
|
import org.bitcoinj.utils.*;
|
||||||
|
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Supports peer discovery through DNS over Socks5 proxy with RESOLVE DNS extension.</p>
|
||||||
|
*
|
||||||
|
* (As of this writing, only Tor is known to support the RESOLVE DNS extension.)
|
||||||
|
*
|
||||||
|
* <p>Failure to resolve individual host names will not cause an Exception to be thrown.
|
||||||
|
* However, if all hosts passed fail to resolve a PeerDiscoveryException will be thrown during getPeers().
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>DNS seeds do not attempt to enumerate every peer on the network. {@link DnsDiscovery#getPeers(long, java.util.concurrent.TimeUnit)}
|
||||||
|
* will return up to 30 random peers from the set of those returned within the timeout period. If you want more peers
|
||||||
|
* to connect to, you need to discover them via other means (like addr broadcasts).</p>
|
||||||
|
*/
|
||||||
|
public class Socks5DnsDiscovery extends MultiplexingDiscovery {
|
||||||
|
|
||||||
|
private Socks5Proxy proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports finding peers through DNS A records. Community run DNS entry points will be used.
|
||||||
|
*
|
||||||
|
* @param netParams Network parameters to be used for port information.
|
||||||
|
*/
|
||||||
|
public Socks5DnsDiscovery(Socks5Proxy proxy, NetworkParameters netParams) {
|
||||||
|
this(proxy, netParams.getDnsSeeds(), netParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports finding peers through DNS A records.
|
||||||
|
*
|
||||||
|
* @param dnsSeeds Host names to be examined for seed addresses.
|
||||||
|
* @param params Network parameters to be used for port information.
|
||||||
|
*/
|
||||||
|
public Socks5DnsDiscovery(Socks5Proxy proxy, String[] dnsSeeds, NetworkParameters params) {
|
||||||
|
super(params, buildDiscoveries(proxy, params, dnsSeeds));
|
||||||
|
this.proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PeerDiscovery> buildDiscoveries(Socks5Proxy proxy, NetworkParameters params, String[] seeds) {
|
||||||
|
List<PeerDiscovery> discoveries = new ArrayList<PeerDiscovery>(seeds.length);
|
||||||
|
for (String seed : seeds) {
|
||||||
|
if(seed == "dnsseed.bluematt.me") {
|
||||||
|
continue; // this dns is known to fail with tor-resolve because it returns too many addrs.
|
||||||
|
// we avoid adding it in order to prevent ugly log messages.
|
||||||
|
}
|
||||||
|
discoveries.add(new Socks5DnsSeedDiscovery(proxy, params, seed));
|
||||||
|
}
|
||||||
|
return discoveries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ExecutorService createExecutor() {
|
||||||
|
// Attempted workaround for reported bugs on Linux in which gethostbyname does not appear to be properly
|
||||||
|
// thread safe and can cause segfaults on some libc versions.
|
||||||
|
if (System.getProperty("os.name").toLowerCase().contains("linux")) {
|
||||||
|
return Executors.newSingleThreadExecutor(new ContextPropagatingThreadFactory("DNS seed lookups"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory("DNS seed lookups"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implements discovery from a single DNS host over Socks5 proxy with RESOLVE DNS extension. */
|
||||||
|
public static class Socks5DnsSeedDiscovery implements PeerDiscovery {
|
||||||
|
private final String hostname;
|
||||||
|
private final NetworkParameters params;
|
||||||
|
private final Socks5Proxy proxy;
|
||||||
|
|
||||||
|
public Socks5DnsSeedDiscovery(Socks5Proxy proxy, NetworkParameters params, String hostname) {
|
||||||
|
this.hostname = hostname;
|
||||||
|
this.params = params;
|
||||||
|
this.proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns peer addresses. The actual DNS lookup is performed here.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
|
||||||
|
try {
|
||||||
|
InetSocketAddress addr = new InetSocketAddress(DnsLookupTor.lookup(proxy, hostname), params.getPort());
|
||||||
|
return new InetSocketAddress[] {addr};
|
||||||
|
}
|
||||||
|
catch(Exception e) {
|
||||||
|
throw new PeerDiscoveryException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Bitsquare.
|
||||||
|
*
|
||||||
|
* Bitsquare 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.
|
||||||
|
*
|
||||||
|
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bitsquare.network;
|
||||||
|
|
||||||
|
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||||
|
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
||||||
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
|
import org.bitcoinj.net.discovery.PeerDiscovery;
|
||||||
|
import org.bitcoinj.net.discovery.PeerDiscoveryException;
|
||||||
|
import org.bitcoinj.net.discovery.SeedPeers;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements various types of discovery over Socks5,
|
||||||
|
* which can be enabled/disabled via constructor flag.
|
||||||
|
*/
|
||||||
|
public class Socks5MultiDiscovery implements PeerDiscovery {
|
||||||
|
|
||||||
|
public static final int SOCKS5_DISCOVER_ADDR = 0x0001;
|
||||||
|
public static final int SOCKS5_DISCOVER_DNS = 0x0010;
|
||||||
|
public static final int SOCKS5_DISCOVER_ONION = 0x0100;
|
||||||
|
public static final int SOCKS5_DISCOVER_ALL = 0x1111;
|
||||||
|
|
||||||
|
private ArrayList<PeerDiscovery> discoveryList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports finding peers by hostname over a socks5 proxy.
|
||||||
|
*
|
||||||
|
* @param Socks5Proxy proxy the socks5 proxy to connect over.
|
||||||
|
* @param NetworkParameters param to be used for seed and port information.
|
||||||
|
* @param mode specify discovery mode, OR'd together. one or more of:
|
||||||
|
* SOCKS5_DISCOVER_ADDR
|
||||||
|
* SOCKS5_DISCOVER_DNS
|
||||||
|
* SOCKS5_DISCOVER_ONION
|
||||||
|
* SOCKS5_DISCOVER_ALL
|
||||||
|
*/
|
||||||
|
public Socks5MultiDiscovery(Socks5Proxy proxy, NetworkParameters params, int mode) {
|
||||||
|
|
||||||
|
discoveryList = new ArrayList<PeerDiscovery>();
|
||||||
|
|
||||||
|
if( (mode & SOCKS5_DISCOVER_ONION) != 0) {
|
||||||
|
discoveryList.add(new Socks5SeedOnionDiscovery(proxy, params));
|
||||||
|
}
|
||||||
|
if( (mode & SOCKS5_DISCOVER_ADDR) != 0) {
|
||||||
|
// note: SeedPeers does not perform any network operations, so does not use proxy.
|
||||||
|
discoveryList.add(new SeedPeers(params));
|
||||||
|
}
|
||||||
|
if( (mode & SOCKS5_DISCOVER_DNS) != 0) {
|
||||||
|
discoveryList.add(new Socks5DnsDiscovery(proxy, params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array containing all the Bitcoin nodes that have been discovered.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
|
||||||
|
ArrayList<InetSocketAddress> list = new ArrayList<InetSocketAddress>();
|
||||||
|
|
||||||
|
for (PeerDiscovery discovery : discoveryList) {
|
||||||
|
list.addAll(Arrays.asList(discovery.getPeers(timeoutValue, timeoutUnit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.toArray(new InetSocketAddress[list.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Bitsquare.
|
||||||
|
*
|
||||||
|
* Bitsquare 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.
|
||||||
|
*
|
||||||
|
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bitsquare.network;
|
||||||
|
|
||||||
|
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||||
|
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
||||||
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
|
import org.bitcoinj.net.discovery.PeerDiscovery;
|
||||||
|
import org.bitcoinj.net.discovery.PeerDiscoveryException;
|
||||||
|
import org.bitcoinj.params.MainNetParams;
|
||||||
|
import org.bitcoinj.params.RegTestParams;
|
||||||
|
import org.bitcoinj.params.TestNet3Params;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socks5SeedOnionDiscovery provides a list of known Bitcoin .onion seeds.
|
||||||
|
* These are nodes running as hidden services on the Tor network.
|
||||||
|
*/
|
||||||
|
public class Socks5SeedOnionDiscovery implements PeerDiscovery {
|
||||||
|
private Socks5Proxy proxy;
|
||||||
|
private NetworkParameters params;
|
||||||
|
private InetSocketAddress[] seedAddrs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports finding peers by hostname over a socks5 proxy.
|
||||||
|
*
|
||||||
|
* @param Socks5Proxy proxy the socks5 proxy to connect over.
|
||||||
|
* @param NetworkParameters param to be used for seed and port information.
|
||||||
|
*/
|
||||||
|
public Socks5SeedOnionDiscovery(Socks5Proxy proxy, NetworkParameters params) {
|
||||||
|
|
||||||
|
this.proxy = proxy;
|
||||||
|
this.params = params;
|
||||||
|
|
||||||
|
// We do this because NetworkParameters does not contain any .onion
|
||||||
|
// seeds. Perhaps someday...
|
||||||
|
if(params == MainNetParams.get()) {
|
||||||
|
this.seedAddrs = convertAddrsString(mainNetSeeds(), params.getPort());
|
||||||
|
}
|
||||||
|
else if(params == TestNet3Params.get()) {
|
||||||
|
this.seedAddrs = convertAddrsString(testNet3Seeds(), params.getPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns .onion nodes available on mainnet
|
||||||
|
*/
|
||||||
|
private String[] mainNetSeeds() {
|
||||||
|
// this list copied from bitcoin-core on 2017-01-19
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/57b34599b2deb179ff1bd97ffeab91ec9f904d85/contrib/seeds/nodes_main.txt
|
||||||
|
|
||||||
|
return new String[] {
|
||||||
|
"3ffk7iumtx3cegbi.onion",
|
||||||
|
"3nmbbakinewlgdln.onion",
|
||||||
|
"4j77gihpokxu2kj4.onion",
|
||||||
|
"546esc6botbjfbxb.onion",
|
||||||
|
"5at7sq5nm76xijkd.onion",
|
||||||
|
"77mx2jsxaoyesz2p.onion",
|
||||||
|
"7g7j54btiaxhtsiy.onion",
|
||||||
|
"a6obdgzn67l7exu3.onion",
|
||||||
|
"ab64h7olpl7qpxci.onion",
|
||||||
|
"am2a4rahltfuxz6l.onion",
|
||||||
|
"azuxls4ihrr2mep7.onion",
|
||||||
|
"bitcoin7bi4op7wb.onion",
|
||||||
|
"bitcoinostk4e4re.onion",
|
||||||
|
"bk7yp6epnmcllq72.onion",
|
||||||
|
"bmutjfrj5btseddb.onion",
|
||||||
|
"ceeji4qpfs3ms3zc.onion",
|
||||||
|
"clexmzqio7yhdao4.onion",
|
||||||
|
"gb5ypqt63du3wfhn.onion",
|
||||||
|
"h2vlpudzphzqxutd.onion",
|
||||||
|
"ncwk3lutemffcpc4.onion",
|
||||||
|
"okdzjarwekbshnof.onion",
|
||||||
|
"pjghcivzkoersesd.onion",
|
||||||
|
"rw7ocjltix26mefn.onion",
|
||||||
|
"uws7itep7o3yinxo.onion",
|
||||||
|
"vk3qjdehyy4dwcxw.onion",
|
||||||
|
"vqpye2k5rcqvj5mq.onion",
|
||||||
|
"wpi7rpvhnndl52ee.onion"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns .onion nodes available on testnet3
|
||||||
|
*/
|
||||||
|
private String[] testNet3Seeds() {
|
||||||
|
// this list copied from bitcoin-core on 2017-01-19
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/57b34599b2deb179ff1bd97ffeab91ec9f904d85/contrib/seeds/nodes_test.txt
|
||||||
|
return new String[] {
|
||||||
|
"thfsmmn2jbitcoin.onion",
|
||||||
|
"it2pj4f7657g3rhi.onion",
|
||||||
|
"nkf5e6b7pl4jfd4a.onion",
|
||||||
|
"4zhkir2ofl7orfom.onion",
|
||||||
|
"t6xj6wilh4ytvcs7.onion",
|
||||||
|
"i6y6ivorwakd7nw3.onion",
|
||||||
|
"ubqj4rsu3nqtxmtp.onion"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array containing all the Bitcoin nodes within the list.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
|
||||||
|
return seedAddrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an array of hostnames to array of unresolved InetSocketAddress
|
||||||
|
*/
|
||||||
|
private InetSocketAddress[] convertAddrsString(String[] addrs, int port) {
|
||||||
|
InetSocketAddress[] list = new InetSocketAddress[addrs.length];
|
||||||
|
for (int i = 0; i < addrs.length; i++) {
|
||||||
|
list[i] = InetSocketAddress.createUnresolved(addrs[i], port);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user