Skip to content

Commit

Permalink
Tor using the Orchid library
Browse files Browse the repository at this point in the history
  • Loading branch information
devrandom authored and mikehearn committed Apr 27, 2014
1 parent c5e82e6 commit 99448b7
Show file tree
Hide file tree
Showing 260 changed files with 32,486 additions and 3 deletions.
5 changes: 5 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@
<version>9.1-901.jdbc4</version>
</dependency>
-->
<dependency>
<groupId>com.subgraph</groupId>
<artifactId>orchid</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

</project>
6 changes: 4 additions & 2 deletions core/src/main/java/com/google/bitcoin/net/BlockingClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -65,14 +66,15 @@ public BlockingClient(final SocketAddress serverAddress, final StreamParser pars
// sure it doesnt get too large or have to call read too often.
dbuf = ByteBuffer.allocateDirect(Math.min(Math.max(parser.getMaxMessageSize(), BUFFER_SIZE_LOWER_BOUND), BUFFER_SIZE_UPPER_BOUND));
parser.setWriteTarget(this);
socket = socketFactory.createSocket();
Thread t = new Thread() {
@Override
public void run() {
if (clientSet != null)
clientSet.add(BlockingClient.this);
try {
socket.connect(serverAddress, connectTimeoutMillis);
InetSocketAddress iServerAddress = (InetSocketAddress)serverAddress;
socket = socketFactory.createSocket(iServerAddress.getAddress(), iServerAddress.getPort());
//socket.connect(serverAddress, connectTimeoutMillis);
parser.connectionOpened();
InputStream stream = socket.getInputStream();
byte[] readBuff = new byte[dbuf.capacity()];
Expand Down
270 changes: 270 additions & 0 deletions core/src/main/java/com/google/bitcoin/net/discovery/TorDiscovery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
/**
* Copyright 2014 Miron Cuperman
*
* 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.bitcoin.net.discovery;

import com.google.bitcoin.core.NetworkParameters;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.subgraph.orchid.Circuit;
import com.subgraph.orchid.RelayCell;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.TorClient;
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.data.exitpolicy.ExitTarget;

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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
* <p>Supports peer discovery through Tor.</p>
*
* <p>Failure to obtain at least four different peers through different exit nodes will cause
* a PeerDiscoveryException will be thrown during getPeers().
* </p>
*
* <p>DNS seeds do not attempt to enumerate every peer on the network. If you want more peers
* to connect to, you need to discover them via other means (like addr broadcasts).</p>
*/
public class TorDiscovery implements PeerDiscovery {
private static final Logger log = LoggerFactory.getLogger(TorDiscovery.class);
public static final int MINIMUM_ROUTER_COUNT = 4;
public static final int MINIMUM_ROUTER_LOOKUP_COUNT = 10;
public static final int RECEIVE_RETRIES = 3;
public static final int RESOLVE_STREAM_ID = 0x1000; // An arbitrary stream ID
public static final int RESOLVE_CNAME = 0x00;
public static final int RESOLVE_ERROR = 0xf0;
public static final int RESOLVE_IPV4 = 0x04;
public static final int RESOLVE_IPV6 = 0x06;

private final String[] hostNames;
private final NetworkParameters netParams;
private final CircuitPathChooser pathChooser;
private final TorClient torClient;
private ListeningExecutorService threadPool;

/**
* Supports finding peers through Tor. Community run DNS entry points will be used.
*
* @param netParams Network parameters to be used for port information.
*/
public TorDiscovery(NetworkParameters netParams, TorClient torClient) {
this(netParams.getDnsSeeds(), netParams, torClient);
}

/**
* Supports finding peers through Tor.
*
* @param hostNames Host names to be examined for seed addresses.
* @param netParams Network parameters to be used for port information.
* @param torClient an already-started Tor client.
*/
public TorDiscovery(String[] hostNames, NetworkParameters netParams, TorClient torClient) {
this.hostNames = hostNames;
this.netParams = netParams;

this.torClient = torClient;
this.pathChooser = CircuitPathChooser.create(torClient.getConfig(), torClient.getDirectory());
}

private static class Lookup {
final Router router;
final InetAddress address;

Lookup(Router router, InetAddress address) {
this.router = router;
this.address = address;
}
}

public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
if (hostNames == null)
throw new PeerDiscoveryException("Unable to find any peers via DNS");

Set<Router> routers = Sets.newHashSet();
ArrayList<ExitTarget> dummyTargets = Lists.newArrayList();

// Collect exit nodes until we have enough
while (routers.size() < MINIMUM_ROUTER_LOOKUP_COUNT) {
Router router = pathChooser.chooseExitNodeForTargets(dummyTargets);
routers.add(router);
}

try {
List<Circuit> circuits = getCircuits(timeoutValue, timeoutUnit, routers);

Collection<InetSocketAddress> addresses = lookupAddresses(timeoutValue, timeoutUnit, circuits);

if (addresses.size() < MINIMUM_ROUTER_COUNT)
throw new PeerDiscoveryException("Unable to find enough peers via Tor - got " + addresses.size());
ArrayList<InetSocketAddress> addressList = Lists.newArrayList();
addressList.addAll(addresses);
Collections.shuffle(addressList);
return addressList.toArray(new InetSocketAddress[addressList.size()]);
} catch (InterruptedException e) {
throw new PeerDiscoveryException(e);
}
}

private List<Circuit> getCircuits(long timeoutValue, TimeUnit timeoutUnit, Set<Router> routers) throws InterruptedException {
createThreadPool(routers.size());

try {
List<ListenableFuture<Circuit>> circuitFutures = Lists.newArrayList();
for (final Router router : routers) {
circuitFutures.add(threadPool.submit(new Callable<Circuit>() {
public Circuit call() throws Exception {
return torClient.getCircuitManager().openInternalCircuitTo(Lists.newArrayList(router));
}
}));
}

threadPool.awaitTermination(timeoutValue, timeoutUnit);
for (ListenableFuture<Circuit> future : circuitFutures) {
if (!future.isDone()) {
log.warn("circuit timed out");
future.cancel(true);
}
}

List<Circuit> circuits;
try {
circuits = Futures.successfulAsList(circuitFutures).get();
// Any failures will result in null entries. Remove them.
circuits.removeAll(Collections.singleton(null));
return circuits;
} catch (ExecutionException e) {
// Cannot happen, successfulAsList accepts failures
throw new RuntimeException(e);
}
} finally {
shutdownThreadPool();
}
}

private Collection<InetSocketAddress> lookupAddresses(long timeoutValue, TimeUnit timeoutUnit, List<Circuit> circuits) throws InterruptedException {
createThreadPool(circuits.size() * hostNames.length);

try {
List<ListenableFuture<Lookup>> lookupFutures = Lists.newArrayList();
for (final Circuit circuit : circuits) {
for (final String seed : hostNames) {
lookupFutures.add(threadPool.submit(new Callable<Lookup>() {
public Lookup call() throws Exception {
return new Lookup(circuit.getFinalCircuitNode().getRouter(), lookup(circuit, seed));
}
}));
}
}

threadPool.awaitTermination(timeoutValue, timeoutUnit);
for (ListenableFuture<Lookup> future : lookupFutures) {
if (!future.isDone()) {
log.warn("circuit timed out");
future.cancel(true);
}
}

try {
List<Lookup> lookups = Futures.successfulAsList(lookupFutures).get();
// Any failures will result in null entries. Remove them.
lookups.removeAll(Collections.singleton(null));

// Use a map to enforce one result per exit node
// TODO: randomize result selection better
Map<HexDigest, InetSocketAddress> lookupMap = Maps.newHashMap();

for (Lookup lookup : lookups) {
InetSocketAddress address = new InetSocketAddress(lookup.address, netParams.getPort());
lookupMap.put(lookup.router.getIdentityHash(), address);
}

return lookupMap.values();
} catch (ExecutionException e) {
// Cannot happen, successfulAsList accepts failures
throw new RuntimeException(e);
}
} finally {
shutdownThreadPool();
}
}

private synchronized void shutdownThreadPool() {
threadPool.shutdownNow();
threadPool = null;
}

private synchronized void createThreadPool(int size) {
threadPool =
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(size));
}

private InetAddress lookup(Circuit circuit, String seed) throws UnknownHostException {
// Send a resolve cell to the exit node
RelayCell cell = circuit.createRelayCell(RelayCell.RELAY_RESOLVE, RESOLVE_STREAM_ID, circuit.getFinalCircuitNode());
cell.putString(seed);
circuit.sendRelayCell(cell);

// Wait a few cell timeout periods (3 * 20 sec) for replies, in case the path is slow
for (int i = 0 ; i < RECEIVE_RETRIES; i++) {
RelayCell res = circuit.receiveRelayCell();
if (res != null) {
while (res.cellBytesRemaining() > 0) {
int type = res.getByte();
int len = res.getByte();
byte[] value = new byte[len];
res.getByteArray(value);
int ttl = res.getInt();

if (type == RESOLVE_CNAME || type >= RESOLVE_ERROR) {
// TODO handle .onion CNAME replies
throw new RuntimeException(new String(value));
} else if (type == RESOLVE_IPV4 || type == RESOLVE_IPV6) {
return InetAddress.getByAddress(value);
}
}
break;
}
}
throw new RuntimeException("Could not look up " + seed);
}

public synchronized void shutdown() {
if (threadPool != null) {
shutdownThreadPool();
}
}
}
5 changes: 5 additions & 0 deletions orchid/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
orchid-*.jar
orchid-*.zip
build-revision
lib/xmlrpc-*
25 changes: 25 additions & 0 deletions orchid/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright (c) 2009-2011, Bruce Leidl
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Empty file added orchid/README
Empty file.

0 comments on commit 99448b7

Please sign in to comment.