Merge pull request #730 from dan-da/hiddenservices_pr

DNS lookups over Tor and connecting bitcoinj to .onion addresses.
This commit is contained in:
Manfred Karrer 2017-02-02 19:24:54 -05:00 committed by GitHub
commit a1e7ccc1bf
7 changed files with 538 additions and 187 deletions

View File

@ -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() {
}
}

View File

@ -32,7 +32,9 @@ import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.network.DnsLookupTor;
import io.bitsquare.network.Socks5ProxyProvider;
import io.bitsquare.network.Socks5MultiDiscovery;
import io.bitsquare.storage.FileUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Preferences;
@ -266,9 +268,7 @@ public class WalletService {
// Pass custom seed nodes if set in options
if (!btcNodes.isEmpty()) {
// TODO: this parsing should be more robust,
// give validation error if needed.
String[] nodes = btcNodes.replace(", ", ",").split(",");
String[] nodes = parseCSV(btcNodes);
List<PeerAddress> peerAddressList = new ArrayList<>();
for (String node : nodes) {
String[] parts = node.split(":");
@ -279,19 +279,24 @@ public class WalletService {
if (parts.length == 2) {
// note: this will cause a DNS request if hostname 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;
if (socks5Proxy != null) {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
// proxy remote DNS request happens here.
addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
try {
// proxy remote DNS request happens here. blocking.
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 {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
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()));
}
}
}
if (peerAddressList.size() > 0) {
@ -343,9 +348,10 @@ public class WalletService {
// could become outdated, so it is important that the user be able to
// disable it, but should be made aware of the reduced privacy.
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.
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)
@ -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

View File

@ -206,7 +206,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
peerList.stream().forEach(e -> {
if (bitcoinPeersTextArea.getText().length() > 0)
bitcoinPeersTextArea.appendText("\n");
bitcoinPeersTextArea.appendText(e.getAddress().getSocketAddress().toString());
bitcoinPeersTextArea.appendText(e.toString());
});
}
}

View 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;
}
}

View File

@ -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;
}
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}