/** * Copyright 2011 John Sample * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.google.digitalcoin.discovery; import com.google.digitalcoin.core.NetworkParameters; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Set; import java.util.concurrent.*; /** * <p>Supports peer discovery through DNS.</p> * * <p>This class does not support the testnet as currently there are no DNS servers providing testnet hosts. * If this class is being used for testnet you must specify the hostnames to use.</p> * * <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 DnsDiscovery implements PeerDiscovery { private static final Logger log = LoggerFactory.getLogger(DnsDiscovery.class); private String[] hostNames; private NetworkParameters netParams; public static final String[] defaultHosts = new String[]{ "direct.crypto-expert.com", "207.12.89.119", "198.50.30.145", "178.237.35.34", "dgc.kadaplace.com", }; /** * 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 DnsDiscovery(NetworkParameters netParams) { this(getDefaultHostNames(), netParams); } /** * Supports finding peers through DNS A records. * * @param hostNames Host names to be examined for seed addresses. * @param netParams Network parameters to be used for port information. */ public DnsDiscovery(String[] hostNames, NetworkParameters netParams) { this.hostNames = hostNames; this.netParams = netParams; } public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException { final BlockingQueue<InetSocketAddress> queue = new LinkedBlockingQueue<InetSocketAddress>(); ArrayList<String> seeds = new ArrayList<String>(Arrays.asList(hostNames)); // Java doesn't have an async DNS API so we have to do all lookups in a thread pool, as sometimes seeds go // hard down and it takes ages to give up and move on. ExecutorService pool = Executors.newFixedThreadPool(seeds.size()); for (final String seed : seeds) { pool.submit(new Runnable() { public void run() { try { InetAddress[] addrs = InetAddress.getAllByName(seed); for (InetAddress addr : addrs) queue.put(new InetSocketAddress(addr, netParams.port)); } catch (UnknownHostException e) { log.warn("Unable to resolve {}", seed); } catch (InterruptedException e) { // Silently go away. } } }); } // The queue will fill up with resolutions. Let's wait until we got at least 30 or we run out of time. final long timeout = timeoutUnit.toMillis(timeoutValue); long start = System.currentTimeMillis(); Set<InetSocketAddress> addrs = Sets.newHashSet(); while (addrs.size() < 30) { try { long pollTime = timeout - (System.currentTimeMillis() - start); if (pollTime < 0) break; InetSocketAddress a = queue.poll(pollTime, TimeUnit.MILLISECONDS); if (a == null) { break; } addrs.add(a); } catch (InterruptedException e) { break; } } if (addrs.size() == 0) { throw new PeerDiscoveryException("Unable to find any peers via DNS"); } ArrayList<InetSocketAddress> shuffledAddrs = new ArrayList<InetSocketAddress>(addrs); Collections.shuffle(shuffledAddrs); pool.shutdown(); return shuffledAddrs.toArray(new InetSocketAddress[]{}); } /** * Returns the well known discovery host names on the production network. */ public static String[] getDefaultHostNames() { return defaultHosts; } /** We don't have a way to abort a DNS lookup, so this does nothing */ public void shutdown() { } }