mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 18:25:51 +00:00
Remove Orchid forked subproject and support for connecting via Tor.
This commit is contained in:
parent
ba6e65f091
commit
7e609a2409
@ -6,9 +6,7 @@ install: true # remove default
|
|||||||
script:
|
script:
|
||||||
- mvn -q clean install -Pno-network
|
- mvn -q clean install -Pno-network
|
||||||
- jdk_switcher use openjdk6
|
- jdk_switcher use openjdk6
|
||||||
- cd orchid
|
- cd core
|
||||||
- mvn -q clean package
|
|
||||||
- cd ../core
|
|
||||||
- mvn -q clean package -Pno-network
|
- mvn -q clean package -Pno-network
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
@ -426,11 +426,6 @@
|
|||||||
<version>1.8</version>
|
<version>1.8</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.bitcoinj</groupId>
|
|
||||||
<artifactId>orchid</artifactId>
|
|
||||||
<version>1.2.1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp</groupId>
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
|
@ -23,11 +23,8 @@ import com.google.common.collect.*;
|
|||||||
import com.google.common.net.*;
|
import com.google.common.net.*;
|
||||||
import com.google.common.primitives.*;
|
import com.google.common.primitives.*;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.squareup.okhttp.*;
|
|
||||||
import com.subgraph.orchid.*;
|
|
||||||
import net.jcip.annotations.*;
|
import net.jcip.annotations.*;
|
||||||
import org.bitcoinj.core.listeners.*;
|
import org.bitcoinj.core.listeners.*;
|
||||||
import org.bitcoinj.crypto.*;
|
|
||||||
import org.bitcoinj.net.*;
|
import org.bitcoinj.net.*;
|
||||||
import org.bitcoinj.net.discovery.*;
|
import org.bitcoinj.net.discovery.*;
|
||||||
import org.bitcoinj.script.*;
|
import org.bitcoinj.script.*;
|
||||||
@ -84,7 +81,6 @@ public class PeerGroup implements TransactionBroadcaster {
|
|||||||
* get through.
|
* get through.
|
||||||
*/
|
*/
|
||||||
public static final int DEFAULT_CONNECTIONS = 12;
|
public static final int DEFAULT_CONNECTIONS = 12;
|
||||||
private static final int TOR_TIMEOUT_SECONDS = 60;
|
|
||||||
private volatile int vMaxPeersToDiscoverCount = 100;
|
private volatile int vMaxPeersToDiscoverCount = 100;
|
||||||
private static final long DEFAULT_PEER_DISCOVERY_TIMEOUT_MILLIS = 5000;
|
private static final long DEFAULT_PEER_DISCOVERY_TIMEOUT_MILLIS = 5000;
|
||||||
private volatile long vPeerDiscoveryTimeoutMillis = DEFAULT_PEER_DISCOVERY_TIMEOUT_MILLIS;
|
private volatile long vPeerDiscoveryTimeoutMillis = DEFAULT_PEER_DISCOVERY_TIMEOUT_MILLIS;
|
||||||
@ -113,7 +109,6 @@ public class PeerGroup implements TransactionBroadcaster {
|
|||||||
// Currently connecting peers.
|
// Currently connecting peers.
|
||||||
private final CopyOnWriteArrayList<Peer> pendingPeers;
|
private final CopyOnWriteArrayList<Peer> pendingPeers;
|
||||||
private final ClientConnectionManager channels;
|
private final ClientConnectionManager channels;
|
||||||
@Nullable private final TorClient torClient;
|
|
||||||
|
|
||||||
// The peer that has been selected for the purposes of downloading announced data.
|
// The peer that has been selected for the purposes of downloading announced data.
|
||||||
@GuardedBy("lock") private Peer downloadPeer;
|
@GuardedBy("lock") private Peer downloadPeer;
|
||||||
@ -314,95 +309,22 @@ public class PeerGroup implements TransactionBroadcaster {
|
|||||||
this(context, chain, new NioClientManager());
|
this(context, chain, new NioClientManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** See {@link #newWithTor(Context, AbstractBlockChain, TorClient)} */
|
|
||||||
public static PeerGroup newWithTor(NetworkParameters params, @Nullable AbstractBlockChain chain, TorClient torClient) throws TimeoutException {
|
|
||||||
return newWithTor(Context.getOrCreate(params), chain, torClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Creates a PeerGroup that accesses the network via the Tor network. The provided TorClient is used so you can
|
|
||||||
* preconfigure it beforehand. It should not have been already started. You can just use "new TorClient()" if
|
|
||||||
* you don't have any particular configuration requirements.</p>
|
|
||||||
*
|
|
||||||
* <p>Peer discovery is automatically configured to use DNS seeds resolved via a random selection of exit nodes.
|
|
||||||
* If running on the Oracle JDK the unlimited strength jurisdiction checks will also be overridden,
|
|
||||||
* as they no longer apply anyway and can cause startup failures due to the requirement for AES-256.</p>
|
|
||||||
*
|
|
||||||
* <p>The user does not need any additional software for this: it's all pure Java. As of April 2014 <b>this mode
|
|
||||||
* is experimental</b>.</p>
|
|
||||||
*
|
|
||||||
* @throws TimeoutException if Tor fails to start within 20 seconds.
|
|
||||||
*/
|
|
||||||
public static PeerGroup newWithTor(Context context, @Nullable AbstractBlockChain chain, TorClient torClient) throws TimeoutException {
|
|
||||||
return newWithTor(context, chain, torClient, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Creates a PeerGroup that accesses the network via the Tor network. The provided TorClient is used so you can
|
|
||||||
* preconfigure it beforehand. It should not have been already started. You can just use "new TorClient()" if
|
|
||||||
* you don't have any particular configuration requirements.</p>
|
|
||||||
*
|
|
||||||
* <p>If running on the Oracle JDK the unlimited strength jurisdiction checks will also be overridden,
|
|
||||||
* as they no longer apply anyway and can cause startup failures due to the requirement for AES-256.</p>
|
|
||||||
*
|
|
||||||
* <p>The user does not need any additional software for this: it's all pure Java. As of April 2014 <b>this mode
|
|
||||||
* is experimental</b>.</p>
|
|
||||||
*
|
|
||||||
* @param doDiscovery if true, DNS or HTTP peer discovery will be performed via Tor: this is almost always what you want.
|
|
||||||
* @throws java.util.concurrent.TimeoutException if Tor fails to start within 20 seconds.
|
|
||||||
*/
|
|
||||||
public static PeerGroup newWithTor(Context context, @Nullable AbstractBlockChain chain, TorClient torClient, boolean doDiscovery) throws TimeoutException {
|
|
||||||
checkNotNull(torClient);
|
|
||||||
DRMWorkaround.maybeDisableExportControls();
|
|
||||||
BlockingClientManager manager = new BlockingClientManager(torClient.getSocketFactory());
|
|
||||||
final int CONNECT_TIMEOUT_MSEC = TOR_TIMEOUT_SECONDS * 1000;
|
|
||||||
manager.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
|
|
||||||
PeerGroup result = new PeerGroup(context, chain, manager, torClient);
|
|
||||||
result.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
|
|
||||||
|
|
||||||
if (doDiscovery) {
|
|
||||||
NetworkParameters params = context.getParams();
|
|
||||||
HttpDiscovery.Details[] httpSeeds = params.getHttpSeeds();
|
|
||||||
if (httpSeeds.length > 0) {
|
|
||||||
// Use HTTP discovery when Tor is active and there is a Cartographer seed, for a much needed speed boost.
|
|
||||||
OkHttpClient httpClient = new OkHttpClient();
|
|
||||||
httpClient.setSocketFactory(torClient.getSocketFactory());
|
|
||||||
List<PeerDiscovery> discoveries = Lists.newArrayList();
|
|
||||||
for (HttpDiscovery.Details httpSeed : httpSeeds)
|
|
||||||
discoveries.add(new HttpDiscovery(params, httpSeed, httpClient));
|
|
||||||
result.addPeerDiscovery(new MultiplexingDiscovery(params, discoveries));
|
|
||||||
} else {
|
|
||||||
result.addPeerDiscovery(new TorDiscovery(params, torClient));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** See {@link #PeerGroup(Context, AbstractBlockChain, ClientConnectionManager)} */
|
/** See {@link #PeerGroup(Context, AbstractBlockChain, ClientConnectionManager)} */
|
||||||
public PeerGroup(NetworkParameters params, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager) {
|
public PeerGroup(NetworkParameters params, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager) {
|
||||||
this(Context.getOrCreate(params), chain, connectionManager, null);
|
this(Context.getOrCreate(params), chain, connectionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new PeerGroup allowing you to specify the {@link ClientConnectionManager} which is used to create new
|
* Creates a new PeerGroup allowing you to specify the {@link ClientConnectionManager} which is used to create new
|
||||||
* connections and keep track of existing ones.
|
* connections and keep track of existing ones.
|
||||||
*/
|
*/
|
||||||
public PeerGroup(Context context, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager) {
|
private PeerGroup(Context context, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager) {
|
||||||
this(context, chain, connectionManager, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new PeerGroup allowing you to specify the {@link ClientConnectionManager} which is used to create new
|
|
||||||
* connections and keep track of existing ones.
|
|
||||||
*/
|
|
||||||
private PeerGroup(Context context, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager, @Nullable TorClient torClient) {
|
|
||||||
checkNotNull(context);
|
checkNotNull(context);
|
||||||
this.params = context.getParams();
|
this.params = context.getParams();
|
||||||
this.chain = chain;
|
this.chain = chain;
|
||||||
fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
|
fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
|
||||||
wallets = new CopyOnWriteArrayList<Wallet>();
|
wallets = new CopyOnWriteArrayList<Wallet>();
|
||||||
peerFilterProviders = new CopyOnWriteArrayList<PeerFilterProvider>();
|
peerFilterProviders = new CopyOnWriteArrayList<PeerFilterProvider>();
|
||||||
this.torClient = torClient;
|
|
||||||
|
|
||||||
executor = createPrivateExecutor();
|
executor = createPrivateExecutor();
|
||||||
|
|
||||||
@ -1137,16 +1059,6 @@ public class PeerGroup implements TransactionBroadcaster {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
log.info("Starting ...");
|
log.info("Starting ...");
|
||||||
if (torClient != null) {
|
|
||||||
log.info("Starting Tor/Orchid ...");
|
|
||||||
torClient.start();
|
|
||||||
try {
|
|
||||||
torClient.waitUntilReady(TOR_TIMEOUT_SECONDS * 1000);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
log.info("Tor ready");
|
|
||||||
}
|
|
||||||
channels.startAsync();
|
channels.startAsync();
|
||||||
channels.awaitRunning();
|
channels.awaitRunning();
|
||||||
triggerConnections();
|
triggerConnections();
|
||||||
@ -1183,9 +1095,6 @@ public class PeerGroup implements TransactionBroadcaster {
|
|||||||
for (PeerDiscovery peerDiscovery : peerDiscoverers) {
|
for (PeerDiscovery peerDiscovery : peerDiscoverers) {
|
||||||
peerDiscovery.shutdown();
|
peerDiscovery.shutdown();
|
||||||
}
|
}
|
||||||
if (torClient != null) {
|
|
||||||
torClient.stop();
|
|
||||||
}
|
|
||||||
vRunning = false;
|
vRunning = false;
|
||||||
log.info("Stopped.");
|
log.info("Stopped.");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@ -2327,14 +2236,6 @@ public class PeerGroup implements TransactionBroadcaster {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link com.subgraph.orchid.TorClient} object for this peer group, if Tor is in use, null otherwise.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public TorClient getTorClient() {
|
|
||||||
return torClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the maximum number of {@link Peer}s to discover. This maximum is checked after
|
* Returns the maximum number of {@link Peer}s to discover. This maximum is checked after
|
||||||
* each {@link PeerDiscovery} so this max number can be surpassed.
|
* each {@link PeerDiscovery} so this max number can be surpassed.
|
||||||
|
@ -19,7 +19,6 @@ package org.bitcoinj.kits;
|
|||||||
|
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.subgraph.orchid.*;
|
|
||||||
import org.bitcoinj.core.listeners.*;
|
import org.bitcoinj.core.listeners.*;
|
||||||
import org.bitcoinj.core.*;
|
import org.bitcoinj.core.*;
|
||||||
import org.bitcoinj.net.discovery.*;
|
import org.bitcoinj.net.discovery.*;
|
||||||
@ -78,7 +77,6 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
protected boolean autoStop = true;
|
protected boolean autoStop = true;
|
||||||
protected InputStream checkpoints;
|
protected InputStream checkpoints;
|
||||||
protected boolean blockingStartup = true;
|
protected boolean blockingStartup = true;
|
||||||
protected boolean useTor = false; // Perhaps in future we can change this to true.
|
|
||||||
protected String userAgent, version;
|
protected String userAgent, version;
|
||||||
protected WalletProtobufSerializer.WalletFactory walletFactory;
|
protected WalletProtobufSerializer.WalletFactory walletFactory;
|
||||||
@Nullable protected DeterministicSeed restoreFromSeed;
|
@Nullable protected DeterministicSeed restoreFromSeed;
|
||||||
@ -178,15 +176,6 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If called, then an embedded Tor client library will be used to connect to the P2P network. The user does not need
|
|
||||||
* any additional software for this: it's all pure Java. As of April 2014 <b>this mode is experimental</b>.
|
|
||||||
*/
|
|
||||||
public WalletAppKit useTor() {
|
|
||||||
this.useTor = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a seed is set here then any existing wallet that matches the file name will be renamed to a backup name,
|
* If a seed is set here then any existing wallet that matches the file name will be renamed to a backup name,
|
||||||
* the chain file will be deleted, and the wallet object will be instantiated with the given seed instead of
|
* the chain file will be deleted, and the wallet object will be instantiated with the given seed instead of
|
||||||
@ -318,7 +307,7 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
for (PeerAddress addr : peerAddresses) vPeerGroup.addAddress(addr);
|
for (PeerAddress addr : peerAddresses) vPeerGroup.addAddress(addr);
|
||||||
vPeerGroup.setMaxConnections(peerAddresses.length);
|
vPeerGroup.setMaxConnections(peerAddresses.length);
|
||||||
peerAddresses = null;
|
peerAddresses = null;
|
||||||
} else if (!params.getId().equals(NetworkParameters.ID_REGTEST) && !useTor) {
|
} else if (!params.getId().equals(NetworkParameters.ID_REGTEST)) {
|
||||||
vPeerGroup.addPeerDiscovery(discovery != null ? discovery : new DnsDiscovery(params));
|
vPeerGroup.addPeerDiscovery(discovery != null ? discovery : new DnsDiscovery(params));
|
||||||
}
|
}
|
||||||
vChain.addWallet(vWallet);
|
vChain.addWallet(vWallet);
|
||||||
@ -457,13 +446,7 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
|
|
||||||
|
|
||||||
protected PeerGroup createPeerGroup() throws TimeoutException {
|
protected PeerGroup createPeerGroup() throws TimeoutException {
|
||||||
if (useTor) {
|
return new PeerGroup(params, vChain);
|
||||||
TorClient torClient = new TorClient();
|
|
||||||
torClient.getConfig().setDataDirectory(directory);
|
|
||||||
return PeerGroup.newWithTor(params, vChain, torClient);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return new PeerGroup(params, vChain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installShutdownHook() {
|
private void installShutdownHook() {
|
||||||
|
@ -1,279 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014 Miron Cuperman
|
|
||||||
* Copyright 2015 Andreas Schildbach
|
|
||||||
*
|
|
||||||
* 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 org.bitcoinj.net.discovery;
|
|
||||||
|
|
||||||
import com.google.common.collect.*;
|
|
||||||
import com.google.common.util.concurrent.*;
|
|
||||||
import com.subgraph.orchid.*;
|
|
||||||
import com.subgraph.orchid.circuits.path.*;
|
|
||||||
import com.subgraph.orchid.data.*;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.*;
|
|
||||||
import org.bitcoinj.core.*;
|
|
||||||
import org.bitcoinj.utils.*;
|
|
||||||
import org.slf4j.*;
|
|
||||||
|
|
||||||
import java.net.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.*;
|
|
||||||
import static java.util.Collections.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <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 ROUTER_LOOKUP_COUNT = 10;
|
|
||||||
public static final int MINIMUM_ROUTER_LOOKUP_COUNT = 6;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InetSocketAddress[] getPeers(long services, long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
|
|
||||||
if (hostNames == null)
|
|
||||||
throw new PeerDiscoveryException("Unable to find any peers via DNS");
|
|
||||||
if (services != 0)
|
|
||||||
throw new PeerDiscoveryException("DNS seeds cannot filter by services: " + services);
|
|
||||||
|
|
||||||
Set<Router> routers = Sets.newHashSet();
|
|
||||||
ArrayList<ExitTarget> dummyTargets = Lists.newArrayList();
|
|
||||||
|
|
||||||
// Collect exit nodes until we have enough
|
|
||||||
while (routers.size() < ROUTER_LOOKUP_COUNT) {
|
|
||||||
Router router = pathChooser.chooseExitNodeForTargets(dummyTargets);
|
|
||||||
routers.add(router);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<Circuit> circuits =
|
|
||||||
getCircuits(torClient.getConfig().getCircuitBuildTimeout(), TimeUnit.MILLISECONDS, routers);
|
|
||||||
if (circuits.isEmpty())
|
|
||||||
throw new PeerDiscoveryException("Failed to open any circuit within " +
|
|
||||||
String.valueOf(timeoutValue) + " " + timeoutUnit);
|
|
||||||
|
|
||||||
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 {
|
|
||||||
checkArgument(routers.size() >= MINIMUM_ROUTER_LOOKUP_COUNT, "Set of {} routers is smaller than required minimum {}",
|
|
||||||
routers.size(), MINIMUM_ROUTER_LOOKUP_COUNT);
|
|
||||||
createThreadPool(routers.size());
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<ListenableFuture<Circuit>> circuitFutures = Lists.newArrayList();
|
|
||||||
final CountDownLatch doneSignal = new CountDownLatch(MINIMUM_ROUTER_LOOKUP_COUNT);
|
|
||||||
for (final Router router : routers) {
|
|
||||||
ListenableFuture<Circuit> openCircuit = threadPool.submit(new Callable<Circuit>() {
|
|
||||||
@Override
|
|
||||||
public Circuit call() throws Exception {
|
|
||||||
return torClient.getCircuitManager().openInternalCircuitTo(Lists.newArrayList(router));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Futures.addCallback(openCircuit, new FutureCallback<Circuit>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Circuit circuit) {
|
|
||||||
doneSignal.countDown();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable thrown) {
|
|
||||||
doneSignal.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
circuitFutures.add(openCircuit);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean countedDown = doneSignal.await(timeoutValue, timeoutUnit);
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<Circuit> circuits = new ArrayList<Circuit>(Futures.successfulAsList(circuitFutures).get());
|
|
||||||
// Any failures will result in null entries. Remove them.
|
|
||||||
circuits.removeAll(singleton(null));
|
|
||||||
int failures = routers.size() - circuits.size();
|
|
||||||
if (failures > 0) log.warn("{} failures " + (countedDown ? "" : "(including timeout) ") +
|
|
||||||
"opening DNS lookup circuits", failures);
|
|
||||||
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>() {
|
|
||||||
@Override
|
|
||||||
public Lookup call() throws Exception {
|
|
||||||
return new Lookup(circuit.getFinalCircuitNode().getRouter(), lookup(circuit, seed));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
threadPool.awaitTermination(timeoutValue, timeoutUnit);
|
|
||||||
int timeouts = 0;
|
|
||||||
for (ListenableFuture<Lookup> future : lookupFutures) {
|
|
||||||
if (!future.isDone()) {
|
|
||||||
timeouts++;
|
|
||||||
future.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (timeouts > 0)
|
|
||||||
log.warn("{} DNS lookups timed out", timeouts);
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<Lookup> lookups = new ArrayList<Lookup>(Futures.successfulAsList(lookupFutures).get());
|
|
||||||
// Any failures will result in null entries. Remove them.
|
|
||||||
lookups.removeAll(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, new ContextPropagatingThreadFactory("Tor DNS discovery")));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdown() {
|
|
||||||
if (threadPool != null) {
|
|
||||||
shutdownThreadPool();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
5
orchid/.gitignore
vendored
5
orchid/.gitignore
vendored
@ -1,5 +0,0 @@
|
|||||||
bin/
|
|
||||||
orchid-*.jar
|
|
||||||
orchid-*.zip
|
|
||||||
build-revision
|
|
||||||
lib/xmlrpc-*
|
|
@ -1,25 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
116
orchid/build.xml
116
orchid/build.xml
@ -1,116 +0,0 @@
|
|||||||
<project name="orchid" default="all">
|
|
||||||
|
|
||||||
<exec executable="git" outputproperty="orchid.gittag" logError="true" failifexecutionfails="false">
|
|
||||||
<arg value="rev-parse"/>
|
|
||||||
<arg value="--short"/>
|
|
||||||
<arg value="HEAD"/>
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<property name="orchid.version" value="1.0.0" />
|
|
||||||
|
|
||||||
<condition property="orchid.basename" value="orchid-${orchid.version}.${orchid.gittag}">
|
|
||||||
<isset property="orchid.gittag"/>
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<property name="orchid.basename" value="orchid-${orchid.version}" />
|
|
||||||
|
|
||||||
<property name="orchid.jarfile" value="${orchid.basename}.jar" />
|
|
||||||
<property name="orchid.sourcefile" value="${orchid.basename}-src.zip" />
|
|
||||||
|
|
||||||
<path id="compile.classpath">
|
|
||||||
<fileset dir="lib">
|
|
||||||
<include name="*.jar"/>
|
|
||||||
</fileset>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<path id="test.classpath">
|
|
||||||
<fileset dir="lib/testing">
|
|
||||||
<include name="*.jar"/>
|
|
||||||
</fileset>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<path id="bin">
|
|
||||||
<pathelement location="${basedir}/bin"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<condition property="xmlrpc.present">
|
|
||||||
<and>
|
|
||||||
<available classname="org.apache.xmlrpc.client.XmlRpcTransportFactory" classpathref="compile.classpath"/>
|
|
||||||
<available classname="org.apache.xmlrpc.XmlRpcException" classpathref="compile.classpath"/>
|
|
||||||
</and>
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<target name="all" depends="write-revision,compile,compile-xmlrpc,package,source"/>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${basedir}/bin" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="write-revision">
|
|
||||||
<echo message="${orchid.gittag}${line.separator}" file="build-revision" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="init">
|
|
||||||
<mkdir dir="${basedir}/bin" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="compile" depends="init">
|
|
||||||
<javac source="1.5" target="1.5" destdir="${basedir}/bin" includeantruntime="false">
|
|
||||||
<src path="${basedir}/src" />
|
|
||||||
<classpath refid="compile.classpath"/>
|
|
||||||
</javac>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="compile-xmlrpc" depends="init" if="xmlrpc.present">
|
|
||||||
<echo message="compiling optional xmlrpc classes"/>
|
|
||||||
<javac source="1.5" target="1.5" srcdir="opt/xmlrpc" destdir="bin" classpathref="compile.classpath" includeantruntime="false"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="package">
|
|
||||||
<jar destfile="${orchid.jarfile}">
|
|
||||||
<manifest>
|
|
||||||
<attribute name="Main-Class" value="com.subgraph.orchid.TorClient"/>
|
|
||||||
</manifest>
|
|
||||||
<fileset dir="${basedir}/bin" />
|
|
||||||
<zipfileset dir="${basedir}/data" includes="GeoIP.dat" fullpath="data/GeoIP.dat" />
|
|
||||||
<zipfileset dir="${basedir}" includes="build-revision" />
|
|
||||||
</jar>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="source">
|
|
||||||
<zip destfile="${orchid.sourcefile}">
|
|
||||||
<zipfileset dir="src" prefix="${orchid.basename}/src" />
|
|
||||||
<zipfileset dir="opt/xmlrpc" prefix="${orchid.basename}/src" />
|
|
||||||
</zip>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="compile-test" depends="compile">
|
|
||||||
<javac source="1.5" target="1.5" destdir="${basedir}/bin" includeantruntime="false">
|
|
||||||
<src path="${basedir}/test"/>
|
|
||||||
<classpath>
|
|
||||||
<path refid="compile.classpath"/>
|
|
||||||
<path refid="test.classpath"/>
|
|
||||||
</classpath>
|
|
||||||
</javac>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="test" depends="compile-test">
|
|
||||||
<junit printsummary="on" fork="yes" forkmode="once">
|
|
||||||
<assertions>
|
|
||||||
<enable/>
|
|
||||||
</assertions>
|
|
||||||
<classpath>
|
|
||||||
<path refid="compile.classpath"/>
|
|
||||||
<path refid="test.classpath"/>
|
|
||||||
<path refid="bin"/>
|
|
||||||
</classpath>
|
|
||||||
<test name="com.subgraph.orchid.TorConfigTest"/>
|
|
||||||
<test name="com.subgraph.orchid.circuits.TorInputStreamTest"/>
|
|
||||||
<test name="com.subgraph.orchid.circuits.path.ConfigNodeFilterTest"/>
|
|
||||||
<test name="com.subgraph.orchid.circuits.path.ConfigNodeFilterTest"/>
|
|
||||||
<test name="com.subgraph.orchid.crypto.ASN1ParserTest"/>
|
|
||||||
<test name="com.subgraph.orchid.crypto.RSAKeyEncoderTest"/>
|
|
||||||
<test name="com.subgraph.orchid.geoip.CountryCodeServiceTest"/>
|
|
||||||
</junit>
|
|
||||||
</target>
|
|
||||||
</project>
|
|
Binary file not shown.
@ -1,3 +0,0 @@
|
|||||||
GeoIP.dat GeoLite Country database downloaded September, 2013
|
|
||||||
|
|
||||||
http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
|
|
@ -1,58 +0,0 @@
|
|||||||
|
|
||||||
Special Hostnames in Tor
|
|
||||||
Nick Mathewson
|
|
||||||
|
|
||||||
1. Overview
|
|
||||||
|
|
||||||
Most of the time, Tor treats user-specified hostnames as opaque: When
|
|
||||||
the user connects to www.torproject.org, Tor picks an exit node and uses
|
|
||||||
that node to connect to "www.torproject.org". Some hostnames, however,
|
|
||||||
can be used to override Tor's default behavior and circuit-building
|
|
||||||
rules.
|
|
||||||
|
|
||||||
These hostnames can be passed to Tor as the address part of a SOCKS4a or
|
|
||||||
SOCKS5 request. If the application is connected to Tor using an IP-only
|
|
||||||
method (such as SOCKS4, TransPort, or NatdPort), these hostnames can be
|
|
||||||
substituted for certain IP addresses using the MapAddress configuration
|
|
||||||
option or the MAPADDRESS control command.
|
|
||||||
|
|
||||||
2. .exit
|
|
||||||
|
|
||||||
SYNTAX: [hostname].[name-or-digest].exit
|
|
||||||
[name-or-digest].exit
|
|
||||||
|
|
||||||
Hostname is a valid hostname; [name-or-digest] is either the nickname of a
|
|
||||||
Tor node or the hex-encoded digest of that node's public key.
|
|
||||||
|
|
||||||
When Tor sees an address in this format, it uses the specified hostname as
|
|
||||||
the exit node. If no "hostname" component is given, Tor defaults to the
|
|
||||||
published IPv4 address of the exit node.
|
|
||||||
|
|
||||||
It is valid to try to resolve hostnames, and in fact upon success Tor
|
|
||||||
will cache an internal mapaddress of the form
|
|
||||||
"www.google.com.foo.exit=64.233.161.99.foo.exit" to speed subsequent
|
|
||||||
lookups.
|
|
||||||
|
|
||||||
The .exit notation is disabled by default as of Tor 0.2.2.1-alpha, due
|
|
||||||
to potential application-level attacks.
|
|
||||||
|
|
||||||
EXAMPLES:
|
|
||||||
www.example.com.exampletornode.exit
|
|
||||||
|
|
||||||
Connect to www.example.com from the node called "exampletornode".
|
|
||||||
|
|
||||||
exampletornode.exit
|
|
||||||
|
|
||||||
Connect to the published IP address of "exampletornode" using
|
|
||||||
"exampletornode" as the exit.
|
|
||||||
|
|
||||||
3. .onion
|
|
||||||
|
|
||||||
SYNTAX: [digest].onion
|
|
||||||
|
|
||||||
The digest is the first eighty bits of a SHA1 hash of the identity key for
|
|
||||||
a hidden service, encoded in base32.
|
|
||||||
|
|
||||||
When Tor sees an address in this format, it tries to look up and connect to
|
|
||||||
the specified hidden service. See rend-spec.txt for full details.
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
|||||||
|
|
||||||
Tor bridges specification
|
|
||||||
|
|
||||||
0. Preface
|
|
||||||
|
|
||||||
This document describes the design decisions around support for bridge
|
|
||||||
users, bridge relays, and bridge authorities. It acts as an overview
|
|
||||||
of the bridge design and deployment for developers, and it also tries
|
|
||||||
to point out limitations in the current design and implementation.
|
|
||||||
|
|
||||||
For more details on what all of these mean, look at blocking.tex in
|
|
||||||
/doc/design-paper/
|
|
||||||
|
|
||||||
1. Bridge relays
|
|
||||||
|
|
||||||
Bridge relays are just like normal Tor relays except they don't publish
|
|
||||||
their server descriptors to the main directory authorities.
|
|
||||||
|
|
||||||
1.1. PublishServerDescriptor
|
|
||||||
|
|
||||||
To configure your relay to be a bridge relay, just add
|
|
||||||
BridgeRelay 1
|
|
||||||
PublishServerDescriptor bridge
|
|
||||||
to your torrc. This will cause your relay to publish its descriptor
|
|
||||||
to the bridge authorities rather than to the default authorities.
|
|
||||||
|
|
||||||
Alternatively, you can say
|
|
||||||
BridgeRelay 1
|
|
||||||
PublishServerDescriptor 0
|
|
||||||
which will cause your relay to not publish anywhere. This could be
|
|
||||||
useful for private bridges.
|
|
||||||
|
|
||||||
1.2. Recommendations.
|
|
||||||
|
|
||||||
Bridge relays should use an exit policy of "reject *:*". This is
|
|
||||||
because they only need to relay traffic between the bridge users
|
|
||||||
and the rest of the Tor network, so there's no need to let people
|
|
||||||
exit directly from them.
|
|
||||||
|
|
||||||
We invented the RelayBandwidth* options for this situation: Tor clients
|
|
||||||
who want to allow relaying too. See proposal 111 for details. Relay
|
|
||||||
operators should feel free to rate-limit their relayed traffic.
|
|
||||||
|
|
||||||
1.3. Implementation note.
|
|
||||||
|
|
||||||
Vidalia 0.0.15 has turned its "Relay" settings page into a tri-state
|
|
||||||
"Don't relay" / "Relay for the Tor network" / "Help censored users".
|
|
||||||
|
|
||||||
If you click the third choice, it forces your exit policy to reject *:*.
|
|
||||||
|
|
||||||
If all the bridges end up on port 9001, that's not so good. On the
|
|
||||||
other hand, putting the bridges on a low-numbered port in the Unix
|
|
||||||
world requires jumping through extra hoops. The current compromise is
|
|
||||||
that Vidalia makes the ORPort default to 443 on Windows, and 9001 on
|
|
||||||
other platforms.
|
|
||||||
|
|
||||||
At the bottom of the relay config settings window, Vidalia displays
|
|
||||||
the bridge identifier to the operator (see Section 3.1) so he can pass
|
|
||||||
it on to bridge users.
|
|
||||||
|
|
||||||
2. Bridge authorities.
|
|
||||||
|
|
||||||
Bridge authorities are like normal v3 directory authorities, except
|
|
||||||
they don't create their own network-status documents or votes. So if
|
|
||||||
you ask a bridge authority for a network-status document or consensus,
|
|
||||||
they behave like a directory mirror: they give you one from one of
|
|
||||||
the main authorities. But if you ask the bridge authority for the
|
|
||||||
descriptor corresponding to a particular identity fingerprint, it will
|
|
||||||
happily give you the latest descriptor for that fingerprint.
|
|
||||||
|
|
||||||
To become a bridge authority, add these lines to your torrc:
|
|
||||||
AuthoritativeDirectory 1
|
|
||||||
BridgeAuthoritativeDir 1
|
|
||||||
|
|
||||||
Right now there's one bridge authority, running on the Tonga relay.
|
|
||||||
|
|
||||||
2.1. Exporting bridge-purpose descriptors
|
|
||||||
|
|
||||||
We've added a new purpose for server descriptors: the "bridge"
|
|
||||||
purpose. With the new router-descriptors file format that includes
|
|
||||||
annotations, it's easy to look through it and find the bridge-purpose
|
|
||||||
descriptors.
|
|
||||||
|
|
||||||
Currently we export the bridge descriptors from Tonga to the
|
|
||||||
BridgeDB server, so it can give them out according to the policies
|
|
||||||
in blocking.pdf.
|
|
||||||
|
|
||||||
2.2. Reachability/uptime testing
|
|
||||||
|
|
||||||
Right now the bridge authorities do active reachability testing of
|
|
||||||
bridges, so we know which ones to recommend for users.
|
|
||||||
|
|
||||||
But in the design document, we suggested that bridges should publish
|
|
||||||
anonymously (i.e. via Tor) to the bridge authority, so somebody watching
|
|
||||||
the bridge authority can't just enumerate all the bridges. But if we're
|
|
||||||
doing active measurement, the game is up. Perhaps we should back off on
|
|
||||||
this goal, or perhaps we should do our active measurement anonymously?
|
|
||||||
|
|
||||||
Answering this issue is scheduled for 0.2.1.x.
|
|
||||||
|
|
||||||
2.3. Future work: migrating to multiple bridge authorities
|
|
||||||
|
|
||||||
Having only one bridge authority is both a trust bottleneck (if you
|
|
||||||
break into one place you learn about every single bridge we've got)
|
|
||||||
and a robustness bottleneck (when it's down, bridge users become sad).
|
|
||||||
|
|
||||||
Right now if we put up a second bridge authority, all the bridges would
|
|
||||||
publish to it, and (assuming the code works) bridge users would query
|
|
||||||
a random bridge authority. This resolves the robustness bottleneck,
|
|
||||||
but makes the trust bottleneck even worse.
|
|
||||||
|
|
||||||
In 0.2.2.x and later we should think about better ways to have multiple
|
|
||||||
bridge authorities.
|
|
||||||
|
|
||||||
3. Bridge users.
|
|
||||||
|
|
||||||
Bridge users are like ordinary Tor users except they use encrypted
|
|
||||||
directory connections by default, and they use bridge relays as both
|
|
||||||
entry guards (their first hop) and directory guards (the source of
|
|
||||||
all their directory information).
|
|
||||||
|
|
||||||
To become a bridge user, add the following line to your torrc:
|
|
||||||
UseBridges 1
|
|
||||||
|
|
||||||
and then add at least one "Bridge" line to your torrc based on the
|
|
||||||
format below.
|
|
||||||
|
|
||||||
3.1. Format of the bridge identifier.
|
|
||||||
|
|
||||||
The canonical format for a bridge identifier contains an IP address,
|
|
||||||
an ORPort, and an identity fingerprint:
|
|
||||||
bridge 128.31.0.34:9009 4C17 FB53 2E20 B2A8 AC19 9441 ECD2 B017 7B39 E4B1
|
|
||||||
|
|
||||||
However, the identity fingerprint can be left out, in which case the
|
|
||||||
bridge user will connect to that relay and use it as a bridge regardless
|
|
||||||
of what identity key it presents:
|
|
||||||
bridge 128.31.0.34:9009
|
|
||||||
This might be useful for cases where only short bridge identifiers
|
|
||||||
can be communicated to bridge users.
|
|
||||||
|
|
||||||
In a future version we may also support bridge identifiers that are
|
|
||||||
only a key fingerprint:
|
|
||||||
bridge 4C17 FB53 2E20 B2A8 AC19 9441 ECD2 B017 7B39 E4B1
|
|
||||||
and the bridge user can fetch the latest descriptor from the bridge
|
|
||||||
authority (see Section 3.4).
|
|
||||||
|
|
||||||
3.2. Bridges as entry guards
|
|
||||||
|
|
||||||
For now, bridge users add their bridge relays to their list of "entry
|
|
||||||
guards" (see path-spec.txt for background on entry guards). They are
|
|
||||||
managed by the entry guard algorithms exactly as if they were a normal
|
|
||||||
entry guard -- their keys and timing get cached in the "state" file,
|
|
||||||
etc. This means that when the Tor user starts up with "UseBridges"
|
|
||||||
disabled, he will skip past the bridge entries since they won't be
|
|
||||||
listed as up and usable in his networkstatus consensus. But to be clear,
|
|
||||||
the "entry_guards" list doesn't currently distinguish guards by purpose.
|
|
||||||
|
|
||||||
Internally, each bridge user keeps a smartlist of "bridge_info_t"
|
|
||||||
that reflects the "bridge" lines from his torrc along with a download
|
|
||||||
schedule (see Section 3.5 below). When he starts Tor, he attempts
|
|
||||||
to fetch a descriptor for each configured bridge (see Section 3.4
|
|
||||||
below). When he succeeds at getting a descriptor for one of the bridges
|
|
||||||
in his list, he adds it directly to the entry guard list using the
|
|
||||||
normal add_an_entry_guard() interface. Once a bridge descriptor has
|
|
||||||
been added, should_delay_dir_fetches() will stop delaying further
|
|
||||||
directory fetches, and the user begins to bootstrap his directory
|
|
||||||
information from that bridge (see Section 3.3).
|
|
||||||
|
|
||||||
Currently bridge users cache their bridge descriptors to the
|
|
||||||
"cached-descriptors" file (annotated with purpose "bridge"), but
|
|
||||||
they don't make any attempt to reuse descriptors they find in this
|
|
||||||
file. The theory is that either the bridge is available now, in which
|
|
||||||
case you can get a fresh descriptor, or it's not, in which case an
|
|
||||||
old descriptor won't do you much good.
|
|
||||||
|
|
||||||
We could disable writing out the bridge lines to the state file, if
|
|
||||||
we think this is a problem.
|
|
||||||
|
|
||||||
As an exception, if we get an application request when we have one
|
|
||||||
or more bridge descriptors but we believe none of them are running,
|
|
||||||
we mark them all as running again. This is similar to the exception
|
|
||||||
already in place to help long-idle Tor clients realize they should
|
|
||||||
fetch fresh directory information rather than just refuse requests.
|
|
||||||
|
|
||||||
3.3. Bridges as directory guards
|
|
||||||
|
|
||||||
In addition to using bridges as the first hop in their circuits, bridge
|
|
||||||
users also use them to fetch directory updates. Other than initial
|
|
||||||
bootstrapping to find a working bridge descriptor (see Section 3.4
|
|
||||||
below), all further non-anonymized directory fetches will be redirected
|
|
||||||
to the bridge.
|
|
||||||
|
|
||||||
This means that bridge relays need to have cached answers for all
|
|
||||||
questions the bridge user might ask. This makes the upgrade path
|
|
||||||
tricky --- for example, if we migrate to a v4 directory design, the
|
|
||||||
bridge user would need to keep using v3 so long as his bridge relays
|
|
||||||
only knew how to answer v3 queries.
|
|
||||||
|
|
||||||
In a future design, for cases where the user has enough information
|
|
||||||
to build circuits yet the chosen bridge doesn't know how to answer a
|
|
||||||
given query, we might teach bridge users to make an anonymized request
|
|
||||||
to a more suitable directory server.
|
|
||||||
|
|
||||||
3.4. How bridge users get their bridge descriptor
|
|
||||||
|
|
||||||
Bridge users can fetch bridge descriptors in two ways: by going directly
|
|
||||||
to the bridge and asking for "/tor/server/authority", or by going to
|
|
||||||
the bridge authority and asking for "/tor/server/fp/ID". By default,
|
|
||||||
they will only try the direct queries. If the user sets
|
|
||||||
UpdateBridgesFromAuthority 1
|
|
||||||
in his config file, then he will try querying the bridge authority
|
|
||||||
first for bridges where he knows a digest (if he only knows an IP
|
|
||||||
address and ORPort, then his only option is a direct query).
|
|
||||||
|
|
||||||
If the user has at least one working bridge, then he will do further
|
|
||||||
queries to the bridge authority through a full three-hop Tor circuit.
|
|
||||||
But when bootstrapping, he will make a direct begin_dir-style connection
|
|
||||||
to the bridge authority.
|
|
||||||
|
|
||||||
As of Tor 0.2.0.10-alpha, if the user attempts to fetch a descriptor
|
|
||||||
from the bridge authority and it returns a 404 not found, the user
|
|
||||||
will automatically fall back to trying a direct query. Therefore it is
|
|
||||||
recommended that bridge users always set UpdateBridgesFromAuthority,
|
|
||||||
since at worst it will delay their fetches a little bit and notify
|
|
||||||
the bridge authority of the identity fingerprint (but not location)
|
|
||||||
of their intended bridges.
|
|
||||||
|
|
||||||
3.5. Bridge descriptor retry schedule
|
|
||||||
|
|
||||||
Bridge users try to fetch a descriptor for each bridge (using the
|
|
||||||
steps in Section 3.4 above) on startup. Whenever they receive a
|
|
||||||
bridge descriptor, they reschedule a new descriptor download for 1
|
|
||||||
hour from then.
|
|
||||||
|
|
||||||
If on the other hand it fails, they try again after 15 minutes for the
|
|
||||||
first attempt, after 15 minutes for the second attempt, and after 60
|
|
||||||
minutes for subsequent attempts.
|
|
||||||
|
|
||||||
In 0.2.2.x we should come up with some smarter retry schedules.
|
|
||||||
|
|
||||||
3.6. Implementation note.
|
|
||||||
|
|
||||||
Vidalia 0.1.0 has a new checkbox in its Network config window called
|
|
||||||
"My ISP blocks connections to the Tor network." Users who click that
|
|
||||||
box change their configuration to:
|
|
||||||
UseBridges 1
|
|
||||||
UpdateBridgesFromAuthority 1
|
|
||||||
and should add at least one bridge identifier.
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,437 +0,0 @@
|
|||||||
|
|
||||||
Tor Path Specification
|
|
||||||
|
|
||||||
Roger Dingledine
|
|
||||||
Nick Mathewson
|
|
||||||
|
|
||||||
Note: This is an attempt to specify Tor as currently implemented. Future
|
|
||||||
versions of Tor will implement improved algorithms.
|
|
||||||
|
|
||||||
This document tries to cover how Tor chooses to build circuits and assign
|
|
||||||
streams to circuits. Other implementations MAY take other approaches, but
|
|
||||||
implementors should be aware of the anonymity and load-balancing implications
|
|
||||||
of their choices.
|
|
||||||
|
|
||||||
THIS SPEC ISN'T DONE YET.
|
|
||||||
|
|
||||||
1. General operation
|
|
||||||
|
|
||||||
Tor begins building circuits as soon as it has enough directory
|
|
||||||
information to do so (see section 5 of dir-spec.txt). Some circuits are
|
|
||||||
built preemptively because we expect to need them later (for user
|
|
||||||
traffic), and some are built because of immediate need (for user traffic
|
|
||||||
that no current circuit can handle, for testing the network or our
|
|
||||||
reachability, and so on).
|
|
||||||
|
|
||||||
When a client application creates a new stream (by opening a SOCKS
|
|
||||||
connection or launching a resolve request), we attach it to an appropriate
|
|
||||||
open circuit if one exists, or wait if an appropriate circuit is
|
|
||||||
in-progress. We launch a new circuit only
|
|
||||||
if no current circuit can handle the request. We rotate circuits over
|
|
||||||
time to avoid some profiling attacks.
|
|
||||||
|
|
||||||
To build a circuit, we choose all the nodes we want to use, and then
|
|
||||||
construct the circuit. Sometimes, when we want a circuit that ends at a
|
|
||||||
given hop, and we have an appropriate unused circuit, we "cannibalize" the
|
|
||||||
existing circuit and extend it to the new terminus.
|
|
||||||
|
|
||||||
These processes are described in more detail below.
|
|
||||||
|
|
||||||
This document describes Tor's automatic path selection logic only; path
|
|
||||||
selection can be overridden by a controller (with the EXTENDCIRCUIT and
|
|
||||||
ATTACHSTREAM commands). Paths constructed through these means may
|
|
||||||
violate some constraints given below.
|
|
||||||
|
|
||||||
1.1. Terminology
|
|
||||||
|
|
||||||
A "path" is an ordered sequence of nodes, not yet built as a circuit.
|
|
||||||
|
|
||||||
A "clean" circuit is one that has not yet been used for any traffic.
|
|
||||||
|
|
||||||
A "fast" or "stable" or "valid" node is one that has the 'Fast' or
|
|
||||||
'Stable' or 'Valid' flag
|
|
||||||
set respectively, based on our current directory information. A "fast"
|
|
||||||
or "stable" circuit is one consisting only of "fast" or "stable" nodes.
|
|
||||||
|
|
||||||
In an "exit" circuit, the final node is chosen based on waiting stream
|
|
||||||
requests if any, and in any case it avoids nodes with exit policy of
|
|
||||||
"reject *:*". An "internal" circuit, on the other hand, is one where
|
|
||||||
the final node is chosen just like a middle node (ignoring its exit
|
|
||||||
policy).
|
|
||||||
|
|
||||||
A "request" is a client-side stream or DNS resolve that needs to be
|
|
||||||
served by a circuit.
|
|
||||||
|
|
||||||
A "pending" circuit is one that we have started to build, but which has
|
|
||||||
not yet completed.
|
|
||||||
|
|
||||||
A circuit or path "supports" a request if it is okay to use the
|
|
||||||
circuit/path to fulfill the request, according to the rules given below.
|
|
||||||
A circuit or path "might support" a request if some aspect of the request
|
|
||||||
is unknown (usually its target IP), but we believe the path probably
|
|
||||||
supports the request according to the rules given below.
|
|
||||||
|
|
||||||
1.1. A server's bandwidth
|
|
||||||
|
|
||||||
Old versions of Tor did not report bandwidths in network status
|
|
||||||
documents, so clients had to learn them from the routers' advertised
|
|
||||||
server descriptors.
|
|
||||||
|
|
||||||
For versions of Tor prior to 0.2.1.17-rc, everywhere below where we
|
|
||||||
refer to a server's "bandwidth", we mean its clipped advertised
|
|
||||||
bandwidth, computed by taking the smaller of the 'rate' and
|
|
||||||
'observed' arguments to the "bandwidth" element in the server's
|
|
||||||
descriptor. If a router's advertised bandwidth is greater than
|
|
||||||
MAX_BELIEVABLE_BANDWIDTH (currently 10 MB/s), we clipped to that
|
|
||||||
value.
|
|
||||||
|
|
||||||
For more recent versions of Tor, we take the bandwidth value declared
|
|
||||||
in the consensus, and fall back to the clipped advertised bandwidth
|
|
||||||
only if the consensus does not have bandwidths listed.
|
|
||||||
|
|
||||||
2. Building circuits
|
|
||||||
|
|
||||||
2.1. When we build
|
|
||||||
|
|
||||||
2.1.1. Clients build circuits preemptively
|
|
||||||
|
|
||||||
When running as a client, Tor tries to maintain at least a certain
|
|
||||||
number of clean circuits, so that new streams can be handled
|
|
||||||
quickly. To increase the likelihood of success, Tor tries to
|
|
||||||
predict what circuits will be useful by choosing from among nodes
|
|
||||||
that support the ports we have used in the recent past (by default
|
|
||||||
one hour). Specifically, on startup Tor tries to maintain one clean
|
|
||||||
fast exit circuit that allows connections to port 80, and at least
|
|
||||||
two fast clean stable internal circuits in case we get a resolve
|
|
||||||
request or hidden service request (at least three if we _run_ a
|
|
||||||
hidden service).
|
|
||||||
|
|
||||||
After that, Tor will adapt the circuits that it preemptively builds
|
|
||||||
based on the requests it sees from the user: it tries to have two fast
|
|
||||||
clean exit circuits available for every port seen within the past hour
|
|
||||||
(each circuit can be adequate for many predicted ports -- it doesn't
|
|
||||||
need two separate circuits for each port), and it tries to have the
|
|
||||||
above internal circuits available if we've seen resolves or hidden
|
|
||||||
service activity within the past hour. If there are 12 or more clean
|
|
||||||
circuits open, it doesn't open more even if it has more predictions.
|
|
||||||
|
|
||||||
Only stable circuits can "cover" a port that is listed in the
|
|
||||||
LongLivedPorts config option. Similarly, hidden service requests
|
|
||||||
to ports listed in LongLivedPorts make us create stable internal
|
|
||||||
circuits.
|
|
||||||
|
|
||||||
Note that if there are no requests from the user for an hour, Tor
|
|
||||||
will predict no use and build no preemptive circuits.
|
|
||||||
|
|
||||||
The Tor client SHOULD NOT store its list of predicted requests to a
|
|
||||||
persistent medium.
|
|
||||||
|
|
||||||
2.1.2. Clients build circuits on demand
|
|
||||||
|
|
||||||
Additionally, when a client request exists that no circuit (built or
|
|
||||||
pending) might support, we create a new circuit to support the request.
|
|
||||||
For exit connections, we pick an exit node that will handle the
|
|
||||||
most pending requests (choosing arbitrarily among ties), launch a
|
|
||||||
circuit to end there, and repeat until every unattached request
|
|
||||||
might be supported by a pending or built circuit. For internal
|
|
||||||
circuits, we pick an arbitrary acceptable path, repeating as needed.
|
|
||||||
|
|
||||||
In some cases we can reuse an already established circuit if it's
|
|
||||||
clean; see Section 2.3 (cannibalizing circuits) for details.
|
|
||||||
|
|
||||||
2.1.3. Servers build circuits for testing reachability and bandwidth
|
|
||||||
|
|
||||||
Tor servers test reachability of their ORPort once they have
|
|
||||||
successfully built a circuit (on start and whenever their IP address
|
|
||||||
changes). They build an ordinary fast internal circuit with themselves
|
|
||||||
as the last hop. As soon as any testing circuit succeeds, the Tor
|
|
||||||
server decides it's reachable and is willing to publish a descriptor.
|
|
||||||
|
|
||||||
We launch multiple testing circuits (one at a time), until we
|
|
||||||
have NUM_PARALLEL_TESTING_CIRC (4) such circuits open. Then we
|
|
||||||
do a "bandwidth test" by sending a certain number of relay drop
|
|
||||||
cells down each circuit: BandwidthRate * 10 / CELL_NETWORK_SIZE
|
|
||||||
total cells divided across the four circuits, but never more than
|
|
||||||
CIRCWINDOW_START (1000) cells total. This exercises both outgoing and
|
|
||||||
incoming bandwidth, and helps to jumpstart the observed bandwidth
|
|
||||||
(see dir-spec.txt).
|
|
||||||
|
|
||||||
Tor servers also test reachability of their DirPort once they have
|
|
||||||
established a circuit, but they use an ordinary exit circuit for
|
|
||||||
this purpose.
|
|
||||||
|
|
||||||
2.1.4. Hidden-service circuits
|
|
||||||
|
|
||||||
See section 4 below.
|
|
||||||
|
|
||||||
2.1.5. Rate limiting of failed circuits
|
|
||||||
|
|
||||||
If we fail to build a circuit N times in a X second period (see Section
|
|
||||||
2.3 for how this works), we stop building circuits until the X seconds
|
|
||||||
have elapsed.
|
|
||||||
XXXX
|
|
||||||
|
|
||||||
2.1.6. When to tear down circuits
|
|
||||||
|
|
||||||
XXXX
|
|
||||||
|
|
||||||
2.2. Path selection and constraints
|
|
||||||
|
|
||||||
We choose the path for each new circuit before we build it. We choose the
|
|
||||||
exit node first, followed by the other nodes in the circuit. All paths
|
|
||||||
we generate obey the following constraints:
|
|
||||||
- We do not choose the same router twice for the same path.
|
|
||||||
- We do not choose any router in the same family as another in the same
|
|
||||||
path.
|
|
||||||
- We do not choose more than one router in a given /16 subnet
|
|
||||||
(unless EnforceDistinctSubnets is 0).
|
|
||||||
- We don't choose any non-running or non-valid router unless we have
|
|
||||||
been configured to do so. By default, we are configured to allow
|
|
||||||
non-valid routers in "middle" and "rendezvous" positions.
|
|
||||||
- If we're using Guard nodes, the first node must be a Guard (see 5
|
|
||||||
below)
|
|
||||||
- XXXX Choosing the length
|
|
||||||
|
|
||||||
For circuits that do not need to be "fast", when choosing among
|
|
||||||
multiple candidates for a path element, we choose randomly.
|
|
||||||
|
|
||||||
For "fast" circuits, we pick a given router as an exit with probability
|
|
||||||
proportional to its bandwidth.
|
|
||||||
|
|
||||||
For non-exit positions on "fast" circuits, we pick routers as above, but
|
|
||||||
we weight the bandwidth of Exit-flagged nodes depending
|
|
||||||
on the fraction of bandwidth available from non-Exit nodes. Call the
|
|
||||||
total bandwidth for Exit nodes under consideration E,
|
|
||||||
and the total bandwidth for all nodes under
|
|
||||||
consideration T. If E<T/3, we do not consider Exit-flagged nodes.
|
|
||||||
Otherwise, we weight their bandwidth with the factor (E-T/3)/E. This
|
|
||||||
ensures that bandwidth is evenly distributed over nodes in 3-hop paths.
|
|
||||||
|
|
||||||
Similarly, guard nodes are weighted by the factor (G-T/3)/G, and not
|
|
||||||
considered for non-guard positions if this value is less than 0.
|
|
||||||
|
|
||||||
Additionally, we may be building circuits with one or more requests in
|
|
||||||
mind. Each kind of request puts certain constraints on paths:
|
|
||||||
|
|
||||||
- All service-side introduction circuits and all rendezvous paths
|
|
||||||
should be Stable.
|
|
||||||
- All connection requests for connections that we think will need to
|
|
||||||
stay open a long time require Stable circuits. Currently, Tor decides
|
|
||||||
this by examining the request's target port, and comparing it to a
|
|
||||||
list of "long-lived" ports. (Default: 21, 22, 706, 1863, 5050,
|
|
||||||
5190, 5222, 5223, 6667, 6697, 8300.)
|
|
||||||
- DNS resolves require an exit node whose exit policy is not equivalent
|
|
||||||
to "reject *:*".
|
|
||||||
- Reverse DNS resolves require a version of Tor with advertised eventdns
|
|
||||||
support (available in Tor 0.1.2.1-alpha-dev and later).
|
|
||||||
- All connection requests require an exit node whose exit policy
|
|
||||||
supports their target address and port (if known), or which "might
|
|
||||||
support it" (if the address isn't known). See 2.2.1.
|
|
||||||
- Rules for Fast? XXXXX
|
|
||||||
|
|
||||||
2.2.1. Choosing an exit
|
|
||||||
|
|
||||||
If we know what IP address we want to connect to or resolve, we can
|
|
||||||
trivially tell whether a given router will support it by simulating
|
|
||||||
its declared exit policy.
|
|
||||||
|
|
||||||
Because we often connect to addresses of the form hostname:port, we do not
|
|
||||||
always know the target IP address when we select an exit node. In these
|
|
||||||
cases, we need to pick an exit node that "might support" connections to a
|
|
||||||
given address port with an unknown address. An exit node "might support"
|
|
||||||
such a connection if any clause that accepts any connections to that port
|
|
||||||
precedes all clauses (if any) that reject all connections to that port.
|
|
||||||
|
|
||||||
Unless requested to do so by the user, we never choose an exit server
|
|
||||||
flagged as "BadExit" by more than half of the authorities who advertise
|
|
||||||
themselves as listing bad exits.
|
|
||||||
|
|
||||||
2.2.2. User configuration
|
|
||||||
|
|
||||||
Users can alter the default behavior for path selection with configuration
|
|
||||||
options.
|
|
||||||
|
|
||||||
- If "ExitNodes" is provided, then every request requires an exit node on
|
|
||||||
the ExitNodes list. (If a request is supported by no nodes on that list,
|
|
||||||
and StrictExitNodes is false, then Tor treats that request as if
|
|
||||||
ExitNodes were not provided.)
|
|
||||||
|
|
||||||
- "EntryNodes" and "StrictEntryNodes" behave analogously.
|
|
||||||
|
|
||||||
- If a user tries to connect to or resolve a hostname of the form
|
|
||||||
<target>.<servername>.exit, the request is rewritten to a request for
|
|
||||||
<target>, and the request is only supported by the exit whose nickname
|
|
||||||
or fingerprint is <servername>.
|
|
||||||
|
|
||||||
2.3. Cannibalizing circuits
|
|
||||||
|
|
||||||
If we need a circuit and have a clean one already established, in
|
|
||||||
some cases we can adapt the clean circuit for our new
|
|
||||||
purpose. Specifically,
|
|
||||||
|
|
||||||
For hidden service interactions, we can "cannibalize" a clean internal
|
|
||||||
circuit if one is available, so we don't need to build those circuits
|
|
||||||
from scratch on demand.
|
|
||||||
|
|
||||||
We can also cannibalize clean circuits when the client asks to exit
|
|
||||||
at a given node -- either via the ".exit" notation or because the
|
|
||||||
destination is running at the same location as an exit node.
|
|
||||||
|
|
||||||
|
|
||||||
2.4. Handling failure
|
|
||||||
|
|
||||||
If an attempt to extend a circuit fails (either because the first create
|
|
||||||
failed or a subsequent extend failed) then the circuit is torn down and is
|
|
||||||
no longer pending. (XXXX really?) Requests that might have been
|
|
||||||
supported by the pending circuit thus become unsupported, and a new
|
|
||||||
circuit needs to be constructed.
|
|
||||||
|
|
||||||
If a stream "begin" attempt fails with an EXITPOLICY error, we
|
|
||||||
decide that the exit node's exit policy is not correctly advertised,
|
|
||||||
so we treat the exit node as if it were a non-exit until we retrieve
|
|
||||||
a fresh descriptor for it.
|
|
||||||
|
|
||||||
XXXX
|
|
||||||
|
|
||||||
3. Attaching streams to circuits
|
|
||||||
|
|
||||||
When a circuit that might support a request is built, Tor tries to attach
|
|
||||||
the request's stream to the circuit and sends a BEGIN, BEGIN_DIR,
|
|
||||||
or RESOLVE relay
|
|
||||||
cell as appropriate. If the request completes unsuccessfully, Tor
|
|
||||||
considers the reason given in the CLOSE relay cell. [XXX yes, and?]
|
|
||||||
|
|
||||||
|
|
||||||
After a request has remained unattached for SocksTimeout (2 minutes
|
|
||||||
by default), Tor abandons the attempt and signals an error to the
|
|
||||||
client as appropriate (e.g., by closing the SOCKS connection).
|
|
||||||
|
|
||||||
XXX Timeouts and when Tor auto-retries.
|
|
||||||
* What stream-end-reasons are appropriate for retrying.
|
|
||||||
|
|
||||||
If no reply to BEGIN/RESOLVE, then the stream will timeout and fail.
|
|
||||||
|
|
||||||
4. Hidden-service related circuits
|
|
||||||
|
|
||||||
XXX Tracking expected hidden service use (client-side and hidserv-side)
|
|
||||||
|
|
||||||
5. Guard nodes
|
|
||||||
|
|
||||||
We use Guard nodes (also called "helper nodes" in the literature) to
|
|
||||||
prevent certain profiling attacks. Here's the risk: if we choose entry and
|
|
||||||
exit nodes at random, and an attacker controls C out of N servers
|
|
||||||
(ignoring bandwidth), then the
|
|
||||||
attacker will control the entry and exit node of any given circuit with
|
|
||||||
probability (C/N)^2. But as we make many different circuits over time,
|
|
||||||
then the probability that the attacker will see a sample of about (C/N)^2
|
|
||||||
of our traffic goes to 1. Since statistical sampling works, the attacker
|
|
||||||
can be sure of learning a profile of our behavior.
|
|
||||||
|
|
||||||
If, on the other hand, we picked an entry node and held it fixed, we would
|
|
||||||
have probability C/N of choosing a bad entry and being profiled, and
|
|
||||||
probability (N-C)/N of choosing a good entry and not being profiled.
|
|
||||||
|
|
||||||
When guard nodes are enabled, Tor maintains an ordered list of entry nodes
|
|
||||||
as our chosen guards, and stores this list persistently to disk. If a Guard
|
|
||||||
node becomes unusable, rather than replacing it, Tor adds new guards to the
|
|
||||||
end of the list. When choosing the first hop of a circuit, Tor
|
|
||||||
chooses at
|
|
||||||
random from among the first NumEntryGuards (default 3) usable guards on the
|
|
||||||
list. If there are not at least 2 usable guards on the list, Tor adds
|
|
||||||
routers until there are, or until there are no more usable routers to add.
|
|
||||||
|
|
||||||
A guard is unusable if any of the following hold:
|
|
||||||
- it is not marked as a Guard by the networkstatuses,
|
|
||||||
- it is not marked Valid (and the user hasn't set AllowInvalid entry)
|
|
||||||
- it is not marked Running
|
|
||||||
- Tor couldn't reach it the last time it tried to connect
|
|
||||||
|
|
||||||
A guard is unusable for a particular circuit if any of the rules for path
|
|
||||||
selection in 2.2 are not met. In particular, if the circuit is "fast"
|
|
||||||
and the guard is not Fast, or if the circuit is "stable" and the guard is
|
|
||||||
not Stable, or if the guard has already been chosen as the exit node in
|
|
||||||
that circuit, Tor can't use it as a guard node for that circuit.
|
|
||||||
|
|
||||||
If the guard is excluded because of its status in the networkstatuses for
|
|
||||||
over 30 days, Tor removes it from the list entirely, preserving order.
|
|
||||||
|
|
||||||
If Tor fails to connect to an otherwise usable guard, it retries
|
|
||||||
periodically: every hour for six hours, every 4 hours for 3 days, every
|
|
||||||
18 hours for a week, and every 36 hours thereafter. Additionally, Tor
|
|
||||||
retries unreachable guards the first time it adds a new guard to the list,
|
|
||||||
since it is possible that the old guards were only marked as unreachable
|
|
||||||
because the network was unreachable or down.
|
|
||||||
|
|
||||||
Tor does not add a guard persistently to the list until the first time we
|
|
||||||
have connected to it successfully.
|
|
||||||
|
|
||||||
6. Router descriptor purposes
|
|
||||||
|
|
||||||
There are currently three "purposes" supported for router descriptors:
|
|
||||||
general, controller, and bridge. Most descriptors are of type general
|
|
||||||
-- these are the ones listed in the consensus, and the ones fetched
|
|
||||||
and used in normal cases.
|
|
||||||
|
|
||||||
Controller-purpose descriptors are those delivered by the controller
|
|
||||||
and labelled as such: they will be kept around (and expire like
|
|
||||||
normal descriptors), and they can be used by the controller in its
|
|
||||||
CIRCUITEXTEND commands. Otherwise they are ignored by Tor when it
|
|
||||||
chooses paths.
|
|
||||||
|
|
||||||
Bridge-purpose descriptors are for routers that are used as bridges. See
|
|
||||||
doc/design-paper/blocking.pdf for more design explanation, or proposal
|
|
||||||
125 for specific details. Currently bridge descriptors are used in place
|
|
||||||
of normal entry guards, for Tor clients that have UseBridges enabled.
|
|
||||||
|
|
||||||
|
|
||||||
X. Old notes
|
|
||||||
|
|
||||||
X.1. Do we actually do this?
|
|
||||||
|
|
||||||
How to deal with network down.
|
|
||||||
- While all helpers are down/unreachable and there are no established
|
|
||||||
or on-the-way testing circuits, launch a testing circuit. (Do this
|
|
||||||
periodically in the same way we try to establish normal circuits
|
|
||||||
when things are working normally.)
|
|
||||||
(Testing circuits are a special type of circuit, that streams won't
|
|
||||||
attach to by accident.)
|
|
||||||
- When a testing circuit succeeds, mark all helpers up and hold
|
|
||||||
the testing circuit open.
|
|
||||||
- If a connection to a helper succeeds, close all testing circuits.
|
|
||||||
Else mark that helper down and try another.
|
|
||||||
- If the last helper is marked down and we already have a testing
|
|
||||||
circuit established, then add the first hop of that testing circuit
|
|
||||||
to the end of our helper node list, close that testing circuit,
|
|
||||||
and go back to square one. (Actually, rather than closing the
|
|
||||||
testing circuit, can we get away with converting it to a normal
|
|
||||||
circuit and beginning to use it immediately?)
|
|
||||||
|
|
||||||
[Do we actually do any of the above? If so, let's spec it. If not, let's
|
|
||||||
remove it. -NM]
|
|
||||||
|
|
||||||
X.2. A thing we could do to deal with reachability.
|
|
||||||
|
|
||||||
And as a bonus, it leads to an answer to Nick's attack ("If I pick
|
|
||||||
my helper nodes all on 18.0.0.0:*, then I move, you'll know where I
|
|
||||||
bootstrapped") -- the answer is to pick your original three helper nodes
|
|
||||||
without regard for reachability. Then the above algorithm will add some
|
|
||||||
more that are reachable for you, and if you move somewhere, it's more
|
|
||||||
likely (though not certain) that some of the originals will become useful.
|
|
||||||
Is that smart or just complex?
|
|
||||||
|
|
||||||
X.3. Some stuff that worries me about entry guards. 2006 Jun, Nickm.
|
|
||||||
|
|
||||||
It is unlikely for two users to have the same set of entry guards.
|
|
||||||
Observing a user is sufficient to learn its entry guards. So, as we move
|
|
||||||
around, entry guards make us linkable. If we want to change guards when
|
|
||||||
our location (IP? subnet?) changes, we have two bad options. We could
|
|
||||||
- Drop the old guards. But if we go back to our old location,
|
|
||||||
we'll not use our old guards. For a laptop that sometimes gets used
|
|
||||||
from work and sometimes from home, this is pretty fatal.
|
|
||||||
- Remember the old guards as associated with the old location, and use
|
|
||||||
them again if we ever go back to the old location. This would be
|
|
||||||
nasty, since it would force us to record where we've been.
|
|
||||||
|
|
||||||
[Do we do any of this now? If not, this should move into 099-misc or
|
|
||||||
098-todo. -NM]
|
|
||||||
|
|
@ -1,751 +0,0 @@
|
|||||||
|
|
||||||
Tor Rendezvous Specification
|
|
||||||
|
|
||||||
0. Overview and preliminaries
|
|
||||||
|
|
||||||
Read
|
|
||||||
https://www.torproject.org/doc/design-paper/tor-design.html#sec:rendezvous
|
|
||||||
before you read this specification. It will make more sense.
|
|
||||||
|
|
||||||
Rendezvous points provide location-hidden services (server
|
|
||||||
anonymity) for the onion routing network. With rendezvous points,
|
|
||||||
Bob can offer a TCP service (say, a webserver) via the onion
|
|
||||||
routing network, without revealing the IP of that service.
|
|
||||||
|
|
||||||
Bob does this by anonymously advertising a public key for his
|
|
||||||
service, along with a list of onion routers to act as "Introduction
|
|
||||||
Points" for his service. He creates forward circuits to those
|
|
||||||
introduction points, and tells them about his public key. To
|
|
||||||
connect to Bob, Alice first builds a circuit to an OR to act as
|
|
||||||
her "Rendezvous Point." She then connects to one of Bob's chosen
|
|
||||||
introduction points, optionally provides authentication or
|
|
||||||
authorization information, and asks it to tell him about her Rendezvous
|
|
||||||
Point (RP). If Bob chooses to answer, he builds a circuit to her
|
|
||||||
RP, and tells it to connect him to Alice. The RP joins their
|
|
||||||
circuits together, and begins relaying cells. Alice's 'BEGIN'
|
|
||||||
cells are received directly by Bob's OP, which passes data to
|
|
||||||
and from the local server implementing Bob's service.
|
|
||||||
|
|
||||||
Below we describe a network-level specification of this service,
|
|
||||||
along with interfaces to make this process transparent to Alice
|
|
||||||
(so long as she is using an OP).
|
|
||||||
|
|
||||||
0.1. Notation, conventions and prerequisites
|
|
||||||
|
|
||||||
In the specifications below, we use the same notation and terminology
|
|
||||||
as in "tor-spec.txt". The service specified here also requires the
|
|
||||||
existence of an onion routing network as specified in that file.
|
|
||||||
|
|
||||||
H(x) is a SHA1 digest of x.
|
|
||||||
PKSign(SK,x) is a PKCS.1-padded RSA signature of x with SK.
|
|
||||||
PKEncrypt(SK,x) is a PKCS.1-padded RSA encryption of x with SK.
|
|
||||||
Public keys are all RSA, and encoded in ASN.1.
|
|
||||||
All integers are stored in network (big-endian) order.
|
|
||||||
All symmetric encryption uses AES in counter mode, except where
|
|
||||||
otherwise noted.
|
|
||||||
|
|
||||||
In all discussions, "Alice" will refer to a user connecting to a
|
|
||||||
location-hidden service, and "Bob" will refer to a user running a
|
|
||||||
location-hidden service.
|
|
||||||
|
|
||||||
An OP is (as defined elsewhere) an "Onion Proxy" or Tor client.
|
|
||||||
|
|
||||||
An OR is (as defined elsewhere) an "Onion Router" or Tor server.
|
|
||||||
|
|
||||||
An "Introduction point" is a Tor server chosen to be Bob's medium-term
|
|
||||||
'meeting place'. A "Rendezvous point" is a Tor server chosen by Alice to
|
|
||||||
be a short-term communication relay between her and Bob. All Tor servers
|
|
||||||
potentially act as introduction and rendezvous points.
|
|
||||||
|
|
||||||
0.2. Protocol outline
|
|
||||||
|
|
||||||
1. Bob->Bob's OP: "Offer IP:Port as
|
|
||||||
public-key-name:Port". [configuration]
|
|
||||||
(We do not specify this step; it is left to the implementor of
|
|
||||||
Bob's OP.)
|
|
||||||
|
|
||||||
2. Bob's OP generates keypair and rendezvous service descriptor:
|
|
||||||
"Meet public-key X at introduction point A, B, or C." (signed)
|
|
||||||
|
|
||||||
3. Bob's OP->Introduction point via Tor: [introduction setup]
|
|
||||||
"This pk is me."
|
|
||||||
|
|
||||||
4. Bob's OP->directory service via Tor: publishes Bob's service
|
|
||||||
descriptor [advertisement]
|
|
||||||
|
|
||||||
5. Out of band, Alice receives a [x.y.]z.onion:port address.
|
|
||||||
She opens a SOCKS connection to her OP, and requests
|
|
||||||
x.y.z.onion:port.
|
|
||||||
|
|
||||||
6. Alice's OP retrieves Bob's descriptor via Tor. [descriptor lookup.]
|
|
||||||
|
|
||||||
7. Alice's OP chooses a rendezvous point, opens a circuit to that
|
|
||||||
rendezvous point, and establishes a rendezvous circuit. [rendezvous
|
|
||||||
setup.]
|
|
||||||
|
|
||||||
8. Alice connects to the Introduction point via Tor, and tells it about
|
|
||||||
her rendezvous point and optional authentication/authorization
|
|
||||||
information. (Encrypted to Bob.) [Introduction 1]
|
|
||||||
|
|
||||||
9. The Introduction point passes this on to Bob's OP via Tor, along the
|
|
||||||
introduction circuit. [Introduction 2]
|
|
||||||
|
|
||||||
10. Bob's OP decides whether to connect to Alice, and if so, creates a
|
|
||||||
circuit to Alice's RP via Tor. Establishes a shared circuit.
|
|
||||||
[Rendezvous.]
|
|
||||||
|
|
||||||
11. Alice's OP sends begin cells to Bob's OP. [Connection]
|
|
||||||
|
|
||||||
0.3. Constants and new cell types
|
|
||||||
|
|
||||||
Relay cell types
|
|
||||||
32 -- RELAY_ESTABLISH_INTRO
|
|
||||||
33 -- RELAY_ESTABLISH_RENDEZVOUS
|
|
||||||
34 -- RELAY_INTRODUCE1
|
|
||||||
35 -- RELAY_INTRODUCE2
|
|
||||||
36 -- RELAY_RENDEZVOUS1
|
|
||||||
37 -- RELAY_RENDEZVOUS2
|
|
||||||
38 -- RELAY_INTRO_ESTABLISHED
|
|
||||||
39 -- RELAY_RENDEZVOUS_ESTABLISHED
|
|
||||||
40 -- RELAY_COMMAND_INTRODUCE_ACK
|
|
||||||
|
|
||||||
0.4. Version overview
|
|
||||||
|
|
||||||
There are several parts in the hidden service protocol that have
|
|
||||||
changed over time, each of them having its own version number, whereas
|
|
||||||
other parts remained the same. The following list of potentially
|
|
||||||
versioned protocol parts should help reduce some confusion:
|
|
||||||
|
|
||||||
- Hidden service descriptor: the binary-based v0 was the default for
|
|
||||||
a long time, and an ascii-based v2 has been added by proposal
|
|
||||||
114. See 1.2.
|
|
||||||
|
|
||||||
- Hidden service descriptor propagation mechanism: currently related to
|
|
||||||
the hidden service descriptor version -- v0 publishes to the original
|
|
||||||
hs directory authorities, whereas v2 publishes to a rotating subset
|
|
||||||
of relays with the "hsdir" flag; see 1.4 and 1.6.
|
|
||||||
|
|
||||||
- Introduction protocol for how to generate an introduction cell:
|
|
||||||
v0 specified a nickname for the rendezvous point and assumed the
|
|
||||||
relay would know about it, whereas v2 now specifies IP address,
|
|
||||||
port, and onion key so the relay doesn't need to already recognize
|
|
||||||
it. See 1.8.
|
|
||||||
|
|
||||||
1. The Protocol
|
|
||||||
|
|
||||||
1.1. Bob configures his local OP.
|
|
||||||
|
|
||||||
We do not specify a format for the OP configuration file. However,
|
|
||||||
OPs SHOULD allow Bob to provide more than one advertised service
|
|
||||||
per OP, and MUST allow Bob to specify one or more virtual ports per
|
|
||||||
service. Bob provides a mapping from each of these virtual ports
|
|
||||||
to a local IP:Port pair.
|
|
||||||
|
|
||||||
1.2. Bob's OP generates service descriptors.
|
|
||||||
|
|
||||||
The first time the OP provides an advertised service, it generates
|
|
||||||
a public/private keypair (stored locally).
|
|
||||||
|
|
||||||
Beginning with 0.2.0.10-alpha, Bob's OP encodes "V2" descriptors. The
|
|
||||||
format of a "V2" descriptor is as follows:
|
|
||||||
|
|
||||||
"rendezvous-service-descriptor" descriptor-id NL
|
|
||||||
|
|
||||||
[At start, exactly once]
|
|
||||||
|
|
||||||
Indicates the beginning of the descriptor. "descriptor-id" is a
|
|
||||||
periodically changing identifier of 160 bits formatted as 32 base32
|
|
||||||
chars that is calculated by the hidden service and its clients. If
|
|
||||||
the optional "descriptor-cookie" is used, this "descriptor-id"
|
|
||||||
cannot be computed by anyone else. (Everyone can verify that this
|
|
||||||
"descriptor-id" belongs to the rest of the descriptor, even without
|
|
||||||
knowing the optional "descriptor-cookie", as described below.) The
|
|
||||||
"descriptor-id" is calculated by performing the following operation:
|
|
||||||
|
|
||||||
descriptor-id =
|
|
||||||
H(permanent-id | H(time-period | descriptor-cookie | replica))
|
|
||||||
|
|
||||||
"permanent-id" is the permanent identifier of the hidden service,
|
|
||||||
consisting of 80 bits. It can be calculated by computing the hash value
|
|
||||||
of the public hidden service key and truncating after the first 80 bits:
|
|
||||||
|
|
||||||
permanent-id = H(public-key)[:10]
|
|
||||||
|
|
||||||
"H(time-period | descriptor-cookie | replica)" is the (possibly
|
|
||||||
secret) id part that is
|
|
||||||
necessary to verify that the hidden service is the true originator
|
|
||||||
of this descriptor. It can only be created by the hidden service
|
|
||||||
and its clients, but the "signature" below can only be created by
|
|
||||||
the service.
|
|
||||||
|
|
||||||
"descriptor-cookie" is an optional secret password of 128 bits that
|
|
||||||
is shared between the hidden service provider and its clients.
|
|
||||||
|
|
||||||
"replica" denotes the number of the non-consecutive replica.
|
|
||||||
|
|
||||||
(Each descriptor is replicated on a number of _consecutive_ nodes
|
|
||||||
in the identifier ring by making every storing node responsible
|
|
||||||
for the identifier intervals starting from its 3rd predecessor's
|
|
||||||
ID to its own ID. In addition to that, every service publishes
|
|
||||||
multiple descriptors with different descriptor IDs in order to
|
|
||||||
distribute them to different places on the ring. Therefore,
|
|
||||||
"replica" chooses one of the _non-consecutive_ replicas. -KL)
|
|
||||||
|
|
||||||
The "time-period" changes periodically depending on the global time and
|
|
||||||
as a function of "permanent-id". The current value for "time-period" can
|
|
||||||
be calculated using the following formula:
|
|
||||||
|
|
||||||
time-period = (current-time + permanent-id-byte * 86400 / 256)
|
|
||||||
/ 86400
|
|
||||||
|
|
||||||
"current-time" contains the current system time in seconds since
|
|
||||||
1970-01-01 00:00, e.g. 1188241957. "permanent-id-byte" is the first
|
|
||||||
(unsigned) byte of the permanent identifier (which is in network
|
|
||||||
order), e.g. 143. Adding the product of "permanent-id-byte" and
|
|
||||||
86400 (seconds per day), divided by 256, prevents "time-period" from
|
|
||||||
changing for all descriptors at the same time of the day. The result
|
|
||||||
of the overall operation is a (network-ordered) 32-bit integer, e.g.
|
|
||||||
13753 or 0x000035B9 with the example values given above.
|
|
||||||
|
|
||||||
"version" version-number NL
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The version number of this descriptor's format. In this case: 2.
|
|
||||||
|
|
||||||
"permanent-key" NL a public key in PEM format
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The public key of the hidden service which is required to verify the
|
|
||||||
"descriptor-id" and the "signature".
|
|
||||||
|
|
||||||
"secret-id-part" secret-id-part NL
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The result of the following operation as explained above, formatted as
|
|
||||||
32 base32 chars. Using this secret id part, everyone can verify that
|
|
||||||
the signed descriptor belongs to "descriptor-id".
|
|
||||||
|
|
||||||
secret-id-part = H(time-period | descriptor-cookie | replica)
|
|
||||||
|
|
||||||
"publication-time" YYYY-MM-DD HH:MM:SS NL
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
A timestamp when this descriptor has been created.
|
|
||||||
|
|
||||||
"protocol-versions" version-string NL
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
A comma-separated list of recognized and permitted version numbers
|
|
||||||
for use in INTRODUCE cells; these versions are described in section
|
|
||||||
1.8 below.
|
|
||||||
|
|
||||||
"introduction-points" NL encrypted-string
|
|
||||||
|
|
||||||
[At most once]
|
|
||||||
|
|
||||||
A list of introduction points. If the optional "descriptor-cookie" is
|
|
||||||
used, this list is encrypted with AES in CTR mode with a random
|
|
||||||
initialization vector of 128 bits that is written to
|
|
||||||
the beginning of the encrypted string, and the "descriptor-cookie" as
|
|
||||||
secret key of 128 bits length.
|
|
||||||
|
|
||||||
The string containing the introduction point data (either encrypted
|
|
||||||
or not) is encoded in base64, and surrounded with
|
|
||||||
"-----BEGIN MESSAGE-----" and "-----END MESSAGE-----".
|
|
||||||
|
|
||||||
The unencrypted string may begin with:
|
|
||||||
|
|
||||||
["service-authentication" auth-type NL auth-data ... reserved]
|
|
||||||
|
|
||||||
[At start, any number]
|
|
||||||
|
|
||||||
The service-specific authentication data can be used to perform
|
|
||||||
client authentication. This data is independent of the selected
|
|
||||||
introduction point as opposed to "intro-authentication" below.
|
|
||||||
|
|
||||||
Subsequently, an arbitrary number of introduction point entries may
|
|
||||||
follow, each containing the following data:
|
|
||||||
|
|
||||||
"introduction-point" identifier NL
|
|
||||||
|
|
||||||
[At start, exactly once]
|
|
||||||
|
|
||||||
The identifier of this introduction point: the base-32 encoded
|
|
||||||
hash of this introduction point's identity key.
|
|
||||||
|
|
||||||
"ip-address" ip-address NL
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The IP address of this introduction point.
|
|
||||||
|
|
||||||
"onion-port" port NL
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The TCP port on which the introduction point is listening for
|
|
||||||
incoming onion requests.
|
|
||||||
|
|
||||||
"onion-key" NL a public key in PEM format
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The public key that can be used to encrypt messages to this
|
|
||||||
introduction point.
|
|
||||||
|
|
||||||
"service-key" NL a public key in PEM format
|
|
||||||
|
|
||||||
[Exactly once]
|
|
||||||
|
|
||||||
The public key that can be used to encrypt messages to the hidden
|
|
||||||
service.
|
|
||||||
|
|
||||||
["intro-authentication" auth-type NL auth-data ... reserved]
|
|
||||||
|
|
||||||
[Any number]
|
|
||||||
|
|
||||||
The introduction-point-specific authentication data can be used
|
|
||||||
to perform client authentication. This data depends on the
|
|
||||||
selected introduction point as opposed to "service-authentication"
|
|
||||||
above.
|
|
||||||
|
|
||||||
(This ends the fields in the encrypted portion of the descriptor.)
|
|
||||||
|
|
||||||
[It's ok for Bob to advertise 0 introduction points. He might want
|
|
||||||
to do that if he previously advertised some introduction points,
|
|
||||||
and now he doesn't have any. -RD]
|
|
||||||
|
|
||||||
"signature" NL signature-string
|
|
||||||
|
|
||||||
[At end, exactly once]
|
|
||||||
|
|
||||||
A signature of all fields above with the private key of the hidden
|
|
||||||
service.
|
|
||||||
|
|
||||||
1.2.1. Other descriptor formats we don't use.
|
|
||||||
|
|
||||||
Support for the V0 descriptor format was dropped in 0.2.2.0-alpha-dev:
|
|
||||||
|
|
||||||
KL Key length [2 octets]
|
|
||||||
PK Bob's public key [KL octets]
|
|
||||||
TS A timestamp [4 octets]
|
|
||||||
NI Number of introduction points [2 octets]
|
|
||||||
Ipt A list of NUL-terminated ORs [variable]
|
|
||||||
SIG Signature of above fields [variable]
|
|
||||||
|
|
||||||
KL is the length of PK, in octets.
|
|
||||||
TS is the number of seconds elapsed since Jan 1, 1970.
|
|
||||||
|
|
||||||
The members of Ipt may be either (a) nicknames, or (b) identity key
|
|
||||||
digests, encoded in hex, and prefixed with a '$'.
|
|
||||||
|
|
||||||
The V1 descriptor format was understood and accepted from
|
|
||||||
0.1.1.5-alpha-cvs to 0.2.0.6-alpha-dev, but no Tors generated it and
|
|
||||||
it was removed:
|
|
||||||
|
|
||||||
V Format byte: set to 255 [1 octet]
|
|
||||||
V Version byte: set to 1 [1 octet]
|
|
||||||
KL Key length [2 octets]
|
|
||||||
PK Bob's public key [KL octets]
|
|
||||||
TS A timestamp [4 octets]
|
|
||||||
PROTO Protocol versions: bitmask [2 octets]
|
|
||||||
NI Number of introduction points [2 octets]
|
|
||||||
For each introduction point: (as in INTRODUCE2 cells)
|
|
||||||
IP Introduction point's address [4 octets]
|
|
||||||
PORT Introduction point's OR port [2 octets]
|
|
||||||
ID Introduction point identity ID [20 octets]
|
|
||||||
KLEN Length of onion key [2 octets]
|
|
||||||
KEY Introduction point onion key [KLEN octets]
|
|
||||||
SIG Signature of above fields [variable]
|
|
||||||
|
|
||||||
A hypothetical "V1" descriptor, that has never been used but might
|
|
||||||
be useful for historical reasons, contains:
|
|
||||||
|
|
||||||
V Format byte: set to 255 [1 octet]
|
|
||||||
V Version byte: set to 1 [1 octet]
|
|
||||||
KL Key length [2 octets]
|
|
||||||
PK Bob's public key [KL octets]
|
|
||||||
TS A timestamp [4 octets]
|
|
||||||
PROTO Rendezvous protocol versions: bitmask [2 octets]
|
|
||||||
NA Number of auth mechanisms accepted [1 octet]
|
|
||||||
For each auth mechanism:
|
|
||||||
AUTHT The auth type that is supported [2 octets]
|
|
||||||
AUTHL Length of auth data [1 octet]
|
|
||||||
AUTHD Auth data [variable]
|
|
||||||
NI Number of introduction points [2 octets]
|
|
||||||
For each introduction point: (as in INTRODUCE2 cells)
|
|
||||||
ATYPE An address type (typically 4) [1 octet]
|
|
||||||
ADDR Introduction point's IP address [4 or 16 octets]
|
|
||||||
PORT Introduction point's OR port [2 octets]
|
|
||||||
AUTHT The auth type that is supported [2 octets]
|
|
||||||
AUTHL Length of auth data [1 octet]
|
|
||||||
AUTHD Auth data [variable]
|
|
||||||
ID Introduction point identity ID [20 octets]
|
|
||||||
KLEN Length of onion key [2 octets]
|
|
||||||
KEY Introduction point onion key [KLEN octets]
|
|
||||||
SIG Signature of above fields [variable]
|
|
||||||
|
|
||||||
AUTHT specifies which authentication/authorization mechanism is
|
|
||||||
required by the hidden service or the introduction point. AUTHD
|
|
||||||
is arbitrary data that can be associated with an auth approach.
|
|
||||||
Currently only AUTHT of [00 00] is supported, with an AUTHL of 0.
|
|
||||||
See section 2 of this document for details on auth mechanisms.
|
|
||||||
|
|
||||||
1.3. Bob's OP establishes his introduction points.
|
|
||||||
|
|
||||||
The OP establishes a new introduction circuit to each introduction
|
|
||||||
point. These circuits MUST NOT be used for anything but hidden service
|
|
||||||
introduction. To establish the introduction, Bob sends a
|
|
||||||
RELAY_ESTABLISH_INTRO cell, containing:
|
|
||||||
|
|
||||||
KL Key length [2 octets]
|
|
||||||
PK Introduction public key [KL octets]
|
|
||||||
HS Hash of session info [20 octets]
|
|
||||||
SIG Signature of above information [variable]
|
|
||||||
|
|
||||||
[XXX011, need to add auth information here. -RD]
|
|
||||||
|
|
||||||
To prevent replay attacks, the HS field contains a SHA-1 hash based on the
|
|
||||||
shared secret KH between Bob's OP and the introduction point, as
|
|
||||||
follows:
|
|
||||||
HS = H(KH | "INTRODUCE")
|
|
||||||
That is:
|
|
||||||
HS = H(KH | [49 4E 54 52 4F 44 55 43 45])
|
|
||||||
(KH, as specified in tor-spec.txt, is H(g^xy | [00]) .)
|
|
||||||
|
|
||||||
Upon receiving such a cell, the OR first checks that the signature is
|
|
||||||
correct with the included public key. If so, it checks whether HS is
|
|
||||||
correct given the shared state between Bob's OP and the OR. If either
|
|
||||||
check fails, the OP discards the cell; otherwise, it associates the
|
|
||||||
circuit with Bob's public key, and dissociates any other circuits
|
|
||||||
currently associated with PK. On success, the OR sends Bob a
|
|
||||||
RELAY_INTRO_ESTABLISHED cell with an empty payload.
|
|
||||||
|
|
||||||
Bob's OP does not include its own public key in the RELAY_ESTABLISH_INTRO
|
|
||||||
cell, but the public key of a freshly generated introduction key pair.
|
|
||||||
The OP also includes these fresh public keys in the v2 hidden service
|
|
||||||
descriptor together with the other introduction point information. The
|
|
||||||
reason is that the introduction point does not need to and therefore
|
|
||||||
should not know for which hidden service it works, so as to prevent it
|
|
||||||
from tracking the hidden service's activity.
|
|
||||||
|
|
||||||
1.4. Bob's OP advertises his service descriptor(s).
|
|
||||||
|
|
||||||
Bob's OP opens a stream to each directory server's directory port via Tor.
|
|
||||||
(He may re-use old circuits for this.) Over this stream, Bob's OP makes
|
|
||||||
an HTTP 'POST' request, to a URL "/tor/rendezvous/publish" relative to the
|
|
||||||
directory server's root, containing as its body Bob's service descriptor.
|
|
||||||
|
|
||||||
Bob should upload a service descriptor for each version format that
|
|
||||||
is supported in the current Tor network.
|
|
||||||
|
|
||||||
Upon receiving a descriptor, the directory server checks the signature,
|
|
||||||
and discards the descriptor if the signature does not match the enclosed
|
|
||||||
public key. Next, the directory server checks the timestamp. If the
|
|
||||||
timestamp is more than 24 hours in the past or more than 1 hour in the
|
|
||||||
future, or the directory server already has a newer descriptor with the
|
|
||||||
same public key, the server discards the descriptor. Otherwise, the
|
|
||||||
server discards any older descriptors with the same public key and
|
|
||||||
version format, and associates the new descriptor with the public key.
|
|
||||||
The directory server remembers this descriptor for at least 24 hours
|
|
||||||
after its timestamp. At least every 18 hours, Bob's OP uploads a
|
|
||||||
fresh descriptor.
|
|
||||||
|
|
||||||
Bob's OP publishes v2 descriptors to a changing subset of all v2 hidden
|
|
||||||
service directories. Therefore, Bob's OP opens a stream via Tor to each
|
|
||||||
responsible hidden service directory. (He may re-use old circuits
|
|
||||||
for this.) Over this stream, Bob's OP makes an HTTP 'POST' request to a
|
|
||||||
URL "/tor/rendezvous2/publish" relative to the hidden service
|
|
||||||
directory's root, containing as its body Bob's service descriptor.
|
|
||||||
|
|
||||||
At any time, there are 6 hidden service directories responsible for
|
|
||||||
keeping replicas of a descriptor; they consist of 2 sets of 3 hidden
|
|
||||||
service directories with consecutive onion IDs. Bob's OP learns about
|
|
||||||
the complete list of hidden service directories by filtering the
|
|
||||||
consensus status document received from the directory authorities. A
|
|
||||||
hidden service directory is deemed responsible for all descriptor IDs in
|
|
||||||
the interval from its direct predecessor, exclusive, to its own ID,
|
|
||||||
inclusive; it further holds replicas for its 2 predecessors. A
|
|
||||||
participant only trusts its own routing list and never learns about
|
|
||||||
routing information from other parties.
|
|
||||||
|
|
||||||
Bob's OP publishes a new v2 descriptor once an hour or whenever its
|
|
||||||
content changes. V2 descriptors can be found by clients within a given
|
|
||||||
time period of 24 hours, after which they change their ID as described
|
|
||||||
under 1.2. If a published descriptor would be valid for less than 60
|
|
||||||
minutes (= 2 x 30 minutes to allow the server to be 30 minutes behind
|
|
||||||
and the client 30 minutes ahead), Bob's OP publishes the descriptor
|
|
||||||
under the ID of both, the current and the next publication period.
|
|
||||||
|
|
||||||
1.5. Alice receives a x.y.z.onion address.
|
|
||||||
|
|
||||||
When Alice receives a pointer to a location-hidden service, it is as a
|
|
||||||
hostname of the form "z.onion" or "y.z.onion" or "x.y.z.onion", where
|
|
||||||
z is a base-32 encoding of a 10-octet hash of Bob's service's public
|
|
||||||
key, computed as follows:
|
|
||||||
|
|
||||||
1. Let H = H(PK).
|
|
||||||
2. Let H' = the first 80 bits of H, considering each octet from
|
|
||||||
most significant bit to least significant bit.
|
|
||||||
2. Generate a 16-character encoding of H', using base32 as defined
|
|
||||||
in RFC 3548.
|
|
||||||
|
|
||||||
(We only use 80 bits instead of the 160 bits from SHA1 because we
|
|
||||||
don't need to worry about arbitrary collisions, and because it will
|
|
||||||
make handling the url's more convenient.)
|
|
||||||
|
|
||||||
The string "x", if present, is the base-32 encoding of the
|
|
||||||
authentication/authorization required by the introduction point.
|
|
||||||
The string "y", if present, is the base-32 encoding of the
|
|
||||||
authentication/authorization required by the hidden service.
|
|
||||||
Omitting a string is taken to mean auth type [00 00].
|
|
||||||
See section 2 of this document for details on auth mechanisms.
|
|
||||||
|
|
||||||
[Yes, numbers are allowed at the beginning. See RFC 1123. -NM]
|
|
||||||
|
|
||||||
1.6. Alice's OP retrieves a service descriptor.
|
|
||||||
|
|
||||||
Similarly to the description in section 1.4, Alice's OP fetches a v2
|
|
||||||
descriptor from a randomly chosen hidden service directory out of the
|
|
||||||
changing subset of 6 nodes. If the request is unsuccessful, Alice retries
|
|
||||||
the other remaining responsible hidden service directories in a random
|
|
||||||
order. Alice relies on Bob to care about a potential clock skew between
|
|
||||||
the two by possibly storing two sets of descriptors (see end of section
|
|
||||||
1.4).
|
|
||||||
|
|
||||||
Alice's OP opens a stream via Tor to the chosen v2 hidden service
|
|
||||||
directory. (She may re-use old circuits for this.) Over this stream,
|
|
||||||
Alice's OP makes an HTTP 'GET' request for the document
|
|
||||||
"/tor/rendezvous2/<z>", where z is replaced with the encoding of the
|
|
||||||
descriptor ID. The directory replies with a 404 HTTP response if it does
|
|
||||||
not recognize <z>, and otherwise returns Bob's most recently uploaded
|
|
||||||
service descriptor.
|
|
||||||
|
|
||||||
If Alice's OP receives a 404 response, it tries the other directory
|
|
||||||
servers, and only fails the lookup if none recognize the public key hash.
|
|
||||||
|
|
||||||
Upon receiving a service descriptor, Alice verifies with the same process
|
|
||||||
as the directory server uses, described above in section 1.4.
|
|
||||||
|
|
||||||
The directory server gives a 400 response if it cannot understand Alice's
|
|
||||||
request.
|
|
||||||
|
|
||||||
Alice should cache the descriptor locally, but should not use
|
|
||||||
descriptors that are more than 24 hours older than their timestamp.
|
|
||||||
[Caching may make her partitionable, but she fetched it anonymously,
|
|
||||||
and we can't very well *not* cache it. -RD]
|
|
||||||
|
|
||||||
1.7. Alice's OP establishes a rendezvous point.
|
|
||||||
|
|
||||||
When Alice requests a connection to a given location-hidden service,
|
|
||||||
and Alice's OP does not have an established circuit to that service,
|
|
||||||
the OP builds a rendezvous circuit. It does this by establishing
|
|
||||||
a circuit to a randomly chosen OR, and sending a
|
|
||||||
RELAY_ESTABLISH_RENDEZVOUS cell to that OR. The body of that cell
|
|
||||||
contains:
|
|
||||||
|
|
||||||
RC Rendezvous cookie [20 octets]
|
|
||||||
|
|
||||||
[XXX011 this looks like an auth mechanism. should we generalize here? -RD]
|
|
||||||
|
|
||||||
The rendezvous cookie is an arbitrary 20-byte value, chosen randomly by
|
|
||||||
Alice's OP.
|
|
||||||
|
|
||||||
Upon receiving a RELAY_ESTABLISH_RENDEZVOUS cell, the OR associates the
|
|
||||||
RC with the circuit that sent it. It replies to Alice with an empty
|
|
||||||
RELAY_RENDEZVOUS_ESTABLISHED cell to indicate success.
|
|
||||||
|
|
||||||
Alice's OP MUST NOT use the circuit which sent the cell for any purpose
|
|
||||||
other than rendezvous with the given location-hidden service.
|
|
||||||
|
|
||||||
1.8. Introduction: from Alice's OP to Introduction Point
|
|
||||||
|
|
||||||
Alice builds a separate circuit to one of Bob's chosen introduction
|
|
||||||
points, and sends it a RELAY_INTRODUCE1 cell containing:
|
|
||||||
|
|
||||||
Cleartext
|
|
||||||
PK_ID Identifier for Bob's PK [20 octets]
|
|
||||||
Encrypted to Bob's PK: (in the v0 intro protocol)
|
|
||||||
RP Rendezvous point's nickname [20 octets]
|
|
||||||
RC Rendezvous cookie [20 octets]
|
|
||||||
g^x Diffie-Hellman data, part 1 [128 octets]
|
|
||||||
OR (in the v1 intro protocol)
|
|
||||||
VER Version byte: set to 1. [1 octet]
|
|
||||||
RP Rendezvous point nick or ID [42 octets]
|
|
||||||
RC Rendezvous cookie [20 octets]
|
|
||||||
g^x Diffie-Hellman data, part 1 [128 octets]
|
|
||||||
OR (in the v2 intro protocol)
|
|
||||||
VER Version byte: set to 2. [1 octet]
|
|
||||||
IP Rendezvous point's address [4 octets]
|
|
||||||
PORT Rendezvous point's OR port [2 octets]
|
|
||||||
ID Rendezvous point identity ID [20 octets]
|
|
||||||
KLEN Length of onion key [2 octets]
|
|
||||||
KEY Rendezvous point onion key [KLEN octets]
|
|
||||||
RC Rendezvous cookie [20 octets]
|
|
||||||
g^x Diffie-Hellman data, part 1 [128 octets]
|
|
||||||
|
|
||||||
PK_ID is the hash of Bob's public key. RP is NUL-padded and
|
|
||||||
terminated. In version 0, it must contain a nickname. In version 1,
|
|
||||||
it must contain EITHER a nickname or an identity key digest that is
|
|
||||||
encoded in hex and prefixed with a '$'.
|
|
||||||
|
|
||||||
The hybrid encryption to Bob's PK works just like the hybrid
|
|
||||||
encryption in CREATE cells (see tor-spec). Thus the payload of the
|
|
||||||
version 0 RELAY_INTRODUCE1 cell on the wire will contain
|
|
||||||
20+42+16+20+20+128=246 bytes, and the version 1 and version 2
|
|
||||||
introduction formats have other sizes.
|
|
||||||
|
|
||||||
Through Tor 0.2.0.6-alpha, clients only generated the v0 introduction
|
|
||||||
format, whereas hidden services have understood and accepted v0,
|
|
||||||
v1, and v2 since 0.1.1.x. As of Tor 0.2.0.7-alpha and 0.1.2.18,
|
|
||||||
clients switched to using the v2 intro format.
|
|
||||||
|
|
||||||
If Alice has downloaded a v2 descriptor, she uses the contained public
|
|
||||||
key ("service-key") instead of Bob's public key to create the
|
|
||||||
RELAY_INTRODUCE1 cell as described above.
|
|
||||||
|
|
||||||
1.8.1. Other introduction formats we don't use.
|
|
||||||
|
|
||||||
We briefly speculated about using the following format for the
|
|
||||||
"encrypted to Bob's PK" part of the introduction, but no Tors have
|
|
||||||
ever generated these.
|
|
||||||
|
|
||||||
VER Version byte: set to 3. [1 octet]
|
|
||||||
ATYPE An address type (typically 4) [1 octet]
|
|
||||||
ADDR Rendezvous point's IP address [4 or 16 octets]
|
|
||||||
PORT Rendezvous point's OR port [2 octets]
|
|
||||||
AUTHT The auth type that is supported [2 octets]
|
|
||||||
AUTHL Length of auth data [1 octet]
|
|
||||||
AUTHD Auth data [variable]
|
|
||||||
ID Rendezvous point identity ID [20 octets]
|
|
||||||
KLEN Length of onion key [2 octets]
|
|
||||||
KEY Rendezvous point onion key [KLEN octets]
|
|
||||||
RC Rendezvous cookie [20 octets]
|
|
||||||
g^x Diffie-Hellman data, part 1 [128 octets]
|
|
||||||
|
|
||||||
1.9. Introduction: From the Introduction Point to Bob's OP
|
|
||||||
|
|
||||||
If the Introduction Point recognizes PK_ID as a public key which has
|
|
||||||
established a circuit for introductions as in 1.3 above, it sends the body
|
|
||||||
of the cell in a new RELAY_INTRODUCE2 cell down the corresponding circuit.
|
|
||||||
(If the PK_ID is unrecognized, the RELAY_INTRODUCE1 cell is discarded.)
|
|
||||||
|
|
||||||
After sending the RELAY_INTRODUCE2 cell, the OR replies to Alice with an
|
|
||||||
empty RELAY_COMMAND_INTRODUCE_ACK cell. If no RELAY_INTRODUCE2 cell can
|
|
||||||
be sent, the OR replies to Alice with a non-empty cell to indicate an
|
|
||||||
error. (The semantics of the cell body may be determined later; the
|
|
||||||
current implementation sends a single '1' byte on failure.)
|
|
||||||
|
|
||||||
When Bob's OP receives the RELAY_INTRODUCE2 cell, it decrypts it with
|
|
||||||
the private key for the corresponding hidden service, and extracts the
|
|
||||||
rendezvous point's nickname, the rendezvous cookie, and the value of g^x
|
|
||||||
chosen by Alice.
|
|
||||||
|
|
||||||
1.10. Rendezvous
|
|
||||||
|
|
||||||
Bob's OP builds a new Tor circuit ending at Alice's chosen rendezvous
|
|
||||||
point, and sends a RELAY_RENDEZVOUS1 cell along this circuit, containing:
|
|
||||||
RC Rendezvous cookie [20 octets]
|
|
||||||
g^y Diffie-Hellman [128 octets]
|
|
||||||
KH Handshake digest [20 octets]
|
|
||||||
|
|
||||||
(Bob's OP MUST NOT use this circuit for any other purpose.)
|
|
||||||
|
|
||||||
If the RP recognizes RC, it relays the rest of the cell down the
|
|
||||||
corresponding circuit in a RELAY_RENDEZVOUS2 cell, containing:
|
|
||||||
|
|
||||||
g^y Diffie-Hellman [128 octets]
|
|
||||||
KH Handshake digest [20 octets]
|
|
||||||
|
|
||||||
(If the RP does not recognize the RC, it discards the cell and
|
|
||||||
tears down the circuit.)
|
|
||||||
|
|
||||||
When Alice's OP receives a RELAY_RENDEZVOUS2 cell on a circuit which
|
|
||||||
has sent a RELAY_ESTABLISH_RENDEZVOUS cell but which has not yet received
|
|
||||||
a reply, it uses g^y and H(g^xy) to complete the handshake as in the Tor
|
|
||||||
circuit extend process: they establish a 60-octet string as
|
|
||||||
K = SHA1(g^xy | [00]) | SHA1(g^xy | [01]) | SHA1(g^xy | [02])
|
|
||||||
and generate
|
|
||||||
KH = K[0..15]
|
|
||||||
Kf = K[16..31]
|
|
||||||
Kb = K[32..47]
|
|
||||||
|
|
||||||
Subsequently, the rendezvous point passes relay cells, unchanged, from
|
|
||||||
each of the two circuits to the other. When Alice's OP sends
|
|
||||||
RELAY cells along the circuit, it first encrypts them with the
|
|
||||||
Kf, then with all of the keys for the ORs in Alice's side of the circuit;
|
|
||||||
and when Alice's OP receives RELAY cells from the circuit, it decrypts
|
|
||||||
them with the keys for the ORs in Alice's side of the circuit, then
|
|
||||||
decrypts them with Kb. Bob's OP does the same, with Kf and Kb
|
|
||||||
interchanged.
|
|
||||||
|
|
||||||
1.11. Creating streams
|
|
||||||
|
|
||||||
To open TCP connections to Bob's location-hidden service, Alice's OP sends
|
|
||||||
a RELAY_BEGIN cell along the established circuit, using the special
|
|
||||||
address "", and a chosen port. Bob's OP chooses a destination IP and
|
|
||||||
port, based on the configuration of the service connected to the circuit,
|
|
||||||
and opens a TCP stream. From then on, Bob's OP treats the stream as an
|
|
||||||
ordinary exit connection.
|
|
||||||
[ Except he doesn't include addr in the connected cell or the end
|
|
||||||
cell. -RD]
|
|
||||||
|
|
||||||
Alice MAY send multiple RELAY_BEGIN cells along the circuit, to open
|
|
||||||
multiple streams to Bob. Alice SHOULD NOT send RELAY_BEGIN cells for any
|
|
||||||
other address along her circuit to Bob; if she does, Bob MUST reject them.
|
|
||||||
|
|
||||||
2. Authentication and authorization.
|
|
||||||
|
|
||||||
Foo.
|
|
||||||
|
|
||||||
3. Hidden service directory operation
|
|
||||||
|
|
||||||
This section has been introduced with the v2 hidden service descriptor
|
|
||||||
format. It describes all operations of the v2 hidden service descriptor
|
|
||||||
fetching and propagation mechanism that are required for the protocol
|
|
||||||
described in section 1 to succeed with v2 hidden service descriptors.
|
|
||||||
|
|
||||||
3.1. Configuring as hidden service directory
|
|
||||||
|
|
||||||
Every onion router that has its directory port open can decide whether it
|
|
||||||
wants to store and serve hidden service descriptors. An onion router which
|
|
||||||
is configured as such includes the "hidden-service-dir" flag in its router
|
|
||||||
descriptors that it sends to directory authorities.
|
|
||||||
|
|
||||||
The directory authorities include a new flag "HSDir" for routers that
|
|
||||||
decided to provide storage for hidden service descriptors and that
|
|
||||||
have been running for at least 24 hours.
|
|
||||||
|
|
||||||
3.2. Accepting publish requests
|
|
||||||
|
|
||||||
Hidden service directory nodes accept publish requests for v2 hidden service
|
|
||||||
descriptors and store them to their local memory. (It is not necessary to
|
|
||||||
make descriptors persistent, because after restarting, the onion router
|
|
||||||
would not be accepted as a storing node anyway, because it has not been
|
|
||||||
running for at least 24 hours.) All requests and replies are formatted as
|
|
||||||
HTTP messages. Requests are initiated via BEGIN_DIR cells directed to
|
|
||||||
the router's directory port, and formatted as HTTP POST requests to the URL
|
|
||||||
"/tor/rendezvous2/publish" relative to the hidden service directory's root,
|
|
||||||
containing as its body a v2 service descriptor.
|
|
||||||
|
|
||||||
A hidden service directory node parses every received descriptor and only
|
|
||||||
stores it when it thinks that it is responsible for storing that descriptor
|
|
||||||
based on its own routing table. See section 1.4 for more information on how
|
|
||||||
to determine responsibility for a certain descriptor ID.
|
|
||||||
|
|
||||||
3.3. Processing fetch requests
|
|
||||||
|
|
||||||
Hidden service directory nodes process fetch requests for hidden service
|
|
||||||
descriptors by looking them up in their local memory. (They do not need to
|
|
||||||
determine if they are responsible for the passed ID, because it does no harm
|
|
||||||
if they deliver a descriptor for which they are not (any more) responsible.)
|
|
||||||
All requests and replies are formatted as HTTP messages. Requests are
|
|
||||||
initiated via BEGIN_DIR cells directed to the router's directory port,
|
|
||||||
and formatted as HTTP GET requests for the document "/tor/rendezvous2/<z>",
|
|
||||||
where z is replaced with the encoding of the descriptor ID.
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
|||||||
Tor's extensions to the SOCKS protocol
|
|
||||||
|
|
||||||
1. Overview
|
|
||||||
|
|
||||||
The SOCKS protocol provides a generic interface for TCP proxies. Client
|
|
||||||
software connects to a SOCKS server via TCP, and requests a TCP connection
|
|
||||||
to another address and port. The SOCKS server establishes the connection,
|
|
||||||
and reports success or failure to the client. After the connection has
|
|
||||||
been established, the client application uses the TCP stream as usual.
|
|
||||||
|
|
||||||
Tor supports SOCKS4 as defined in [1], SOCKS4A as defined in [2], and
|
|
||||||
SOCKS5 as defined in [3].
|
|
||||||
|
|
||||||
The stickiest issue for Tor in supporting clients, in practice, is forcing
|
|
||||||
DNS lookups to occur at the OR side: if clients do their own DNS lookup,
|
|
||||||
the DNS server can learn which addresses the client wants to reach.
|
|
||||||
SOCKS4 supports addressing by IPv4 address; SOCKS4A is a kludge on top of
|
|
||||||
SOCKS4 to allow addressing by hostname; SOCKS5 supports IPv4, IPv6, and
|
|
||||||
hostnames.
|
|
||||||
|
|
||||||
1.1. Extent of support
|
|
||||||
|
|
||||||
Tor supports the SOCKS4, SOCKS4A, and SOCKS5 standards, except as follows:
|
|
||||||
|
|
||||||
BOTH:
|
|
||||||
- The BIND command is not supported.
|
|
||||||
|
|
||||||
SOCKS4,4A:
|
|
||||||
- SOCKS4 usernames are ignored.
|
|
||||||
|
|
||||||
SOCKS5:
|
|
||||||
- The (SOCKS5) "UDP ASSOCIATE" command is not supported.
|
|
||||||
- IPv6 is not supported in CONNECT commands.
|
|
||||||
- Only the "NO AUTHENTICATION" (SOCKS5) authentication method [00] is
|
|
||||||
supported.
|
|
||||||
|
|
||||||
2. Name lookup
|
|
||||||
|
|
||||||
As an extension to SOCKS4A and SOCKS5, Tor implements a new command value,
|
|
||||||
"RESOLVE" [F0]. When Tor receives a "RESOLVE" SOCKS command, it initiates
|
|
||||||
a remote lookup of the hostname provided as the target address in the SOCKS
|
|
||||||
request. The reply is either an error (if the address couldn't be
|
|
||||||
resolved) or a success response. In the case of success, the address is
|
|
||||||
stored in the portion of the SOCKS response reserved for remote IP address.
|
|
||||||
|
|
||||||
(We support RESOLVE in SOCKS4 too, even though it is unnecessary.)
|
|
||||||
|
|
||||||
For SOCKS5 only, we support reverse resolution with a new command value,
|
|
||||||
"RESOLVE_PTR" [F1]. In response to a "RESOLVE_PTR" SOCKS5 command with
|
|
||||||
an IPv4 address as its target, Tor attempts to find the canonical
|
|
||||||
hostname for that IPv4 record, and returns it in the "server bound
|
|
||||||
address" portion of the reply.
|
|
||||||
(This command was not supported before Tor 0.1.2.2-alpha.)
|
|
||||||
|
|
||||||
3. Other command extensions.
|
|
||||||
|
|
||||||
Tor 0.1.2.4-alpha added a new command value: "CONNECT_DIR" [F2].
|
|
||||||
In this case, Tor will open an encrypted direct TCP connection to the
|
|
||||||
directory port of the Tor server specified by address:port (the port
|
|
||||||
specified should be the ORPort of the server). It uses a one-hop tunnel
|
|
||||||
and a "BEGIN_DIR" relay cell to accomplish this secure connection.
|
|
||||||
|
|
||||||
The F2 command value was removed in Tor 0.2.0.10-alpha in favor of a
|
|
||||||
new use_begindir flag in edge_connection_t.
|
|
||||||
|
|
||||||
4. HTTP-resistance
|
|
||||||
|
|
||||||
Tor checks the first byte of each SOCKS request to see whether it looks
|
|
||||||
more like an HTTP request (that is, it starts with a "G", "H", or "P"). If
|
|
||||||
so, Tor returns a small webpage, telling the user that his/her browser is
|
|
||||||
misconfigured. This is helpful for the many users who mistakenly try to
|
|
||||||
use Tor as an HTTP proxy instead of a SOCKS proxy.
|
|
||||||
|
|
||||||
References:
|
|
||||||
[1] http://archive.socks.permeo.com/protocol/socks4.protocol
|
|
||||||
[2] http://archive.socks.permeo.com/protocol/socks4a.protocol
|
|
||||||
[3] SOCKS5: RFC1928
|
|
||||||
|
|
@ -1,992 +0,0 @@
|
|||||||
|
|
||||||
Tor Protocol Specification
|
|
||||||
|
|
||||||
Roger Dingledine
|
|
||||||
Nick Mathewson
|
|
||||||
|
|
||||||
Note: This document aims to specify Tor as implemented in 0.2.1.x. Future
|
|
||||||
versions of Tor may implement improved protocols, and compatibility is not
|
|
||||||
guaranteed. Compatibility notes are given for versions 0.1.1.15-rc and
|
|
||||||
later; earlier versions are not compatible with the Tor network as of this
|
|
||||||
writing.
|
|
||||||
|
|
||||||
This specification is not a design document; most design criteria
|
|
||||||
are not examined. For more information on why Tor acts as it does,
|
|
||||||
see tor-design.pdf.
|
|
||||||
|
|
||||||
0. Preliminaries
|
|
||||||
|
|
||||||
0.1. Notation and encoding
|
|
||||||
|
|
||||||
PK -- a public key.
|
|
||||||
SK -- a private key.
|
|
||||||
K -- a key for a symmetric cypher.
|
|
||||||
|
|
||||||
a|b -- concatenation of 'a' and 'b'.
|
|
||||||
|
|
||||||
[A0 B1 C2] -- a three-byte sequence, containing the bytes with
|
|
||||||
hexadecimal values A0, B1, and C2, in that order.
|
|
||||||
|
|
||||||
All numeric values are encoded in network (big-endian) order.
|
|
||||||
|
|
||||||
H(m) -- a cryptographic hash of m.
|
|
||||||
|
|
||||||
0.2. Security parameters
|
|
||||||
|
|
||||||
Tor uses a stream cipher, a public-key cipher, the Diffie-Hellman
|
|
||||||
protocol, and a hash function.
|
|
||||||
|
|
||||||
KEY_LEN -- the length of the stream cipher's key, in bytes.
|
|
||||||
|
|
||||||
PK_ENC_LEN -- the length of a public-key encrypted message, in bytes.
|
|
||||||
PK_PAD_LEN -- the number of bytes added in padding for public-key
|
|
||||||
encryption, in bytes. (The largest number of bytes that can be encrypted
|
|
||||||
in a single public-key operation is therefore PK_ENC_LEN-PK_PAD_LEN.)
|
|
||||||
|
|
||||||
DH_LEN -- the number of bytes used to represent a member of the
|
|
||||||
Diffie-Hellman group.
|
|
||||||
DH_SEC_LEN -- the number of bytes used in a Diffie-Hellman private key (x).
|
|
||||||
|
|
||||||
HASH_LEN -- the length of the hash function's output, in bytes.
|
|
||||||
|
|
||||||
PAYLOAD_LEN -- The longest allowable cell payload, in bytes. (509)
|
|
||||||
|
|
||||||
CELL_LEN -- The length of a Tor cell, in bytes.
|
|
||||||
|
|
||||||
0.3. Ciphers
|
|
||||||
|
|
||||||
For a stream cipher, we use 128-bit AES in counter mode, with an IV of all
|
|
||||||
0 bytes.
|
|
||||||
|
|
||||||
For a public-key cipher, we use RSA with 1024-bit keys and a fixed
|
|
||||||
exponent of 65537. We use OAEP-MGF1 padding, with SHA-1 as its digest
|
|
||||||
function. We leave the optional "Label" parameter unset. (For OAEP
|
|
||||||
padding, see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf)
|
|
||||||
|
|
||||||
For Diffie-Hellman, we use a generator (g) of 2. For the modulus (p), we
|
|
||||||
use the 1024-bit safe prime from rfc2409 section 6.2 whose hex
|
|
||||||
representation is:
|
|
||||||
|
|
||||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
|
|
||||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
|
|
||||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
|
|
||||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
|
|
||||||
"49286651ECE65381FFFFFFFFFFFFFFFF"
|
|
||||||
|
|
||||||
As an optimization, implementations SHOULD choose DH private keys (x) of
|
|
||||||
320 bits. Implementations that do this MUST never use any DH key more
|
|
||||||
than once.
|
|
||||||
[May other implementations reuse their DH keys?? -RD]
|
|
||||||
[Probably not. Conceivably, you could get away with changing DH keys once
|
|
||||||
per second, but there are too many oddball attacks for me to be
|
|
||||||
comfortable that this is safe. -NM]
|
|
||||||
|
|
||||||
For a hash function, we use SHA-1.
|
|
||||||
|
|
||||||
KEY_LEN=16.
|
|
||||||
DH_LEN=128; DH_SEC_LEN=40.
|
|
||||||
PK_ENC_LEN=128; PK_PAD_LEN=42.
|
|
||||||
HASH_LEN=20.
|
|
||||||
|
|
||||||
When we refer to "the hash of a public key", we mean the SHA-1 hash of the
|
|
||||||
DER encoding of an ASN.1 RSA public key (as specified in PKCS.1).
|
|
||||||
|
|
||||||
All "random" values should be generated with a cryptographically strong
|
|
||||||
random number generator, unless otherwise noted.
|
|
||||||
|
|
||||||
The "hybrid encryption" of a byte sequence M with a public key PK is
|
|
||||||
computed as follows:
|
|
||||||
1. If M is less than PK_ENC_LEN-PK_PAD_LEN, pad and encrypt M with PK.
|
|
||||||
2. Otherwise, generate a KEY_LEN byte random key K.
|
|
||||||
Let M1 = the first PK_ENC_LEN-PK_PAD_LEN-KEY_LEN bytes of M,
|
|
||||||
and let M2 = the rest of M.
|
|
||||||
Pad and encrypt K|M1 with PK. Encrypt M2 with our stream cipher,
|
|
||||||
using the key K. Concatenate these encrypted values.
|
|
||||||
[XXX Note that this "hybrid encryption" approach does not prevent
|
|
||||||
an attacker from adding or removing bytes to the end of M. It also
|
|
||||||
allows attackers to modify the bytes not covered by the OAEP --
|
|
||||||
see Goldberg's PET2006 paper for details. We will add a MAC to this
|
|
||||||
scheme one day. -RD]
|
|
||||||
|
|
||||||
0.4. Other parameter values
|
|
||||||
|
|
||||||
CELL_LEN=512
|
|
||||||
|
|
||||||
1. System overview
|
|
||||||
|
|
||||||
Tor is a distributed overlay network designed to anonymize
|
|
||||||
low-latency TCP-based applications such as web browsing, secure shell,
|
|
||||||
and instant messaging. Clients choose a path through the network and
|
|
||||||
build a ``circuit'', in which each node (or ``onion router'' or ``OR'')
|
|
||||||
in the path knows its predecessor and successor, but no other nodes in
|
|
||||||
the circuit. Traffic flowing down the circuit is sent in fixed-size
|
|
||||||
``cells'', which are unwrapped by a symmetric key at each node (like
|
|
||||||
the layers of an onion) and relayed downstream.
|
|
||||||
|
|
||||||
1.1. Keys and names
|
|
||||||
|
|
||||||
Every Tor server has multiple public/private keypairs:
|
|
||||||
|
|
||||||
- A long-term signing-only "Identity key" used to sign documents and
|
|
||||||
certificates, and used to establish server identity.
|
|
||||||
- A medium-term "Onion key" used to decrypt onion skins when accepting
|
|
||||||
circuit extend attempts. (See 5.1.) Old keys MUST be accepted for at
|
|
||||||
least one week after they are no longer advertised. Because of this,
|
|
||||||
servers MUST retain old keys for a while after they're rotated.
|
|
||||||
- A short-term "Connection key" used to negotiate TLS connections.
|
|
||||||
Tor implementations MAY rotate this key as often as they like, and
|
|
||||||
SHOULD rotate this key at least once a day.
|
|
||||||
|
|
||||||
Tor servers are also identified by "nicknames"; these are specified in
|
|
||||||
dir-spec.txt.
|
|
||||||
|
|
||||||
2. Connections
|
|
||||||
|
|
||||||
Connections between two Tor servers, or between a client and a server,
|
|
||||||
use TLS/SSLv3 for link authentication and encryption. All
|
|
||||||
implementations MUST support the SSLv3 ciphersuite
|
|
||||||
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", and SHOULD support the TLS
|
|
||||||
ciphersuite "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" if it is available.
|
|
||||||
|
|
||||||
There are three acceptable ways to perform a TLS handshake when
|
|
||||||
connecting to a Tor server: "certificates up-front", "renegotiation", and
|
|
||||||
"backwards-compatible renegotiation". ("Backwards-compatible
|
|
||||||
renegotiation" is, as the name implies, compatible with both other
|
|
||||||
handshake types.)
|
|
||||||
|
|
||||||
Before Tor 0.2.0.21, only "certificates up-front" was supported. In Tor
|
|
||||||
0.2.0.21 or later, "backwards-compatible renegotiation" is used.
|
|
||||||
|
|
||||||
In "certificates up-front", the connection initiator always sends a
|
|
||||||
two-certificate chain, consisting of an X.509 certificate using a
|
|
||||||
short-term connection public key and a second, self- signed X.509
|
|
||||||
certificate containing its identity key. The other party sends a similar
|
|
||||||
certificate chain. The initiator's ClientHello MUST NOT include any
|
|
||||||
ciphersuites other than:
|
|
||||||
TLS_DHE_RSA_WITH_AES_256_CBC_SHA
|
|
||||||
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
|
|
||||||
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
|
|
||||||
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
|
|
||||||
|
|
||||||
In "renegotiation", the connection initiator sends no certificates, and
|
|
||||||
the responder sends a single connection certificate. Once the TLS
|
|
||||||
handshake is complete, the initiator renegotiates the handshake, with each
|
|
||||||
parties sending a two-certificate chain as in "certificates up-front".
|
|
||||||
The initiator's ClientHello MUST include at least once ciphersuite not in
|
|
||||||
the list above. The responder SHOULD NOT select any ciphersuite besides
|
|
||||||
those in the list above.
|
|
||||||
[The above "should not" is because some of the ciphers that
|
|
||||||
clients list may be fake.]
|
|
||||||
|
|
||||||
In "backwards-compatible renegotiation", the connection initiator's
|
|
||||||
ClientHello MUST include at least one ciphersuite other than those listed
|
|
||||||
above. The connection responder examines the initiator's ciphersuite list
|
|
||||||
to see whether it includes any ciphers other than those included in the
|
|
||||||
list above. If extra ciphers are included, the responder proceeds as in
|
|
||||||
"renegotiation": it sends a single certificate and does not request
|
|
||||||
client certificates. Otherwise (in the case that no extra ciphersuites
|
|
||||||
are included in the ClientHello) the responder proceeds as in
|
|
||||||
"certificates up-front": it requests client certificates, and sends a
|
|
||||||
two-certificate chain. In either case, once the responder has sent its
|
|
||||||
certificate or certificates, the initiator counts them. If two
|
|
||||||
certificates have been sent, it proceeds as in "certificates up-front";
|
|
||||||
otherwise, it proceeds as in "renegotiation".
|
|
||||||
|
|
||||||
All new implementations of the Tor server protocol MUST support
|
|
||||||
"backwards-compatible renegotiation"; clients SHOULD do this too. If
|
|
||||||
this is not possible, new client implementations MUST support both
|
|
||||||
"renegotiation" and "certificates up-front" and use the router's
|
|
||||||
published link protocols list (see dir-spec.txt on the "protocols" entry)
|
|
||||||
to decide which to use.
|
|
||||||
|
|
||||||
In all of the above handshake variants, certificates sent in the clear
|
|
||||||
SHOULD NOT include any strings to identify the host as a Tor server. In
|
|
||||||
the "renegotation" and "backwards-compatible renegotiation", the
|
|
||||||
initiator SHOULD chose a list of ciphersuites and TLS extensions chosen
|
|
||||||
to mimic one used by a popular web browser.
|
|
||||||
|
|
||||||
Responders MUST NOT select any TLS ciphersuite that lacks ephemeral keys,
|
|
||||||
or whose symmetric keys are less then KEY_LEN bits, or whose digests are
|
|
||||||
less than HASH_LEN bits. Responders SHOULD NOT select any SSLv3
|
|
||||||
ciphersuite other than those listed above.
|
|
||||||
|
|
||||||
Even though the connection protocol is identical, we will think of the
|
|
||||||
initiator as either an onion router (OR) if it is willing to relay
|
|
||||||
traffic for other Tor users, or an onion proxy (OP) if it only handles
|
|
||||||
local requests. Onion proxies SHOULD NOT provide long-term-trackable
|
|
||||||
identifiers in their handshakes.
|
|
||||||
|
|
||||||
In all handshake variants, once all certificates are exchanged, all
|
|
||||||
parties receiving certificates must confirm that the identity key is as
|
|
||||||
expected. (When initiating a connection, the expected identity key is
|
|
||||||
the one given in the directory; when creating a connection because of an
|
|
||||||
EXTEND cell, the expected identity key is the one given in the cell.) If
|
|
||||||
the key is not as expected, the party must close the connection.
|
|
||||||
|
|
||||||
When connecting to an OR, all parties SHOULD reject the connection if that
|
|
||||||
OR has a malformed or missing certificate. When accepting an incoming
|
|
||||||
connection, an OR SHOULD NOT reject incoming connections from parties with
|
|
||||||
malformed or missing certificates. (However, an OR should not believe
|
|
||||||
that an incoming connection is from another OR unless the certificates
|
|
||||||
are present and well-formed.)
|
|
||||||
|
|
||||||
[Before version 0.1.2.8-rc, ORs rejected incoming connections from ORs and
|
|
||||||
OPs alike if their certificates were missing or malformed.]
|
|
||||||
|
|
||||||
Once a TLS connection is established, the two sides send cells
|
|
||||||
(specified below) to one another. Cells are sent serially. All
|
|
||||||
cells are CELL_LEN bytes long. Cells may be sent embedded in TLS
|
|
||||||
records of any size or divided across TLS records, but the framing
|
|
||||||
of TLS records MUST NOT leak information about the type or contents
|
|
||||||
of the cells.
|
|
||||||
|
|
||||||
TLS connections are not permanent. Either side MAY close a connection
|
|
||||||
if there are no circuits running over it and an amount of time
|
|
||||||
(KeepalivePeriod, defaults to 5 minutes) has passed since the last time
|
|
||||||
any traffic was transmitted over the TLS connection. Clients SHOULD
|
|
||||||
also hold a TLS connection with no circuits open, if it is likely that a
|
|
||||||
circuit will be built soon using that connection.
|
|
||||||
|
|
||||||
(As an exception, directory servers may try to stay connected to all of
|
|
||||||
the ORs -- though this will be phased out for the Tor 0.1.2.x release.)
|
|
||||||
|
|
||||||
To avoid being trivially distinguished from servers, client-only Tor
|
|
||||||
instances are encouraged but not required to use a two-certificate chain
|
|
||||||
as well. Clients SHOULD NOT keep using the same certificates when
|
|
||||||
their IP address changes. Clients MAY send no certificates at all.
|
|
||||||
|
|
||||||
3. Cell Packet format
|
|
||||||
|
|
||||||
The basic unit of communication for onion routers and onion
|
|
||||||
proxies is a fixed-width "cell".
|
|
||||||
|
|
||||||
On a version 1 connection, each cell contains the following
|
|
||||||
fields:
|
|
||||||
|
|
||||||
CircID [2 bytes]
|
|
||||||
Command [1 byte]
|
|
||||||
Payload (padded with 0 bytes) [PAYLOAD_LEN bytes]
|
|
||||||
|
|
||||||
On a version 2 connection, all cells are as in version 1 connections,
|
|
||||||
except for the initial VERSIONS cell, whose format is:
|
|
||||||
|
|
||||||
Circuit [2 octets; set to 0]
|
|
||||||
Command [1 octet; set to 7 for VERSIONS]
|
|
||||||
Length [2 octets; big-endian integer]
|
|
||||||
Payload [Length bytes]
|
|
||||||
|
|
||||||
The CircID field determines which circuit, if any, the cell is
|
|
||||||
associated with.
|
|
||||||
|
|
||||||
The 'Command' field holds one of the following values:
|
|
||||||
0 -- PADDING (Padding) (See Sec 7.2)
|
|
||||||
1 -- CREATE (Create a circuit) (See Sec 5.1)
|
|
||||||
2 -- CREATED (Acknowledge create) (See Sec 5.1)
|
|
||||||
3 -- RELAY (End-to-end data) (See Sec 5.5 and 6)
|
|
||||||
4 -- DESTROY (Stop using a circuit) (See Sec 5.4)
|
|
||||||
5 -- CREATE_FAST (Create a circuit, no PK) (See Sec 5.1)
|
|
||||||
6 -- CREATED_FAST (Circuit created, no PK) (See Sec 5.1)
|
|
||||||
7 -- VERSIONS (Negotiate proto version) (See Sec 4)
|
|
||||||
8 -- NETINFO (Time and address info) (See Sec 4)
|
|
||||||
9 -- RELAY_EARLY (End-to-end data; limited) (See sec 5.6)
|
|
||||||
|
|
||||||
The interpretation of 'Payload' depends on the type of the cell.
|
|
||||||
PADDING: Payload is unused.
|
|
||||||
CREATE: Payload contains the handshake challenge.
|
|
||||||
CREATED: Payload contains the handshake response.
|
|
||||||
RELAY: Payload contains the relay header and relay body.
|
|
||||||
DESTROY: Payload contains a reason for closing the circuit.
|
|
||||||
(see 5.4)
|
|
||||||
Upon receiving any other value for the command field, an OR must
|
|
||||||
drop the cell. Since more cell types may be added in the future, ORs
|
|
||||||
should generally not warn when encountering unrecognized commands.
|
|
||||||
|
|
||||||
The payload is padded with 0 bytes.
|
|
||||||
|
|
||||||
PADDING cells are currently used to implement connection keepalive.
|
|
||||||
If there is no other traffic, ORs and OPs send one another a PADDING
|
|
||||||
cell every few minutes.
|
|
||||||
|
|
||||||
CREATE, CREATED, and DESTROY cells are used to manage circuits;
|
|
||||||
see section 5 below.
|
|
||||||
|
|
||||||
RELAY cells are used to send commands and data along a circuit; see
|
|
||||||
section 6 below.
|
|
||||||
|
|
||||||
VERSIONS and NETINFO cells are used to set up connections. See section 4
|
|
||||||
below.
|
|
||||||
|
|
||||||
4. Negotiating and initializing connections
|
|
||||||
|
|
||||||
4.1. Negotiating versions with VERSIONS cells
|
|
||||||
|
|
||||||
There are multiple instances of the Tor link connection protocol. Any
|
|
||||||
connection negotiated using the "certificates up front" handshake (see
|
|
||||||
section 2 above) is "version 1". In any connection where both parties
|
|
||||||
have behaved as in the "renegotiation" handshake, the link protocol
|
|
||||||
version is 2 or higher.
|
|
||||||
|
|
||||||
To determine the version, in any connection where the "renegotiation"
|
|
||||||
handshake was used (that is, where the server sent only one certificate
|
|
||||||
at first and where the client did not send any certificates until
|
|
||||||
renegotiation), both parties MUST send a VERSIONS cell immediately after
|
|
||||||
the renegotiation is finished, before any other cells are sent. Parties
|
|
||||||
MUST NOT send any other cells on a connection until they have received a
|
|
||||||
VERSIONS cell.
|
|
||||||
|
|
||||||
The payload in a VERSIONS cell is a series of big-endian two-byte
|
|
||||||
integers. Both parties MUST select as the link protocol version the
|
|
||||||
highest number contained both in the VERSIONS cell they sent and in the
|
|
||||||
versions cell they received. If they have no such version in common,
|
|
||||||
they cannot communicate and MUST close the connection.
|
|
||||||
|
|
||||||
Since the version 1 link protocol does not use the "renegotiation"
|
|
||||||
handshake, implementations MUST NOT list version 1 in their VERSIONS
|
|
||||||
cell.
|
|
||||||
|
|
||||||
4.2. NETINFO cells
|
|
||||||
|
|
||||||
If version 2 or higher is negotiated, each party sends the other a
|
|
||||||
NETINFO cell. The cell's payload is:
|
|
||||||
|
|
||||||
Timestamp [4 bytes]
|
|
||||||
Other OR's address [variable]
|
|
||||||
Number of addresses [1 byte]
|
|
||||||
This OR's addresses [variable]
|
|
||||||
|
|
||||||
The address format is a type/length/value sequence as given in section
|
|
||||||
6.4 below. The timestamp is a big-endian unsigned integer number of
|
|
||||||
seconds since the unix epoch.
|
|
||||||
|
|
||||||
Implementations MAY use the timestamp value to help decide if their
|
|
||||||
clocks are skewed. Initiators MAY use "other OR's address" to help
|
|
||||||
learn which address their connections are originating from, if they do
|
|
||||||
not know it. Initiators SHOULD use "this OR's address" to make sure
|
|
||||||
that they have connected to another OR at its canonical address.
|
|
||||||
|
|
||||||
[As of 0.2.0.23-rc, implementations use none of the above values.]
|
|
||||||
|
|
||||||
|
|
||||||
5. Circuit management
|
|
||||||
|
|
||||||
5.1. CREATE and CREATED cells
|
|
||||||
|
|
||||||
Users set up circuits incrementally, one hop at a time. To create a
|
|
||||||
new circuit, OPs send a CREATE cell to the first node, with the
|
|
||||||
first half of the DH handshake; that node responds with a CREATED
|
|
||||||
cell with the second half of the DH handshake plus the first 20 bytes
|
|
||||||
of derivative key data (see section 5.2). To extend a circuit past
|
|
||||||
the first hop, the OP sends an EXTEND relay cell (see section 5)
|
|
||||||
which instructs the last node in the circuit to send a CREATE cell
|
|
||||||
to extend the circuit.
|
|
||||||
|
|
||||||
The payload for a CREATE cell is an 'onion skin', which consists
|
|
||||||
of the first step of the DH handshake data (also known as g^x).
|
|
||||||
This value is hybrid-encrypted (see 0.3) to Bob's onion key, giving
|
|
||||||
an onion-skin of:
|
|
||||||
PK-encrypted:
|
|
||||||
Padding [PK_PAD_LEN bytes]
|
|
||||||
Symmetric key [KEY_LEN bytes]
|
|
||||||
First part of g^x [PK_ENC_LEN-PK_PAD_LEN-KEY_LEN bytes]
|
|
||||||
Symmetrically encrypted:
|
|
||||||
Second part of g^x [DH_LEN-(PK_ENC_LEN-PK_PAD_LEN-KEY_LEN)
|
|
||||||
bytes]
|
|
||||||
|
|
||||||
The relay payload for an EXTEND relay cell consists of:
|
|
||||||
Address [4 bytes]
|
|
||||||
Port [2 bytes]
|
|
||||||
Onion skin [DH_LEN+KEY_LEN+PK_PAD_LEN bytes]
|
|
||||||
Identity fingerprint [HASH_LEN bytes]
|
|
||||||
|
|
||||||
The port and address field denote the IPV4 address and port of the next
|
|
||||||
onion router in the circuit; the public key hash is the hash of the PKCS#1
|
|
||||||
ASN1 encoding of the next onion router's identity (signing) key. (See 0.3
|
|
||||||
above.) Including this hash allows the extending OR verify that it is
|
|
||||||
indeed connected to the correct target OR, and prevents certain
|
|
||||||
man-in-the-middle attacks.
|
|
||||||
|
|
||||||
The payload for a CREATED cell, or the relay payload for an
|
|
||||||
EXTENDED cell, contains:
|
|
||||||
DH data (g^y) [DH_LEN bytes]
|
|
||||||
Derivative key data (KH) [HASH_LEN bytes] <see 5.2 below>
|
|
||||||
|
|
||||||
The CircID for a CREATE cell is an arbitrarily chosen 2-byte integer,
|
|
||||||
selected by the node (OP or OR) that sends the CREATE cell. To prevent
|
|
||||||
CircID collisions, when one node sends a CREATE cell to another, it chooses
|
|
||||||
from only one half of the possible values based on the ORs' public
|
|
||||||
identity keys: if the sending node has a lower key, it chooses a CircID with
|
|
||||||
an MSB of 0; otherwise, it chooses a CircID with an MSB of 1.
|
|
||||||
|
|
||||||
(An OP with no public key MAY choose any CircID it wishes, since an OP
|
|
||||||
never needs to process a CREATE cell.)
|
|
||||||
|
|
||||||
Public keys are compared numerically by modulus.
|
|
||||||
|
|
||||||
As usual with DH, x and y MUST be generated randomly.
|
|
||||||
|
|
||||||
5.1.1. CREATE_FAST/CREATED_FAST cells
|
|
||||||
|
|
||||||
When initializing the first hop of a circuit, the OP has already
|
|
||||||
established the OR's identity and negotiated a secret key using TLS.
|
|
||||||
Because of this, it is not always necessary for the OP to perform the
|
|
||||||
public key operations to create a circuit. In this case, the
|
|
||||||
OP MAY send a CREATE_FAST cell instead of a CREATE cell for the first
|
|
||||||
hop only. The OR responds with a CREATED_FAST cell, and the circuit is
|
|
||||||
created.
|
|
||||||
|
|
||||||
A CREATE_FAST cell contains:
|
|
||||||
|
|
||||||
Key material (X) [HASH_LEN bytes]
|
|
||||||
|
|
||||||
A CREATED_FAST cell contains:
|
|
||||||
|
|
||||||
Key material (Y) [HASH_LEN bytes]
|
|
||||||
Derivative key data [HASH_LEN bytes] (See 5.2 below)
|
|
||||||
|
|
||||||
The values of X and Y must be generated randomly.
|
|
||||||
|
|
||||||
If an OR sees a circuit created with CREATE_FAST, the OR is sure to be the
|
|
||||||
first hop of a circuit. ORs SHOULD reject attempts to create streams with
|
|
||||||
RELAY_BEGIN exiting the circuit at the first hop: letting Tor be used as a
|
|
||||||
single hop proxy makes exit nodes a more attractive target for compromise.
|
|
||||||
|
|
||||||
5.2. Setting circuit keys
|
|
||||||
|
|
||||||
Once the handshake between the OP and an OR is completed, both can
|
|
||||||
now calculate g^xy with ordinary DH. Before computing g^xy, both client
|
|
||||||
and server MUST verify that the received g^x or g^y value is not degenerate;
|
|
||||||
that is, it must be strictly greater than 1 and strictly less than p-1
|
|
||||||
where p is the DH modulus. Implementations MUST NOT complete a handshake
|
|
||||||
with degenerate keys. Implementations MUST NOT discard other "weak"
|
|
||||||
g^x values.
|
|
||||||
|
|
||||||
(Discarding degenerate keys is critical for security; if bad keys
|
|
||||||
are not discarded, an attacker can substitute the server's CREATED
|
|
||||||
cell's g^y with 0 or 1, thus creating a known g^xy and impersonating
|
|
||||||
the server. Discarding other keys may allow attacks to learn bits of
|
|
||||||
the private key.)
|
|
||||||
|
|
||||||
If CREATE or EXTEND is used to extend a circuit, the client and server
|
|
||||||
base their key material on K0=g^xy, represented as a big-endian unsigned
|
|
||||||
integer.
|
|
||||||
|
|
||||||
If CREATE_FAST is used, the client and server base their key material on
|
|
||||||
K0=X|Y.
|
|
||||||
|
|
||||||
From the base key material K0, they compute KEY_LEN*2+HASH_LEN*3 bytes of
|
|
||||||
derivative key data as
|
|
||||||
K = H(K0 | [00]) | H(K0 | [01]) | H(K0 | [02]) | ...
|
|
||||||
|
|
||||||
The first HASH_LEN bytes of K form KH; the next HASH_LEN form the forward
|
|
||||||
digest Df; the next HASH_LEN 41-60 form the backward digest Db; the next
|
|
||||||
KEY_LEN 61-76 form Kf, and the final KEY_LEN form Kb. Excess bytes from K
|
|
||||||
are discarded.
|
|
||||||
|
|
||||||
KH is used in the handshake response to demonstrate knowledge of the
|
|
||||||
computed shared key. Df is used to seed the integrity-checking hash
|
|
||||||
for the stream of data going from the OP to the OR, and Db seeds the
|
|
||||||
integrity-checking hash for the data stream from the OR to the OP. Kf
|
|
||||||
is used to encrypt the stream of data going from the OP to the OR, and
|
|
||||||
Kb is used to encrypt the stream of data going from the OR to the OP.
|
|
||||||
|
|
||||||
5.3. Creating circuits
|
|
||||||
|
|
||||||
When creating a circuit through the network, the circuit creator
|
|
||||||
(OP) performs the following steps:
|
|
||||||
|
|
||||||
1. Choose an onion router as an exit node (R_N), such that the onion
|
|
||||||
router's exit policy includes at least one pending stream that
|
|
||||||
needs a circuit (if there are any).
|
|
||||||
|
|
||||||
2. Choose a chain of (N-1) onion routers
|
|
||||||
(R_1...R_N-1) to constitute the path, such that no router
|
|
||||||
appears in the path twice.
|
|
||||||
|
|
||||||
3. If not already connected to the first router in the chain,
|
|
||||||
open a new connection to that router.
|
|
||||||
|
|
||||||
4. Choose a circID not already in use on the connection with the
|
|
||||||
first router in the chain; send a CREATE cell along the
|
|
||||||
connection, to be received by the first onion router.
|
|
||||||
|
|
||||||
5. Wait until a CREATED cell is received; finish the handshake
|
|
||||||
and extract the forward key Kf_1 and the backward key Kb_1.
|
|
||||||
|
|
||||||
6. For each subsequent onion router R (R_2 through R_N), extend
|
|
||||||
the circuit to R.
|
|
||||||
|
|
||||||
To extend the circuit by a single onion router R_M, the OP performs
|
|
||||||
these steps:
|
|
||||||
|
|
||||||
1. Create an onion skin, encrypted to R_M's public onion key.
|
|
||||||
|
|
||||||
2. Send the onion skin in a relay EXTEND cell along
|
|
||||||
the circuit (see section 5).
|
|
||||||
|
|
||||||
3. When a relay EXTENDED cell is received, verify KH, and
|
|
||||||
calculate the shared keys. The circuit is now extended.
|
|
||||||
|
|
||||||
When an onion router receives an EXTEND relay cell, it sends a CREATE
|
|
||||||
cell to the next onion router, with the enclosed onion skin as its
|
|
||||||
payload. As special cases, if the extend cell includes a digest of
|
|
||||||
all zeroes, or asks to extend back to the relay that sent the extend
|
|
||||||
cell, the circuit will fail and be torn down. The initiating onion
|
|
||||||
router chooses some circID not yet used on the connection between the
|
|
||||||
two onion routers. (But see section 5.1. above, concerning choosing
|
|
||||||
circIDs based on lexicographic order of nicknames.)
|
|
||||||
|
|
||||||
When an onion router receives a CREATE cell, if it already has a
|
|
||||||
circuit on the given connection with the given circID, it drops the
|
|
||||||
cell. Otherwise, after receiving the CREATE cell, it completes the
|
|
||||||
DH handshake, and replies with a CREATED cell. Upon receiving a
|
|
||||||
CREATED cell, an onion router packs it payload into an EXTENDED relay
|
|
||||||
cell (see section 5), and sends that cell up the circuit. Upon
|
|
||||||
receiving the EXTENDED relay cell, the OP can retrieve g^y.
|
|
||||||
|
|
||||||
(As an optimization, OR implementations may delay processing onions
|
|
||||||
until a break in traffic allows time to do so without harming
|
|
||||||
network latency too greatly.)
|
|
||||||
|
|
||||||
5.3.1. Canonical connections
|
|
||||||
|
|
||||||
It is possible for an attacker to launch a man-in-the-middle attack
|
|
||||||
against a connection by telling OR Alice to extend to OR Bob at some
|
|
||||||
address X controlled by the attacker. The attacker cannot read the
|
|
||||||
encrypted traffic, but the attacker is now in a position to count all
|
|
||||||
bytes sent between Alice and Bob (assuming Alice was not already
|
|
||||||
connected to Bob.)
|
|
||||||
|
|
||||||
To prevent this, when an OR we gets an extend request, it SHOULD use an
|
|
||||||
existing OR connection if the ID matches, and ANY of the following
|
|
||||||
conditions hold:
|
|
||||||
- The IP matches the requested IP.
|
|
||||||
- The OR knows that the IP of the connection it's using is canonical
|
|
||||||
because it was listed in the NETINFO cell.
|
|
||||||
- The OR knows that the IP of the connection it's using is canonical
|
|
||||||
because it was listed in the server descriptor.
|
|
||||||
|
|
||||||
[This is not implemented in Tor 0.2.0.23-rc.]
|
|
||||||
|
|
||||||
5.4. Tearing down circuits
|
|
||||||
|
|
||||||
Circuits are torn down when an unrecoverable error occurs along
|
|
||||||
the circuit, or when all streams on a circuit are closed and the
|
|
||||||
circuit's intended lifetime is over. Circuits may be torn down
|
|
||||||
either completely or hop-by-hop.
|
|
||||||
|
|
||||||
To tear down a circuit completely, an OR or OP sends a DESTROY
|
|
||||||
cell to the adjacent nodes on that circuit, using the appropriate
|
|
||||||
direction's circID.
|
|
||||||
|
|
||||||
Upon receiving an outgoing DESTROY cell, an OR frees resources
|
|
||||||
associated with the corresponding circuit. If it's not the end of
|
|
||||||
the circuit, it sends a DESTROY cell for that circuit to the next OR
|
|
||||||
in the circuit. If the node is the end of the circuit, then it tears
|
|
||||||
down any associated edge connections (see section 6.1).
|
|
||||||
|
|
||||||
After a DESTROY cell has been processed, an OR ignores all data or
|
|
||||||
destroy cells for the corresponding circuit.
|
|
||||||
|
|
||||||
To tear down part of a circuit, the OP may send a RELAY_TRUNCATE cell
|
|
||||||
signaling a given OR (Stream ID zero). That OR sends a DESTROY
|
|
||||||
cell to the next node in the circuit, and replies to the OP with a
|
|
||||||
RELAY_TRUNCATED cell.
|
|
||||||
|
|
||||||
When an unrecoverable error occurs along one connection in a
|
|
||||||
circuit, the nodes on either side of the connection should, if they
|
|
||||||
are able, act as follows: the node closer to the OP should send a
|
|
||||||
RELAY_TRUNCATED cell towards the OP; the node farther from the OP
|
|
||||||
should send a DESTROY cell down the circuit.
|
|
||||||
|
|
||||||
The payload of a RELAY_TRUNCATED or DESTROY cell contains a single octet,
|
|
||||||
describing why the circuit is being closed or truncated. When sending a
|
|
||||||
TRUNCATED or DESTROY cell because of another TRUNCATED or DESTROY cell,
|
|
||||||
the error code should be propagated. The origin of a circuit always sets
|
|
||||||
this error code to 0, to avoid leaking its version.
|
|
||||||
|
|
||||||
The error codes are:
|
|
||||||
0 -- NONE (No reason given.)
|
|
||||||
1 -- PROTOCOL (Tor protocol violation.)
|
|
||||||
2 -- INTERNAL (Internal error.)
|
|
||||||
3 -- REQUESTED (A client sent a TRUNCATE command.)
|
|
||||||
4 -- HIBERNATING (Not currently operating; trying to save bandwidth.)
|
|
||||||
5 -- RESOURCELIMIT (Out of memory, sockets, or circuit IDs.)
|
|
||||||
6 -- CONNECTFAILED (Unable to reach server.)
|
|
||||||
7 -- OR_IDENTITY (Connected to server, but its OR identity was not
|
|
||||||
as expected.)
|
|
||||||
8 -- OR_CONN_CLOSED (The OR connection that was carrying this circuit
|
|
||||||
died.)
|
|
||||||
9 -- FINISHED (The circuit has expired for being dirty or old.)
|
|
||||||
10 -- TIMEOUT (Circuit construction took too long)
|
|
||||||
11 -- DESTROYED (The circuit was destroyed w/o client TRUNCATE)
|
|
||||||
12 -- NOSUCHSERVICE (Request for unknown hidden service)
|
|
||||||
|
|
||||||
5.5. Routing relay cells
|
|
||||||
|
|
||||||
When an OR receives a RELAY or RELAY_EARLY cell, it checks the cell's
|
|
||||||
circID and determines whether it has a corresponding circuit along that
|
|
||||||
connection. If not, the OR drops the cell.
|
|
||||||
|
|
||||||
Otherwise, if the OR is not at the OP edge of the circuit (that is,
|
|
||||||
either an 'exit node' or a non-edge node), it de/encrypts the payload
|
|
||||||
with the stream cipher, as follows:
|
|
||||||
'Forward' relay cell (same direction as CREATE):
|
|
||||||
Use Kf as key; decrypt.
|
|
||||||
'Back' relay cell (opposite direction from CREATE):
|
|
||||||
Use Kb as key; encrypt.
|
|
||||||
Note that in counter mode, decrypt and encrypt are the same operation.
|
|
||||||
|
|
||||||
The OR then decides whether it recognizes the relay cell, by
|
|
||||||
inspecting the payload as described in section 6.1 below. If the OR
|
|
||||||
recognizes the cell, it processes the contents of the relay cell.
|
|
||||||
Otherwise, it passes the decrypted relay cell along the circuit if
|
|
||||||
the circuit continues. If the OR at the end of the circuit
|
|
||||||
encounters an unrecognized relay cell, an error has occurred: the OR
|
|
||||||
sends a DESTROY cell to tear down the circuit.
|
|
||||||
|
|
||||||
When a relay cell arrives at an OP, the OP decrypts the payload
|
|
||||||
with the stream cipher as follows:
|
|
||||||
OP receives data cell:
|
|
||||||
For I=N...1,
|
|
||||||
Decrypt with Kb_I. If the payload is recognized (see
|
|
||||||
section 6..1), then stop and process the payload.
|
|
||||||
|
|
||||||
For more information, see section 6 below.
|
|
||||||
|
|
||||||
5.6. Handling relay_early cells
|
|
||||||
|
|
||||||
A RELAY_EARLY cell is designed to limit the length any circuit can reach.
|
|
||||||
When an OR receives a RELAY_EARLY cell, and the next node in the circuit
|
|
||||||
is speaking v2 of the link protocol or later, the OR relays the cell as a
|
|
||||||
RELAY_EARLY cell. Otherwise, it relays it as a RELAY cell.
|
|
||||||
|
|
||||||
If a node ever receives more than 8 RELAY_EARLY cells on a given
|
|
||||||
outbound circuit, it SHOULD close the circuit. (For historical reasons,
|
|
||||||
we don't limit the number of inbound RELAY_EARLY cells; they should
|
|
||||||
be harmless anyway because clients won't accept extend requests. See
|
|
||||||
bug 1038.)
|
|
||||||
|
|
||||||
When speaking v2 of the link protocol or later, clients MUST only send
|
|
||||||
EXTEND cells inside RELAY_EARLY cells. Clients SHOULD send the first ~8
|
|
||||||
RELAY cells that are not targeted at the first hop of any circuit as
|
|
||||||
RELAY_EARLY cells too, in order to partially conceal the circuit length.
|
|
||||||
|
|
||||||
[In a future version of Tor, servers will reject any EXTEND cell not
|
|
||||||
received in a RELAY_EARLY cell. See proposal 110.]
|
|
||||||
|
|
||||||
6. Application connections and stream management
|
|
||||||
|
|
||||||
6.1. Relay cells
|
|
||||||
|
|
||||||
Within a circuit, the OP and the exit node use the contents of
|
|
||||||
RELAY packets to tunnel end-to-end commands and TCP connections
|
|
||||||
("Streams") across circuits. End-to-end commands can be initiated
|
|
||||||
by either edge; streams are initiated by the OP.
|
|
||||||
|
|
||||||
The payload of each unencrypted RELAY cell consists of:
|
|
||||||
Relay command [1 byte]
|
|
||||||
'Recognized' [2 bytes]
|
|
||||||
StreamID [2 bytes]
|
|
||||||
Digest [4 bytes]
|
|
||||||
Length [2 bytes]
|
|
||||||
Data [CELL_LEN-14 bytes]
|
|
||||||
|
|
||||||
The relay commands are:
|
|
||||||
1 -- RELAY_BEGIN [forward]
|
|
||||||
2 -- RELAY_DATA [forward or backward]
|
|
||||||
3 -- RELAY_END [forward or backward]
|
|
||||||
4 -- RELAY_CONNECTED [backward]
|
|
||||||
5 -- RELAY_SENDME [forward or backward] [sometimes control]
|
|
||||||
6 -- RELAY_EXTEND [forward] [control]
|
|
||||||
7 -- RELAY_EXTENDED [backward] [control]
|
|
||||||
8 -- RELAY_TRUNCATE [forward] [control]
|
|
||||||
9 -- RELAY_TRUNCATED [backward] [control]
|
|
||||||
10 -- RELAY_DROP [forward or backward] [control]
|
|
||||||
11 -- RELAY_RESOLVE [forward]
|
|
||||||
12 -- RELAY_RESOLVED [backward]
|
|
||||||
13 -- RELAY_BEGIN_DIR [forward]
|
|
||||||
|
|
||||||
32..40 -- Used for hidden services; see rend-spec.txt.
|
|
||||||
|
|
||||||
Commands labelled as "forward" must only be sent by the originator
|
|
||||||
of the circuit. Commands labelled as "backward" must only be sent by
|
|
||||||
other nodes in the circuit back to the originator. Commands marked
|
|
||||||
as either can be sent either by the originator or other nodes.
|
|
||||||
|
|
||||||
The 'recognized' field in any unencrypted relay payload is always set
|
|
||||||
to zero; the 'digest' field is computed as the first four bytes of
|
|
||||||
the running digest of all the bytes that have been destined for
|
|
||||||
this hop of the circuit or originated from this hop of the circuit,
|
|
||||||
seeded from Df or Db respectively (obtained in section 5.2 above),
|
|
||||||
and including this RELAY cell's entire payload (taken with the digest
|
|
||||||
field set to zero).
|
|
||||||
|
|
||||||
When the 'recognized' field of a RELAY cell is zero, and the digest
|
|
||||||
is correct, the cell is considered "recognized" for the purposes of
|
|
||||||
decryption (see section 5.5 above).
|
|
||||||
|
|
||||||
(The digest does not include any bytes from relay cells that do
|
|
||||||
not start or end at this hop of the circuit. That is, it does not
|
|
||||||
include forwarded data. Therefore if 'recognized' is zero but the
|
|
||||||
digest does not match, the running digest at that node should
|
|
||||||
not be updated, and the cell should be forwarded on.)
|
|
||||||
|
|
||||||
All RELAY cells pertaining to the same tunneled stream have the
|
|
||||||
same stream ID. StreamIDs are chosen arbitrarily by the OP. RELAY
|
|
||||||
cells that affect the entire circuit rather than a particular
|
|
||||||
stream use a StreamID of zero -- they are marked in the table above
|
|
||||||
as "[control]" style cells. (Sendme cells are marked as "sometimes
|
|
||||||
control" because they can take include a StreamID or not depending
|
|
||||||
on their purpose -- see Section 7.)
|
|
||||||
|
|
||||||
The 'Length' field of a relay cell contains the number of bytes in
|
|
||||||
the relay payload which contain real payload data. The remainder of
|
|
||||||
the payload is padded with NUL bytes.
|
|
||||||
|
|
||||||
If the RELAY cell is recognized but the relay command is not
|
|
||||||
understood, the cell must be dropped and ignored. Its contents
|
|
||||||
still count with respect to the digests, though.
|
|
||||||
|
|
||||||
6.2. Opening streams and transferring data
|
|
||||||
|
|
||||||
To open a new anonymized TCP connection, the OP chooses an open
|
|
||||||
circuit to an exit that may be able to connect to the destination
|
|
||||||
address, selects an arbitrary StreamID not yet used on that circuit,
|
|
||||||
and constructs a RELAY_BEGIN cell with a payload encoding the address
|
|
||||||
and port of the destination host. The payload format is:
|
|
||||||
|
|
||||||
ADDRESS | ':' | PORT | [00]
|
|
||||||
|
|
||||||
where ADDRESS can be a DNS hostname, or an IPv4 address in
|
|
||||||
dotted-quad format, or an IPv6 address surrounded by square brackets;
|
|
||||||
and where PORT is a decimal integer between 1 and 65535, inclusive.
|
|
||||||
|
|
||||||
[What is the [00] for? -NM]
|
|
||||||
[It's so the payload is easy to parse out with string funcs -RD]
|
|
||||||
|
|
||||||
Upon receiving this cell, the exit node resolves the address as
|
|
||||||
necessary, and opens a new TCP connection to the target port. If the
|
|
||||||
address cannot be resolved, or a connection can't be established, the
|
|
||||||
exit node replies with a RELAY_END cell. (See 6.4 below.)
|
|
||||||
Otherwise, the exit node replies with a RELAY_CONNECTED cell, whose
|
|
||||||
payload is in one of the following formats:
|
|
||||||
The IPv4 address to which the connection was made [4 octets]
|
|
||||||
A number of seconds (TTL) for which the address may be cached [4 octets]
|
|
||||||
or
|
|
||||||
Four zero-valued octets [4 octets]
|
|
||||||
An address type (6) [1 octet]
|
|
||||||
The IPv6 address to which the connection was made [16 octets]
|
|
||||||
A number of seconds (TTL) for which the address may be cached [4 octets]
|
|
||||||
[XXXX No version of Tor currently generates the IPv6 format.]
|
|
||||||
|
|
||||||
[Tor servers before 0.1.2.0 set the TTL field to a fixed value. Later
|
|
||||||
versions set the TTL to the last value seen from a DNS server, and expire
|
|
||||||
their own cached entries after a fixed interval. This prevents certain
|
|
||||||
attacks.]
|
|
||||||
|
|
||||||
The OP waits for a RELAY_CONNECTED cell before sending any data.
|
|
||||||
Once a connection has been established, the OP and exit node
|
|
||||||
package stream data in RELAY_DATA cells, and upon receiving such
|
|
||||||
cells, echo their contents to the corresponding TCP stream.
|
|
||||||
RELAY_DATA cells sent to unrecognized streams are dropped.
|
|
||||||
|
|
||||||
Relay RELAY_DROP cells are long-range dummies; upon receiving such
|
|
||||||
a cell, the OR or OP must drop it.
|
|
||||||
|
|
||||||
6.2.1. Opening a directory stream
|
|
||||||
|
|
||||||
If a Tor server is a directory server, it should respond to a
|
|
||||||
RELAY_BEGIN_DIR cell as if it had received a BEGIN cell requesting a
|
|
||||||
connection to its directory port. RELAY_BEGIN_DIR cells ignore exit
|
|
||||||
policy, since the stream is local to the Tor process.
|
|
||||||
|
|
||||||
If the Tor server is not running a directory service, it should respond
|
|
||||||
with a REASON_NOTDIRECTORY RELAY_END cell.
|
|
||||||
|
|
||||||
Clients MUST generate an all-zero payload for RELAY_BEGIN_DIR cells,
|
|
||||||
and servers MUST ignore the payload.
|
|
||||||
|
|
||||||
[RELAY_BEGIN_DIR was not supported before Tor 0.1.2.2-alpha; clients
|
|
||||||
SHOULD NOT send it to routers running earlier versions of Tor.]
|
|
||||||
|
|
||||||
6.3. Closing streams
|
|
||||||
|
|
||||||
When an anonymized TCP connection is closed, or an edge node
|
|
||||||
encounters error on any stream, it sends a 'RELAY_END' cell along the
|
|
||||||
circuit (if possible) and closes the TCP connection immediately. If
|
|
||||||
an edge node receives a 'RELAY_END' cell for any stream, it closes
|
|
||||||
the TCP connection completely, and sends nothing more along the
|
|
||||||
circuit for that stream.
|
|
||||||
|
|
||||||
The payload of a RELAY_END cell begins with a single 'reason' byte to
|
|
||||||
describe why the stream is closing, plus optional data (depending on
|
|
||||||
the reason.) The values are:
|
|
||||||
|
|
||||||
1 -- REASON_MISC (catch-all for unlisted reasons)
|
|
||||||
2 -- REASON_RESOLVEFAILED (couldn't look up hostname)
|
|
||||||
3 -- REASON_CONNECTREFUSED (remote host refused connection) [*]
|
|
||||||
4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)
|
|
||||||
5 -- REASON_DESTROY (Circuit is being destroyed)
|
|
||||||
6 -- REASON_DONE (Anonymized TCP connection was closed)
|
|
||||||
7 -- REASON_TIMEOUT (Connection timed out, or OR timed out
|
|
||||||
while connecting)
|
|
||||||
8 -- (unallocated) [**]
|
|
||||||
9 -- REASON_HIBERNATING (OR is temporarily hibernating)
|
|
||||||
10 -- REASON_INTERNAL (Internal error at the OR)
|
|
||||||
11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)
|
|
||||||
12 -- REASON_CONNRESET (Connection was unexpectedly reset)
|
|
||||||
13 -- REASON_TORPROTOCOL (Sent when closing connection because of
|
|
||||||
Tor protocol violations.)
|
|
||||||
14 -- REASON_NOTDIRECTORY (Client sent RELAY_BEGIN_DIR to a
|
|
||||||
non-directory server.)
|
|
||||||
|
|
||||||
(With REASON_EXITPOLICY, the 4-byte IPv4 address or 16-byte IPv6 address
|
|
||||||
forms the optional data, along with a 4-byte TTL; no other reason
|
|
||||||
currently has extra data.)
|
|
||||||
|
|
||||||
OPs and ORs MUST accept reasons not on the above list, since future
|
|
||||||
versions of Tor may provide more fine-grained reasons.
|
|
||||||
|
|
||||||
Tors SHOULD NOT send any reason except REASON_MISC for a stream that they
|
|
||||||
have originated.
|
|
||||||
|
|
||||||
[*] Older versions of Tor also send this reason when connections are
|
|
||||||
reset.
|
|
||||||
[**] Due to a bug in versions of Tor through 0095, error reason 8 must
|
|
||||||
remain allocated until that version is obsolete.
|
|
||||||
|
|
||||||
--- [The rest of this section describes unimplemented functionality.]
|
|
||||||
|
|
||||||
Because TCP connections can be half-open, we follow an equivalent
|
|
||||||
to TCP's FIN/FIN-ACK/ACK protocol to close streams.
|
|
||||||
|
|
||||||
An exit connection can have a TCP stream in one of three states:
|
|
||||||
'OPEN', 'DONE_PACKAGING', and 'DONE_DELIVERING'. For the purposes
|
|
||||||
of modeling transitions, we treat 'CLOSED' as a fourth state,
|
|
||||||
although connections in this state are not, in fact, tracked by the
|
|
||||||
onion router.
|
|
||||||
|
|
||||||
A stream begins in the 'OPEN' state. Upon receiving a 'FIN' from
|
|
||||||
the corresponding TCP connection, the edge node sends a 'RELAY_FIN'
|
|
||||||
cell along the circuit and changes its state to 'DONE_PACKAGING'.
|
|
||||||
Upon receiving a 'RELAY_FIN' cell, an edge node sends a 'FIN' to
|
|
||||||
the corresponding TCP connection (e.g., by calling
|
|
||||||
shutdown(SHUT_WR)) and changing its state to 'DONE_DELIVERING'.
|
|
||||||
|
|
||||||
When a stream in already in 'DONE_DELIVERING' receives a 'FIN', it
|
|
||||||
also sends a 'RELAY_FIN' along the circuit, and changes its state
|
|
||||||
to 'CLOSED'. When a stream already in 'DONE_PACKAGING' receives a
|
|
||||||
'RELAY_FIN' cell, it sends a 'FIN' and changes its state to
|
|
||||||
'CLOSED'.
|
|
||||||
|
|
||||||
If an edge node encounters an error on any stream, it sends a
|
|
||||||
'RELAY_END' cell (if possible) and closes the stream immediately.
|
|
||||||
|
|
||||||
6.4. Remote hostname lookup
|
|
||||||
|
|
||||||
To find the address associated with a hostname, the OP sends a
|
|
||||||
RELAY_RESOLVE cell containing the hostname to be resolved with a nul
|
|
||||||
terminating byte. (For a reverse lookup, the OP sends a RELAY_RESOLVE
|
|
||||||
cell containing an in-addr.arpa address.) The OR replies with a
|
|
||||||
RELAY_RESOLVED cell containing a status byte, and any number of
|
|
||||||
answers. Each answer is of the form:
|
|
||||||
Type (1 octet)
|
|
||||||
Length (1 octet)
|
|
||||||
Value (variable-width)
|
|
||||||
TTL (4 octets)
|
|
||||||
"Length" is the length of the Value field.
|
|
||||||
"Type" is one of:
|
|
||||||
0x00 -- Hostname
|
|
||||||
0x04 -- IPv4 address
|
|
||||||
0x06 -- IPv6 address
|
|
||||||
0xF0 -- Error, transient
|
|
||||||
0xF1 -- Error, nontransient
|
|
||||||
|
|
||||||
If any answer has a type of 'Error', then no other answer may be given.
|
|
||||||
|
|
||||||
The RELAY_RESOLVE cell must use a nonzero, distinct streamID; the
|
|
||||||
corresponding RELAY_RESOLVED cell must use the same streamID. No stream
|
|
||||||
is actually created by the OR when resolving the name.
|
|
||||||
|
|
||||||
7. Flow control
|
|
||||||
|
|
||||||
7.1. Link throttling
|
|
||||||
|
|
||||||
Each client or relay should do appropriate bandwidth throttling to
|
|
||||||
keep its user happy.
|
|
||||||
|
|
||||||
Communicants rely on TCP's default flow control to push back when they
|
|
||||||
stop reading.
|
|
||||||
|
|
||||||
The mainline Tor implementation uses token buckets (one for reads,
|
|
||||||
one for writes) for the rate limiting.
|
|
||||||
|
|
||||||
Since 0.2.0.x, Tor has let the user specify an additional pair of
|
|
||||||
token buckets for "relayed" traffic, so people can deploy a Tor relay
|
|
||||||
with strict rate limiting, but also use the same Tor as a client. To
|
|
||||||
avoid partitioning concerns we combine both classes of traffic over a
|
|
||||||
given OR connection, and keep track of the last time we read or wrote
|
|
||||||
a high-priority (non-relayed) cell. If it's been less than N seconds
|
|
||||||
(currently N=30), we give the whole connection high priority, else we
|
|
||||||
give the whole connection low priority. We also give low priority
|
|
||||||
to reads and writes for connections that are serving directory
|
|
||||||
information. See proposal 111 for details.
|
|
||||||
|
|
||||||
7.2. Link padding
|
|
||||||
|
|
||||||
Link padding can be created by sending PADDING cells along the
|
|
||||||
connection; relay cells of type "DROP" can be used for long-range
|
|
||||||
padding.
|
|
||||||
|
|
||||||
Currently nodes are not required to do any sort of link padding or
|
|
||||||
dummy traffic. Because strong attacks exist even with link padding,
|
|
||||||
and because link padding greatly increases the bandwidth requirements
|
|
||||||
for running a node, we plan to leave out link padding until this
|
|
||||||
tradeoff is better understood.
|
|
||||||
|
|
||||||
7.3. Circuit-level flow control
|
|
||||||
|
|
||||||
To control a circuit's bandwidth usage, each OR keeps track of two
|
|
||||||
'windows', consisting of how many RELAY_DATA cells it is allowed to
|
|
||||||
originate (package for transmission), and how many RELAY_DATA cells
|
|
||||||
it is willing to consume (receive for local streams). These limits
|
|
||||||
do not apply to cells that the OR receives from one host and relays
|
|
||||||
to another.
|
|
||||||
|
|
||||||
Each 'window' value is initially set to 1000 data cells
|
|
||||||
in each direction (cells that are not data cells do not affect
|
|
||||||
the window). When an OR is willing to deliver more cells, it sends a
|
|
||||||
RELAY_SENDME cell towards the OP, with Stream ID zero. When an OR
|
|
||||||
receives a RELAY_SENDME cell with stream ID zero, it increments its
|
|
||||||
packaging window.
|
|
||||||
|
|
||||||
Each of these cells increments the corresponding window by 100.
|
|
||||||
|
|
||||||
The OP behaves identically, except that it must track a packaging
|
|
||||||
window and a delivery window for every OR in the circuit.
|
|
||||||
|
|
||||||
An OR or OP sends cells to increment its delivery window when the
|
|
||||||
corresponding window value falls under some threshold (900).
|
|
||||||
|
|
||||||
If a packaging window reaches 0, the OR or OP stops reading from
|
|
||||||
TCP connections for all streams on the corresponding circuit, and
|
|
||||||
sends no more RELAY_DATA cells until receiving a RELAY_SENDME cell.
|
|
||||||
[this stuff is badly worded; copy in the tor-design section -RD]
|
|
||||||
|
|
||||||
7.4. Stream-level flow control
|
|
||||||
|
|
||||||
Edge nodes use RELAY_SENDME cells to implement end-to-end flow
|
|
||||||
control for individual connections across circuits. Similarly to
|
|
||||||
circuit-level flow control, edge nodes begin with a window of cells
|
|
||||||
(500) per stream, and increment the window by a fixed value (50)
|
|
||||||
upon receiving a RELAY_SENDME cell. Edge nodes initiate RELAY_SENDME
|
|
||||||
cells when both a) the window is <= 450, and b) there are less than
|
|
||||||
ten cell payloads remaining to be flushed at that edge.
|
|
||||||
|
|
||||||
A.1. Differences between spec and implementation
|
|
||||||
|
|
||||||
- The current specification requires all ORs to have IPv4 addresses, but
|
|
||||||
allows servers to exit and resolve to IPv6 addresses, and to declare IPv6
|
|
||||||
addresses in their exit policies. The current codebase has no IPv6
|
|
||||||
support at all.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
handlers=java.util.logging.ConsoleHandler
|
|
||||||
.level = INFO
|
|
||||||
|
|
||||||
java.util.logging.ConsoleHandler.level = FINEST
|
|
||||||
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
|
||||||
java.util.logging.SimpleFormatter.format =[%1$tT] %4$s: %5$s%6$s%n
|
|
||||||
|
|
||||||
# com.subgraph.orchid.circuits.level=FINE
|
|
@ -1,309 +0,0 @@
|
|||||||
package com.subgraph.orchid.xmlrpc;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
import org.apache.xmlrpc.XmlRpcException;
|
|
||||||
import org.apache.xmlrpc.XmlRpcRequest;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcClient;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcClientException;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcHttpClientConfig;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcHttpTransport;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcHttpTransportException;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcLiteHttpTransport;
|
|
||||||
import org.apache.xmlrpc.common.XmlRpcStreamRequestConfig;
|
|
||||||
import org.apache.xmlrpc.util.HttpUtil;
|
|
||||||
import org.apache.xmlrpc.util.LimitedInputStream;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Tor;
|
|
||||||
import com.subgraph.orchid.sockets.AndroidSSLSocketFactory;
|
|
||||||
|
|
||||||
public class OrchidXmlRpcTransport extends XmlRpcHttpTransport {
|
|
||||||
|
|
||||||
private final static Logger logger = Logger.getLogger(OrchidXmlRpcTransport.class.getName());
|
|
||||||
|
|
||||||
private final SocketFactory socketFactory;
|
|
||||||
private final SSLContext sslContext;
|
|
||||||
|
|
||||||
private SSLSocketFactory sslSocketFactory;
|
|
||||||
|
|
||||||
public OrchidXmlRpcTransport(XmlRpcClient pClient, SocketFactory socketFactory, SSLContext sslContext) {
|
|
||||||
super(pClient, userAgent);
|
|
||||||
this.socketFactory = socketFactory;
|
|
||||||
this.sslContext = sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized SSLSocketFactory getSSLSocketFactory() {
|
|
||||||
if(sslSocketFactory == null) {
|
|
||||||
sslSocketFactory = createSSLSocketFactory();
|
|
||||||
}
|
|
||||||
return sslSocketFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SSLSocketFactory createSSLSocketFactory() {
|
|
||||||
if(Tor.isAndroidRuntime()) {
|
|
||||||
return createAndroidSSLSocketFactory();
|
|
||||||
}
|
|
||||||
if(sslContext == null) {
|
|
||||||
return (SSLSocketFactory) SSLSocketFactory.getDefault();
|
|
||||||
} else {
|
|
||||||
return sslContext.getSocketFactory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SSLSocketFactory createAndroidSSLSocketFactory() {
|
|
||||||
if(sslContext == null) {
|
|
||||||
try {
|
|
||||||
return new AndroidSSLSocketFactory();
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
logger.severe("Failed to create default ssl context");
|
|
||||||
System.exit(1);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new AndroidSSLSocketFactory(sslContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Socket newSocket(boolean pSSL, String pHostName, int pPort) throws UnknownHostException, IOException {
|
|
||||||
final Socket s = socketFactory.createSocket(pHostName, pPort);
|
|
||||||
if(pSSL) {
|
|
||||||
return getSSLSocketFactory().createSocket(s, pHostName, pPort, true);
|
|
||||||
} else {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String userAgent = USER_AGENT + " (Lite HTTP Transport)";
|
|
||||||
private boolean ssl;
|
|
||||||
private String hostname;
|
|
||||||
private String host;
|
|
||||||
private int port;
|
|
||||||
private String uri;
|
|
||||||
private Socket socket;
|
|
||||||
private OutputStream output;
|
|
||||||
private InputStream input;
|
|
||||||
private final Map<String, Object> headers = new HashMap<String, Object>();
|
|
||||||
private boolean responseGzipCompressed = false;
|
|
||||||
private XmlRpcHttpClientConfig config;
|
|
||||||
|
|
||||||
|
|
||||||
public Object sendRequest(XmlRpcRequest pRequest) throws XmlRpcException {
|
|
||||||
config = (XmlRpcHttpClientConfig) pRequest.getConfig();
|
|
||||||
URL url = config.getServerURL();
|
|
||||||
ssl = "https".equals(url.getProtocol());
|
|
||||||
hostname = url.getHost();
|
|
||||||
int p = url.getPort();
|
|
||||||
port = p < 1 ? 80 : p;
|
|
||||||
String u = url.getFile();
|
|
||||||
uri = (u == null || "".equals(u)) ? "/" : u;
|
|
||||||
host = port == 80 ? hostname : hostname + ":" + port;
|
|
||||||
headers.put("Host", host);
|
|
||||||
return super.sendRequest(pRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setRequestHeader(String pHeader, String pValue) {
|
|
||||||
Object value = headers.get(pHeader);
|
|
||||||
if (value == null) {
|
|
||||||
headers.put(pHeader, pValue);
|
|
||||||
} else {
|
|
||||||
List<Object> list;
|
|
||||||
if (value instanceof String) {
|
|
||||||
list = new ArrayList<Object>();
|
|
||||||
list.add((String)value);
|
|
||||||
headers.put(pHeader, list);
|
|
||||||
} else {
|
|
||||||
list = (List<Object>) value;
|
|
||||||
}
|
|
||||||
list.add(pValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void close() throws XmlRpcClientException {
|
|
||||||
IOException e = null;
|
|
||||||
if (input != null) {
|
|
||||||
try {
|
|
||||||
input.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
e = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (output != null) {
|
|
||||||
try {
|
|
||||||
output.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
if (e != null) {
|
|
||||||
e = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (socket != null) {
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
if (e != null) {
|
|
||||||
e = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e != null) {
|
|
||||||
throw new XmlRpcClientException("Failed to close connection: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OutputStream getOutputStream() throws XmlRpcException {
|
|
||||||
try {
|
|
||||||
final int retries = 3;
|
|
||||||
final int delayMillis = 100;
|
|
||||||
|
|
||||||
for (int tries = 0; ; tries++) {
|
|
||||||
try {
|
|
||||||
socket = newSocket(ssl, hostname, port);
|
|
||||||
output = new BufferedOutputStream(socket.getOutputStream()){
|
|
||||||
/** Closing the output stream would close the whole socket, which we don't want,
|
|
||||||
* because the don't want until the request is processed completely.
|
|
||||||
* A close will later occur within
|
|
||||||
* {@link XmlRpcLiteHttpTransport#close()}.
|
|
||||||
*/
|
|
||||||
public void close() throws IOException {
|
|
||||||
flush();
|
|
||||||
if(!(socket instanceof SSLSocket)) {
|
|
||||||
socket.shutdownOutput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
} catch (ConnectException e) {
|
|
||||||
if (tries >= retries) {
|
|
||||||
throw new XmlRpcException("Failed to connect to "
|
|
||||||
+ hostname + ":" + port + ": " + e.getMessage(), e);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Thread.sleep(delayMillis);
|
|
||||||
} catch (InterruptedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendRequestHeaders(output);
|
|
||||||
return output;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new XmlRpcException("Failed to open connection to "
|
|
||||||
+ hostname + ":" + port + ": " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private byte[] toHTTPBytes(String pValue) throws UnsupportedEncodingException {
|
|
||||||
return pValue.getBytes("US-ASCII");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendHeader(OutputStream pOut, String pKey, String pValue) throws IOException {
|
|
||||||
pOut.write(toHTTPBytes(pKey + ": " + pValue + "\r\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendRequestHeaders(OutputStream pOut) throws IOException {
|
|
||||||
pOut.write(("POST " + uri + " HTTP/1.0\r\n").getBytes("US-ASCII"));
|
|
||||||
for (Iterator iter = headers.entrySet().iterator(); iter.hasNext(); ) {
|
|
||||||
Map.Entry entry = (Map.Entry) iter.next();
|
|
||||||
String key = (String) entry.getKey();
|
|
||||||
Object value = entry.getValue();
|
|
||||||
if (value instanceof String) {
|
|
||||||
sendHeader(pOut, key, (String) value);
|
|
||||||
} else {
|
|
||||||
List list = (List) value;
|
|
||||||
for (int i = 0; i < list.size(); i++) {
|
|
||||||
sendHeader(pOut, key, (String) list.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pOut.write(toHTTPBytes("\r\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isResponseGzipCompressed(XmlRpcStreamRequestConfig pConfig) {
|
|
||||||
return responseGzipCompressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InputStream getInputStream() throws XmlRpcException {
|
|
||||||
final byte[] buffer = new byte[2048];
|
|
||||||
try {
|
|
||||||
// If reply timeout specified, set the socket timeout accordingly
|
|
||||||
if (config.getReplyTimeout() != 0)
|
|
||||||
socket.setSoTimeout(config.getReplyTimeout());
|
|
||||||
input = new BufferedInputStream(socket.getInputStream());
|
|
||||||
// start reading server response headers
|
|
||||||
String line = HttpUtil.readLine(input, buffer);
|
|
||||||
StringTokenizer tokens = new StringTokenizer(line);
|
|
||||||
tokens.nextToken(); // Skip HTTP version
|
|
||||||
String statusCode = tokens.nextToken();
|
|
||||||
String statusMsg = tokens.nextToken("\n\r");
|
|
||||||
final int code;
|
|
||||||
try {
|
|
||||||
code = Integer.parseInt(statusCode);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new XmlRpcClientException("Server returned invalid status code: "
|
|
||||||
+ statusCode + " " + statusMsg, null);
|
|
||||||
}
|
|
||||||
if (code < 200 || code > 299) {
|
|
||||||
throw new XmlRpcHttpTransportException(code, statusMsg);
|
|
||||||
}
|
|
||||||
int contentLength = -1;
|
|
||||||
for (;;) {
|
|
||||||
line = HttpUtil.readLine(input, buffer);
|
|
||||||
if (line == null || "".equals(line)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
line = line.toLowerCase();
|
|
||||||
if (line.startsWith("content-length:")) {
|
|
||||||
contentLength = Integer.parseInt(line.substring("content-length:".length()).trim());
|
|
||||||
} else if (line.startsWith("content-encoding:")) {
|
|
||||||
responseGzipCompressed = HttpUtil.isUsingGzipEncoding(line.substring("content-encoding:".length()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputStream result;
|
|
||||||
if (contentLength == -1) {
|
|
||||||
result = input;
|
|
||||||
} else {
|
|
||||||
result = new LimitedInputStream(input, contentLength);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new XmlRpcClientException("Failed to read server response: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isUsingByteArrayOutput(XmlRpcHttpClientConfig pConfig) {
|
|
||||||
boolean result = super.isUsingByteArrayOutput(pConfig);
|
|
||||||
if (!result) {
|
|
||||||
throw new IllegalStateException("The Content-Length header is required with HTTP/1.0, and HTTP/1.1 is unsupported by the Lite HTTP Transport.");
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void writeRequest(ReqWriter pWriter) throws XmlRpcException, IOException, SAXException {
|
|
||||||
pWriter.write(getOutputStream());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package com.subgraph.orchid.xmlrpc;
|
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcClient;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcTransport;
|
|
||||||
import org.apache.xmlrpc.client.XmlRpcTransportFactory;
|
|
||||||
import com.subgraph.orchid.TorClient;
|
|
||||||
import com.subgraph.orchid.sockets.OrchidSocketFactory;
|
|
||||||
|
|
||||||
public class OrchidXmlRpcTransportFactory implements XmlRpcTransportFactory {
|
|
||||||
private final XmlRpcClient client;
|
|
||||||
private final SSLContext sslContext;
|
|
||||||
private final SocketFactory socketFactory;
|
|
||||||
|
|
||||||
public OrchidXmlRpcTransportFactory(XmlRpcClient client, TorClient torClient) {
|
|
||||||
this(client, torClient, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OrchidXmlRpcTransportFactory(XmlRpcClient client, TorClient torClient, SSLContext sslContext) {
|
|
||||||
this.client = client;
|
|
||||||
this.socketFactory = new OrchidSocketFactory(torClient);
|
|
||||||
this.sslContext = sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public XmlRpcTransport getTransport() {
|
|
||||||
return new OrchidXmlRpcTransport(client, socketFactory, sslContext);
|
|
||||||
}
|
|
||||||
}
|
|
156
orchid/pom.xml
156
orchid/pom.xml
@ -1,156 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.bitcoinj</groupId>
|
|
||||||
<artifactId>bitcoinj-parent</artifactId>
|
|
||||||
<version>0.15-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>orchid</artifactId>
|
|
||||||
<version>1.2.1</version>
|
|
||||||
|
|
||||||
<name>Orchid</name>
|
|
||||||
<description>Tor library</description>
|
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<licenses>
|
|
||||||
<license>
|
|
||||||
<name>The Apache Software License, Version 2.0</name>
|
|
||||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
|
||||||
<distribution>repo</distribution>
|
|
||||||
</license>
|
|
||||||
</licenses>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<sourceDirectory>src</sourceDirectory>
|
|
||||||
<plugins>
|
|
||||||
<!-- Ensure compilation is done under Java 6 for backwards compatibility -->
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<source>1.6</source>
|
|
||||||
<target>1.6</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>doclint-java8-disable</id>
|
|
||||||
<activation>
|
|
||||||
<jdk>[1.8,)</jdk>
|
|
||||||
</activation>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<additionalparam>-Xdoclint:none</additionalparam>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<id>release</id>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-sources</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>jar-no-fork</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-javadocs</id>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<minimizeJar>false</minimizeJar>
|
|
||||||
<filters>
|
|
||||||
<filter>
|
|
||||||
<!-- exclude signatures, the bundling process breaks them for some reason -->
|
|
||||||
<artifact>*:*</artifact>
|
|
||||||
<excludes>
|
|
||||||
<exclude>META-INF/*.SF</exclude>
|
|
||||||
<exclude>META-INF/*.DSA</exclude>
|
|
||||||
<exclude>META-INF/*.RSA</exclude>
|
|
||||||
</excludes>
|
|
||||||
</filter>
|
|
||||||
</filters>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
|
||||||
<shadedClassifierName>bundled</shadedClassifierName>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-gpg-plugin</artifactId>
|
|
||||||
<version>1.5</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>sign-artifacts</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>sign</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<version>4.11</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.guava</groupId>
|
|
||||||
<artifactId>guava</artifactId>
|
|
||||||
<version>18.0</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
|
|
||||||
public interface BridgeRouter extends Router {
|
|
||||||
void setIdentity(HexDigest identity);
|
|
||||||
void setDescriptor(RouterDescriptor descriptor);
|
|
||||||
}
|
|
@ -1,230 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
public interface Cell {
|
|
||||||
/** Command constant for a PADDING type cell. */
|
|
||||||
final static int PADDING = 0;
|
|
||||||
|
|
||||||
/** Command constant for a CREATE type cell. */
|
|
||||||
final static int CREATE = 1;
|
|
||||||
|
|
||||||
/** Command constant for a CREATED type cell. */
|
|
||||||
final static int CREATED = 2;
|
|
||||||
|
|
||||||
/** Command constant for a RELAY type cell. */
|
|
||||||
final static int RELAY = 3;
|
|
||||||
|
|
||||||
/** Command constant for a DESTROY type cell. */
|
|
||||||
final static int DESTROY = 4;
|
|
||||||
|
|
||||||
/** Command constant for a CREATE_FAST type cell. */
|
|
||||||
final static int CREATE_FAST = 5;
|
|
||||||
|
|
||||||
/** Command constant for a CREATED_FAST type cell. */
|
|
||||||
final static int CREATED_FAST = 6;
|
|
||||||
|
|
||||||
/** Command constant for a VERSIONS type cell. */
|
|
||||||
final static int VERSIONS = 7;
|
|
||||||
|
|
||||||
/** Command constant for a NETINFO type cell. */
|
|
||||||
final static int NETINFO = 8;
|
|
||||||
|
|
||||||
/** Command constant for a RELAY_EARLY type cell. */
|
|
||||||
final static int RELAY_EARLY = 9;
|
|
||||||
|
|
||||||
final static int VPADDING = 128;
|
|
||||||
final static int CERTS = 129;
|
|
||||||
final static int AUTH_CHALLENGE = 130;
|
|
||||||
final static int AUTHENTICATE = 131;
|
|
||||||
final static int AUTHORIZE = 132;
|
|
||||||
|
|
||||||
final static int ERROR_NONE = 0;
|
|
||||||
final static int ERROR_PROTOCOL = 1;
|
|
||||||
final static int ERROR_INTERNAL = 2;
|
|
||||||
final static int ERROR_REQUESTED = 3;
|
|
||||||
final static int ERROR_HIBERNATING = 4;
|
|
||||||
final static int ERROR_RESOURCELIMIT = 5;
|
|
||||||
final static int ERROR_CONNECTFAILED = 6;
|
|
||||||
final static int ERROR_OR_IDENTITY = 7;
|
|
||||||
final static int ERROR_OR_CONN_CLOSED = 8;
|
|
||||||
final static int ERROR_FINISHED = 9;
|
|
||||||
final static int ERROR_TIMEOUT = 10;
|
|
||||||
final static int ERROR_DESTROYED = 11;
|
|
||||||
final static int ERROR_NOSUCHSERVICE = 12;
|
|
||||||
|
|
||||||
final static int ADDRESS_TYPE_HOSTNAME = 0x00;
|
|
||||||
final static int ADDRESS_TYPE_IPV4 = 0x04;
|
|
||||||
final static int ADRESS_TYPE_IPV6 = 0x06;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The fixed size of a standard cell.
|
|
||||||
*/
|
|
||||||
final static int CELL_LEN = 512;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The length of a standard cell header.
|
|
||||||
*/
|
|
||||||
final static int CELL_HEADER_LEN = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The header length for a variable length cell (ie: VERSIONS)
|
|
||||||
*/
|
|
||||||
final static int CELL_VAR_HEADER_LEN = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The length of the payload space in a standard cell.
|
|
||||||
*/
|
|
||||||
final static int CELL_PAYLOAD_LEN = CELL_LEN - CELL_HEADER_LEN;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the circuit id field from this cell.
|
|
||||||
*
|
|
||||||
* @return The circuit id field of this cell.
|
|
||||||
*/
|
|
||||||
int getCircuitId();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the command field from this cell.
|
|
||||||
*
|
|
||||||
* @return The command field of this cell.
|
|
||||||
*/
|
|
||||||
int getCommand();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the internal pointer to the first byte after the cell header.
|
|
||||||
*/
|
|
||||||
void resetToPayload();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the next byte from the cell and increment the internal pointer by one byte.
|
|
||||||
*
|
|
||||||
* @return The byte at the current pointer location.
|
|
||||||
*/
|
|
||||||
int getByte();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the byte at the specified offset into the cell.
|
|
||||||
*
|
|
||||||
* @param index The cell offset.
|
|
||||||
* @return The byte at the specified offset.
|
|
||||||
*/
|
|
||||||
int getByteAt(int index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the next 16-bit big endian value from the cell and increment the internal pointer by two bytes.
|
|
||||||
*
|
|
||||||
* @return The 16-bit short value at the current pointer location.
|
|
||||||
*/
|
|
||||||
int getShort();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the 16-bit big endian value at the specified offset into the cell.
|
|
||||||
*
|
|
||||||
* @param index The cell offset.
|
|
||||||
* @return The 16-bit short value at the specified offset.
|
|
||||||
*/
|
|
||||||
int getShortAt(int index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the next 32-bit big endian value from the cell and increment the internal pointer by four bytes.
|
|
||||||
*
|
|
||||||
* @return The 32-bit integer value at the current pointer location.
|
|
||||||
*/
|
|
||||||
int getInt();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy <code>buffer.length</code> bytes from the cell into <code>buffer</code>. The data is copied starting
|
|
||||||
* from the current internal pointer location and afterwards the internal pointer is incremented by <code>buffer.length</code>
|
|
||||||
* bytes.
|
|
||||||
*
|
|
||||||
* @param buffer The array of bytes to copy the cell data into.
|
|
||||||
*/
|
|
||||||
void getByteArray(byte[] buffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes already packed (for outgoing cells) or unpacked (for incoming cells). This is
|
|
||||||
* equivalent to the internal pointer position.
|
|
||||||
*
|
|
||||||
* @return The number of bytes already consumed from this cell.
|
|
||||||
*/
|
|
||||||
int cellBytesConsumed();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes remaining between the current internal pointer and the end of the cell. If fields
|
|
||||||
* are being added to a new cell for transmission then this value indicates the remaining space in bytes for
|
|
||||||
* adding new data. If fields are being read from a received cell then this value describes the number of bytes
|
|
||||||
* which can be read without overflowing the cell.
|
|
||||||
*
|
|
||||||
* @return The number of payload bytes remaining in this cell.
|
|
||||||
*/
|
|
||||||
int cellBytesRemaining();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a byte at the current pointer location and increment the pointer by one byte.
|
|
||||||
*
|
|
||||||
* @param value The byte value to store.
|
|
||||||
*/
|
|
||||||
void putByte(int value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a byte at the specified offset into the cell.
|
|
||||||
*
|
|
||||||
* @param index The offset in bytes into the cell.
|
|
||||||
* @param value The byte value to store.
|
|
||||||
*/
|
|
||||||
void putByteAt(int index, int value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a 16-bit short value in big endian order at the current pointer location and
|
|
||||||
* increment the pointer by two bytes.
|
|
||||||
*
|
|
||||||
* @param value The 16-bit short value to store.
|
|
||||||
*/
|
|
||||||
void putShort(int value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a 16-bit short value in big endian byte order at the specified offset into the cell
|
|
||||||
* and increment the pointer by two bytes.
|
|
||||||
*
|
|
||||||
* @param index The offset in bytes into the cell.
|
|
||||||
* @param value The 16-bit short value to store.
|
|
||||||
*/
|
|
||||||
void putShortAt(int index, int value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a 32-bit integer value in big endian order at the current pointer location and
|
|
||||||
* increment the pointer by 4 bytes.
|
|
||||||
*
|
|
||||||
* @param value The 32-bit integer value to store.
|
|
||||||
*/
|
|
||||||
void putInt(int value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the entire array <code>data</code> at the current pointer location and increment
|
|
||||||
* the pointer by <code>data.length</code> bytes.
|
|
||||||
*
|
|
||||||
* @param data The array of bytes to store in the cell.
|
|
||||||
*/
|
|
||||||
void putByteArray(byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store <code>length</code> bytes of the byte array <code>data</code> starting from
|
|
||||||
* <code>offset</code> into the array at the current pointer location and increment
|
|
||||||
* the pointer by <code>length</code> bytes.
|
|
||||||
*
|
|
||||||
* @param data The source array of bytes.
|
|
||||||
* @param offset The offset into the source array.
|
|
||||||
* @param length The number of bytes from the source array to store.
|
|
||||||
*/
|
|
||||||
void putByteArray(byte[] data, int offset, int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the entire cell data as a raw array of bytes. For all cells except
|
|
||||||
* <code>VERSIONS</code>, this array will be exactly <code>CELL_LEN</code> bytes long.
|
|
||||||
*
|
|
||||||
* @return The cell data as an array of bytes.
|
|
||||||
*/
|
|
||||||
byte[] getCellBytes();
|
|
||||||
|
|
||||||
void putString(String string);
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Circuit represents a logical path through multiple ORs. Circuits are described in
|
|
||||||
* section 5 of tor-spec.txt.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface Circuit {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return <code>true</code> if the circuit is presently in the connected state or
|
|
||||||
* <code>false</code> otherwise.
|
|
||||||
*
|
|
||||||
* @return Returns <code>true</code> if the circuit is presently connected, or
|
|
||||||
* <code>false</code> otherwise.
|
|
||||||
*/
|
|
||||||
boolean isConnected();
|
|
||||||
|
|
||||||
boolean isPending();
|
|
||||||
|
|
||||||
boolean isClean();
|
|
||||||
|
|
||||||
boolean isMarkedForClose();
|
|
||||||
|
|
||||||
int getSecondsDirty();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the entry router <code>Connection</code> object of this Circuit. Throws
|
|
||||||
* a TorException if the circuit is not currently open.
|
|
||||||
*
|
|
||||||
* @return The Connection object for the network connection to the entry router of this
|
|
||||||
* circuit.
|
|
||||||
* @throws TorException If this circuit is not currently connected.
|
|
||||||
*/
|
|
||||||
Connection getConnection();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the curcuit id value for this circuit.
|
|
||||||
*
|
|
||||||
* @return The circuit id value for this circuit.
|
|
||||||
*/
|
|
||||||
int getCircuitId();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new relay cell which is configured for delivery to the specified
|
|
||||||
* circuit <code>targetNode</code> with command value <code>relayCommand</code>
|
|
||||||
* and a stream id value of <code>streamId</code>. The returned <code>RelayCell</code>
|
|
||||||
* can then be used to populate the payload of the cell before delivering it.
|
|
||||||
*
|
|
||||||
* @param relayCommand The command value to send in the relay cell header.
|
|
||||||
* @param streamId The stream id value to send in the relay cell header.
|
|
||||||
* @param targetNode The target circuit node to encrypt this cell for.
|
|
||||||
* @return A newly created relay cell object.
|
|
||||||
*/
|
|
||||||
RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next relay response cell received on this circuit. If no response is
|
|
||||||
* received within <code>CIRCUIT_RELAY_RESPONSE_TIMEOUT</code> milliseconds, <code>null</code>
|
|
||||||
* is returned.
|
|
||||||
*
|
|
||||||
* @return The next relay response cell received on this circuit or <code>null</code> if
|
|
||||||
* a timeout is reached before the next relay cell arrives.
|
|
||||||
*/
|
|
||||||
RelayCell receiveRelayCell();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt and deliver the relay cell <code>cell</code>.
|
|
||||||
*
|
|
||||||
* @param cell The relay cell to deliver over this circuit.
|
|
||||||
*/
|
|
||||||
void sendRelayCell(RelayCell cell);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the last node or 'hop' in this circuit.
|
|
||||||
*
|
|
||||||
* @return The final 'hop' or node of this circuit.
|
|
||||||
*/
|
|
||||||
CircuitNode getFinalCircuitNode();
|
|
||||||
|
|
||||||
|
|
||||||
void destroyCircuit();
|
|
||||||
|
|
||||||
void deliverRelayCell(Cell cell);
|
|
||||||
|
|
||||||
void deliverControlCell(Cell cell);
|
|
||||||
|
|
||||||
List<Stream> getActiveStreams();
|
|
||||||
|
|
||||||
void markForClose();
|
|
||||||
|
|
||||||
void appendNode(CircuitNode node);
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This callback interface is used for reporting progress when
|
|
||||||
* opening a new circuit. An instance of this interface is passed
|
|
||||||
* to the {@link Circuit#openCircuit(java.util.List, CircuitBuildHandler)}
|
|
||||||
* method.
|
|
||||||
*
|
|
||||||
* The normal sequence of callbacks which are fired when a circuit is opened
|
|
||||||
* successfully is {@link #connectionCompleted(Connection)} for the initial
|
|
||||||
* connection to the entry router, followed by one or more
|
|
||||||
* {@link #nodeAdded(CircuitNode)} as the circuit is extended with new nodes.
|
|
||||||
* When all requested nodes in the path have been added successfully to the
|
|
||||||
* circuit {@link #circuitBuildCompleted(Circuit)} is called and passed the
|
|
||||||
* newly constructed circuit.
|
|
||||||
*
|
|
||||||
* @see Circuit#openCircuit()
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface CircuitBuildHandler {
|
|
||||||
/**
|
|
||||||
* Called when a network connection to the entry node has completed
|
|
||||||
* successfully or if a network connection to the specified entry router
|
|
||||||
* already exists.
|
|
||||||
*
|
|
||||||
* @param connection The completed connection instance.
|
|
||||||
*/
|
|
||||||
void connectionCompleted(Connection connection);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The circuit build has failed because the network connection to the
|
|
||||||
* entry node failed. No further callback methods will be called after
|
|
||||||
* this failure has been reported.
|
|
||||||
*
|
|
||||||
* @param reason A description of the reason for failing to connect to
|
|
||||||
* the entry node.
|
|
||||||
*/
|
|
||||||
void connectionFailed(String reason);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A node or 'hop' has been added to the circuit which is being created.
|
|
||||||
*
|
|
||||||
* @param node The newly added circuit node.
|
|
||||||
*/
|
|
||||||
void nodeAdded(CircuitNode node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The circuit has been successfully built and is ready for use.
|
|
||||||
*
|
|
||||||
* @param circuit The newly constructed circuit.
|
|
||||||
*/
|
|
||||||
void circuitBuildCompleted(Circuit circuit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if the circuit build fails after connecting to the entry node.
|
|
||||||
*
|
|
||||||
* @param reason A description of the reason the circuit build has failed.
|
|
||||||
*/
|
|
||||||
void circuitBuildFailed(String reason);
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
|
|
||||||
|
|
||||||
public interface CircuitManager {
|
|
||||||
|
|
||||||
static int DIRECTORY_PURPOSE_CONSENSUS = 1;
|
|
||||||
static int DIRECTORY_PURPOSE_CERTIFICATES = 2;
|
|
||||||
static int DIRECTORY_PURPOSE_DESCRIPTORS = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begin automatically building new circuits in the background.
|
|
||||||
*/
|
|
||||||
void startBuildingCircuits();
|
|
||||||
void stopBuildingCircuits(boolean killCircuits);
|
|
||||||
/**
|
|
||||||
* Attempt to open an exit stream to the specified destination <code>hostname</code> and
|
|
||||||
* <code>port</code>.
|
|
||||||
*
|
|
||||||
* @param hostname The name of the host to open an exit connection to.
|
|
||||||
* @param port The port to open an exit connection to.
|
|
||||||
* @return The status response result of attempting to open the exit connection.
|
|
||||||
*/
|
|
||||||
Stream openExitStreamTo(String hostname, int port) throws InterruptedException, TimeoutException, OpenFailedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to open an exit stream to the destination specified by <code>address</code> and
|
|
||||||
* <code>port</code>.
|
|
||||||
*
|
|
||||||
* @param address The address to open an exit connection to.
|
|
||||||
* @param port The port to open an exit connection to.
|
|
||||||
* @return The status response result of attempting the open the exit connection.
|
|
||||||
*/
|
|
||||||
Stream openExitStreamTo(IPv4Address address, int port) throws InterruptedException, TimeoutException, OpenFailedException;
|
|
||||||
|
|
||||||
|
|
||||||
Stream openDirectoryStream(int purpose) throws InterruptedException, TimeoutException, OpenFailedException;
|
|
||||||
|
|
||||||
Stream openDirectoryStream() throws InterruptedException, TimeoutException, OpenFailedException;
|
|
||||||
|
|
||||||
DirectoryCircuit openDirectoryCircuit() throws OpenFailedException;
|
|
||||||
Circuit getCleanInternalCircuit() throws InterruptedException;
|
|
||||||
|
|
||||||
ExitCircuit openExitCircuitTo(List<Router> path) throws OpenFailedException;
|
|
||||||
InternalCircuit openInternalCircuitTo(List<Router> path) throws OpenFailedException;
|
|
||||||
DirectoryCircuit openDirectoryCircuitTo(List<Router> path) throws OpenFailedException;
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the state of a single onion router hop in a connected or connecting {@link Circuit}
|
|
||||||
*/
|
|
||||||
public interface CircuitNode {
|
|
||||||
/**
|
|
||||||
* Return the {@link Router} associated with this node.
|
|
||||||
*
|
|
||||||
* @return The {@link Router} for this hop of the circuit chain.
|
|
||||||
*/
|
|
||||||
Router getRouter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the 'forward' cryptographic digest state for this
|
|
||||||
* node with the contents of <code>cell</code>
|
|
||||||
*
|
|
||||||
* @param cell The {@link RelayCell} to add to the digest.
|
|
||||||
*/
|
|
||||||
void updateForwardDigest(RelayCell cell);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the current 'forward' running digest value for this
|
|
||||||
* node as an array of <code>TOR_DIGEST_SIZE</code> bytes.
|
|
||||||
*
|
|
||||||
* @return The current 'forward' running digest value for this node.
|
|
||||||
*/
|
|
||||||
byte[] getForwardDigestBytes();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt a {@link RelayCell} for this node with the current
|
|
||||||
* 'forward' cipher state.
|
|
||||||
*
|
|
||||||
* @param cell The {@link RelayCell} to encrypt.
|
|
||||||
*/
|
|
||||||
void encryptForwardCell(RelayCell cell);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link CircuitNode} which immediately preceeds this
|
|
||||||
* one in the circuit node chain or <code>null</code> if this is
|
|
||||||
* the first hop.
|
|
||||||
*
|
|
||||||
* @return The previous {@link CircuitNode} in the chain or <code>
|
|
||||||
* null</code> if this is the first node.
|
|
||||||
*/
|
|
||||||
CircuitNode getPreviousNode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return immediately if the packaging window for this node is open (ie: greater than 0), otherwise
|
|
||||||
* block until the circuit is destroyed or the window is incremented by receiving a RELAY_SENDME cell
|
|
||||||
* from this node.
|
|
||||||
*/
|
|
||||||
void waitForSendWindow();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the packaging window for this node is open (ie: greater than 0) this method
|
|
||||||
* decrements the packaging window by 1 and returns immediately, otherwise it will
|
|
||||||
* block until the circuit is destroyed or the window is incremented by receiving
|
|
||||||
* a RELAY_SENDME cell from this node. This method will always decrement the packaging
|
|
||||||
* window before returning unless the circuit has been destroyed.
|
|
||||||
*/
|
|
||||||
void waitForSendWindowAndDecrement();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called to signal that a RELAY_SENDME cell has been received from this
|
|
||||||
* node and the packaging window should be incremented. This will also wake up any threads
|
|
||||||
* that are waiting for the packaging window to open.
|
|
||||||
*/
|
|
||||||
void incrementSendWindow();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called when a RELAY_DATA cell is received from this node to decrement
|
|
||||||
* the deliver window counter.
|
|
||||||
*/
|
|
||||||
void decrementDeliverWindow();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Examines the delivery window and determines if it would be an appropriate time to
|
|
||||||
* send a RELAY_SENDME cell. If this method returns true, it increments the delivery
|
|
||||||
* window assuming that a RELAY_SENDME cell will be transmitted.
|
|
||||||
*
|
|
||||||
* @return Returns true if the deliver window is small enough that sending a RELAY_SENDME
|
|
||||||
* cell would be appropriate.
|
|
||||||
*/
|
|
||||||
boolean considerSendingSendme();
|
|
||||||
|
|
||||||
boolean decryptBackwardCell(Cell cell);
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A network connection to a Tor onion router.
|
|
||||||
*/
|
|
||||||
public interface Connection {
|
|
||||||
/**
|
|
||||||
* Return the {@link Router} associated with this connection.
|
|
||||||
*
|
|
||||||
* @return The entry router this connection represents.
|
|
||||||
*/
|
|
||||||
Router getRouter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return <code>true</code> if the socket for this connection has been closed. Otherwise, <code>false</code>.
|
|
||||||
*
|
|
||||||
* @return <code>true</code> if this connection is closed or <code>false</code> otherwise.
|
|
||||||
*/
|
|
||||||
boolean isClosed();
|
|
||||||
/**
|
|
||||||
* Send a protocol {@link Cell} on this connection.
|
|
||||||
*
|
|
||||||
* @param cell The {@link Cell} to transfer.
|
|
||||||
* @throws ConnectionIOException If the cell could not be send because the connection is not connected
|
|
||||||
* or if an error occured while sending the cell data.
|
|
||||||
*/
|
|
||||||
void sendCell(Cell cell) throws ConnectionIOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a Circuit which has been bound to this Connection by a previous call to {@link #bindCircuit(Circuit) bindCircuit}.
|
|
||||||
* After removing a Circuit, any further received incoming cells for the Circuit will be discarded.
|
|
||||||
*
|
|
||||||
* @param circuit The Circuit to remove.
|
|
||||||
*/
|
|
||||||
void removeCircuit(Circuit circuit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choose an available circuit id value and bind this Circuit to that id value, returning the id value.
|
|
||||||
* Once bound, any incoming relay cells will be delivered to the Circuit with {@link Circuit#deliverRelayCell(Cell)}
|
|
||||||
* and other cells will be delivered with {@link Circuit#deliverControlCell(Cell)}.
|
|
||||||
*
|
|
||||||
* @param circuit The Circuit to bind to this connection.
|
|
||||||
* @return the circuit id value for this binding.
|
|
||||||
*/
|
|
||||||
int bindCircuit(Circuit circuit);
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
public interface ConnectionCache {
|
|
||||||
/**
|
|
||||||
* Returns a completed connection to the specified router. If an open connection
|
|
||||||
* to the requested router already exists it is returned, otherwise a new connection
|
|
||||||
* is opened.
|
|
||||||
*
|
|
||||||
* @param router The router to which a connection is requested.
|
|
||||||
* @param isDirectoryConnection Is this going to be used as a directory connection.
|
|
||||||
* @return a completed connection to the specified router.
|
|
||||||
* @throws InterruptedException if thread is interrupted while waiting for connection to complete.
|
|
||||||
* @throws ConnectionTimeoutException if timeout expires before connection completes.
|
|
||||||
* @throws ConnectionFailedException if connection fails due to I/O error
|
|
||||||
* @throws ConnectionHandshakeException if connection fails because an error occurred during handshake phase
|
|
||||||
*/
|
|
||||||
Connection getConnectionTo(Router router, boolean isDirectoryConnection) throws InterruptedException, ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException;
|
|
||||||
|
|
||||||
void close();
|
|
||||||
|
|
||||||
boolean isClosed();
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public class ConnectionFailedException extends ConnectionIOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4484347156587613574L;
|
|
||||||
|
|
||||||
public ConnectionFailedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public class ConnectionHandshakeException extends ConnectionIOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -2544633445932967966L;
|
|
||||||
|
|
||||||
public ConnectionHandshakeException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public class ConnectionIOException extends Exception {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -5537650738995969203L;
|
|
||||||
|
|
||||||
public ConnectionIOException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionIOException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public class ConnectionTimeoutException extends ConnectionIOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -6098661610150140151L;
|
|
||||||
|
|
||||||
public ConnectionTimeoutException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionTimeoutException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.Timestamp;
|
|
||||||
|
|
||||||
public interface ConsensusDocument extends Document {
|
|
||||||
enum ConsensusFlavor { NS, MICRODESC };
|
|
||||||
enum SignatureStatus { STATUS_VERIFIED, STATUS_FAILED, STATUS_NEED_CERTS };
|
|
||||||
|
|
||||||
interface RequiredCertificate {
|
|
||||||
int getDownloadFailureCount();
|
|
||||||
void incrementDownloadFailureCount();
|
|
||||||
HexDigest getAuthorityIdentity();
|
|
||||||
HexDigest getSigningKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsensusFlavor getFlavor();
|
|
||||||
Timestamp getValidAfterTime();
|
|
||||||
Timestamp getFreshUntilTime();
|
|
||||||
Timestamp getValidUntilTime();
|
|
||||||
int getConsensusMethod();
|
|
||||||
int getVoteSeconds();
|
|
||||||
int getDistSeconds();
|
|
||||||
Set<String> getClientVersions();
|
|
||||||
Set<String> getServerVersions();
|
|
||||||
boolean isLive();
|
|
||||||
List<RouterStatus> getRouterStatusEntries();
|
|
||||||
|
|
||||||
SignatureStatus verifySignatures();
|
|
||||||
Set<RequiredCertificate> getRequiredCertificates();
|
|
||||||
|
|
||||||
HexDigest getSigningHash();
|
|
||||||
HexDigest getSigningHash256();
|
|
||||||
|
|
||||||
int getCircWindowParameter();
|
|
||||||
int getWeightScaleParameter();
|
|
||||||
|
|
||||||
int getBandwidthWeight(String tag);
|
|
||||||
|
|
||||||
boolean getUseNTorHandshake();
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
|
|
||||||
public interface Descriptor extends Document {
|
|
||||||
enum CacheLocation { NOT_CACHED, CACHED_CACHEFILE, CACHED_JOURNAL }
|
|
||||||
|
|
||||||
HexDigest getDescriptorDigest();
|
|
||||||
void setLastListed(long timestamp);
|
|
||||||
long getLastListed();
|
|
||||||
void setCacheLocation(CacheLocation location);
|
|
||||||
CacheLocation getCacheLocation();
|
|
||||||
int getBodyLength();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the public key used to encrypt EXTEND cells while establishing
|
|
||||||
* a circuit through this router.
|
|
||||||
*
|
|
||||||
* @return The onion routing protocol key for this router.
|
|
||||||
*/
|
|
||||||
TorPublicKey getOnionKey();
|
|
||||||
byte[] getNTorOnionKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the IPv4 address of this router.
|
|
||||||
*
|
|
||||||
* @return The IPv4 address of this router.
|
|
||||||
*/
|
|
||||||
IPv4Address getAddress();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the port on which this node accepts TLS connections
|
|
||||||
* for the main OR protocol, or 0 if no router service is advertised.
|
|
||||||
*
|
|
||||||
* @return The onion routing port, or 0 if not a router.
|
|
||||||
*/
|
|
||||||
int getRouterPort();
|
|
||||||
Set<String> getFamilyMembers();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the exit policy of this router permits connections
|
|
||||||
* to the specified destination endpoint.
|
|
||||||
*
|
|
||||||
* @param address The IPv4 address of the destination.
|
|
||||||
* @param port The destination port.
|
|
||||||
*
|
|
||||||
* @return True if an exit connection to the specified destination is allowed
|
|
||||||
* or false otherwise.
|
|
||||||
*/
|
|
||||||
boolean exitPolicyAccepts(IPv4Address address, int port);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the exit policy of this router accepts most connections
|
|
||||||
* to the specified destination port.
|
|
||||||
*
|
|
||||||
* @param port The destination port.
|
|
||||||
* @return True if an exit connection to the specified destination port is generally allowed
|
|
||||||
* or false otherwise.
|
|
||||||
*/
|
|
||||||
boolean exitPolicyAccepts(int port);
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.ConsensusDocument.RequiredCertificate;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.events.EventHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Main interface for accessing directory information and interacting
|
|
||||||
* with directory authorities and caches.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface Directory {
|
|
||||||
boolean haveMinimumRouterInfo();
|
|
||||||
void loadFromStore();
|
|
||||||
void close();
|
|
||||||
void waitUntilLoaded();
|
|
||||||
void storeCertificates();
|
|
||||||
|
|
||||||
Collection<DirectoryServer> getDirectoryAuthorities();
|
|
||||||
DirectoryServer getRandomDirectoryAuthority();
|
|
||||||
void addCertificate(KeyCertificate certificate);
|
|
||||||
Set<RequiredCertificate> getRequiredCertificates();
|
|
||||||
void addRouterMicrodescriptors(List<RouterMicrodescriptor> microdescriptors);
|
|
||||||
void addRouterDescriptors(List<RouterDescriptor> descriptors);
|
|
||||||
void addConsensusDocument(ConsensusDocument consensus, boolean fromCache);
|
|
||||||
ConsensusDocument getCurrentConsensusDocument();
|
|
||||||
boolean hasPendingConsensus();
|
|
||||||
void registerConsensusChangedHandler(EventHandler handler);
|
|
||||||
void unregisterConsensusChangedHandler(EventHandler handler);
|
|
||||||
Router getRouterByName(String name);
|
|
||||||
Router getRouterByIdentity(HexDigest identity);
|
|
||||||
List<Router> getRouterListByNames(List<String> names);
|
|
||||||
List<Router> getRoutersWithDownloadableDescriptors();
|
|
||||||
List<Router> getAllRouters();
|
|
||||||
|
|
||||||
RouterMicrodescriptor getMicrodescriptorFromCache(HexDigest descriptorDigest);
|
|
||||||
RouterDescriptor getBasicDescriptorFromCache(HexDigest descriptorDigest);
|
|
||||||
|
|
||||||
GuardEntry createGuardEntryFor(Router router);
|
|
||||||
List<GuardEntry> getGuardEntries();
|
|
||||||
void removeGuardEntry(GuardEntry entry);
|
|
||||||
void addGuardEntry(GuardEntry entry);
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
public interface DirectoryCircuit extends Circuit {
|
|
||||||
/**
|
|
||||||
* Open an anonymous connection to the directory service running on the
|
|
||||||
* final node in this circuit.
|
|
||||||
*
|
|
||||||
* @param timeout in milliseconds
|
|
||||||
* @param autoclose if set to true, closing stream also marks this circuit for close
|
|
||||||
*
|
|
||||||
* @return The status response returned by trying to open the stream.
|
|
||||||
*/
|
|
||||||
Stream openDirectoryStream(long timeout, boolean autoclose) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.ConsensusDocument.RequiredCertificate;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.directory.downloader.DirectoryRequestFailedException;
|
|
||||||
|
|
||||||
public interface DirectoryDownloader {
|
|
||||||
void start(Directory directory);
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
RouterDescriptor downloadBridgeDescriptor(Router bridge) throws DirectoryRequestFailedException;
|
|
||||||
|
|
||||||
ConsensusDocument downloadCurrentConsensus(boolean useMicrodescriptors) throws DirectoryRequestFailedException;
|
|
||||||
ConsensusDocument downloadCurrentConsensus(boolean useMicrodescriptors, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
|
||||||
|
|
||||||
List<KeyCertificate> downloadKeyCertificates(Set<RequiredCertificate> required) throws DirectoryRequestFailedException;
|
|
||||||
List<KeyCertificate> downloadKeyCertificates(Set<RequiredCertificate> required, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
|
||||||
|
|
||||||
List<RouterDescriptor> downloadRouterDescriptors(Set<HexDigest> fingerprints) throws DirectoryRequestFailedException;
|
|
||||||
List<RouterDescriptor> downloadRouterDescriptors(Set<HexDigest> fingerprints, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
|
||||||
|
|
||||||
List<RouterMicrodescriptor> downloadRouterMicrodescriptors(Set<HexDigest> fingerprints) throws DirectoryRequestFailedException;
|
|
||||||
List<RouterMicrodescriptor> downloadRouterMicrodescriptors(Set<HexDigest> fingerprints, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a directory authority server or a directory cache.
|
|
||||||
*/
|
|
||||||
public interface DirectoryServer extends Router {
|
|
||||||
int getDirectoryPort();
|
|
||||||
boolean isV2Authority();
|
|
||||||
boolean isV3Authority();
|
|
||||||
HexDigest getV3Identity();
|
|
||||||
boolean isHiddenServiceAuthority();
|
|
||||||
boolean isBridgeAuthority();
|
|
||||||
boolean isExtraInfoCache();
|
|
||||||
|
|
||||||
KeyCertificate getCertificateByFingerprint(HexDigest fingerprint);
|
|
||||||
List<KeyCertificate> getCertificates();
|
|
||||||
void addCertificate(KeyCertificate certificate);
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface DirectoryStore {
|
|
||||||
enum CacheFile {
|
|
||||||
CERTIFICATES("certificates"),
|
|
||||||
CONSENSUS("consensus"),
|
|
||||||
CONSENSUS_MICRODESC("consensus-microdesc"),
|
|
||||||
MICRODESCRIPTOR_CACHE("cached-microdescs"),
|
|
||||||
MICRODESCRIPTOR_JOURNAL("cached-microdescs.new"),
|
|
||||||
DESCRIPTOR_CACHE("cached-descriptors"),
|
|
||||||
DESCRIPTOR_JOURNAL("cached-descriptors.new"),
|
|
||||||
STATE("state");
|
|
||||||
|
|
||||||
final private String filename;
|
|
||||||
|
|
||||||
CacheFile(String filename) {
|
|
||||||
this.filename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFilename() {
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer loadCacheFile(CacheFile cacheFile);
|
|
||||||
void writeData(CacheFile cacheFile, ByteBuffer data);
|
|
||||||
void writeDocument(CacheFile cacheFile, Document document);
|
|
||||||
void writeDocumentList(CacheFile cacheFile, List<? extends Document> documents);
|
|
||||||
void appendDocumentList(CacheFile cacheFile, List<? extends Document> documents);
|
|
||||||
|
|
||||||
void removeCacheFile(CacheFile cacheFile);
|
|
||||||
void removeAllCacheFiles();
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
public interface Document {
|
|
||||||
ByteBuffer getRawDocumentBytes();
|
|
||||||
String getRawDocumentData();
|
|
||||||
boolean isValidDocument();
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
|
||||||
|
|
||||||
public interface ExitCircuit extends Circuit {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open an exit stream from the final node in this circuit to the
|
|
||||||
* specified target address and port.
|
|
||||||
*
|
|
||||||
* @param address The network address of the exit target.
|
|
||||||
* @param port The port of the exit target.
|
|
||||||
* @return The status response returned by trying to open the stream.
|
|
||||||
*/
|
|
||||||
Stream openExitStream(IPv4Address address, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open an exit stream from the final node in this circuit to the
|
|
||||||
* specified target hostname and port.
|
|
||||||
*
|
|
||||||
* @param hostname The network hostname of the exit target.
|
|
||||||
* @param port The port of the exit target.
|
|
||||||
* @return The status response returned by trying to open the stream.
|
|
||||||
*/
|
|
||||||
Stream openExitStream(String hostname, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the final node of this circuit is believed to be able to connect to
|
|
||||||
* the specified <code>ExitTarget</code>. Returns false if the target destination is
|
|
||||||
* not permitted by the exit policy of the final node in this circuit or if the target
|
|
||||||
* has been previously recorded to have failed through this circuit.
|
|
||||||
*
|
|
||||||
* @param target The exit destination.
|
|
||||||
* @return Return true if is likely that the final node of this circuit can connect to the specified exit target.
|
|
||||||
*/
|
|
||||||
boolean canHandleExitTo(ExitTarget target);
|
|
||||||
|
|
||||||
boolean canHandleExitToPort(int port);
|
|
||||||
/**
|
|
||||||
* Records the specified <code>ExitTarget</code> as a failed connection so that {@link #canHandleExitTo(ExitTarget)} will
|
|
||||||
* no longer return true for this exit destination.
|
|
||||||
*
|
|
||||||
* @param target The <code>ExitTarget</code> to which a connection has failed through this circuit.
|
|
||||||
*/
|
|
||||||
public void recordFailedExitTarget(ExitTarget target);
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public interface GuardEntry {
|
|
||||||
boolean isAdded();
|
|
||||||
void markAsDown();
|
|
||||||
void clearDownSince();
|
|
||||||
String getNickname();
|
|
||||||
String getIdentity();
|
|
||||||
String getVersion();
|
|
||||||
Date getCreatedTime();
|
|
||||||
Date getDownSince();
|
|
||||||
Date getLastConnectAttempt();
|
|
||||||
Date getUnlistedSince();
|
|
||||||
boolean testCurrentlyUsable();
|
|
||||||
Router getRouterForEntry();
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
|
|
||||||
public interface HiddenServiceCircuit extends Circuit {
|
|
||||||
Stream openStream(int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public interface InternalCircuit extends Circuit {
|
|
||||||
DirectoryCircuit cannibalizeToDirectory(Router target);
|
|
||||||
Circuit cannibalizeToIntroductionPoint(Router target);
|
|
||||||
HiddenServiceCircuit connectHiddenService(CircuitNode node);
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.data.Timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents a key certificate document as specified in
|
|
||||||
* dir-spec.txt (section 3.1). These documents are published by
|
|
||||||
* directory authorities and bind a long-term identity key to a
|
|
||||||
* more temporary signing key.
|
|
||||||
*/
|
|
||||||
public interface KeyCertificate extends Document {
|
|
||||||
/**
|
|
||||||
* Return the network address of this directory authority
|
|
||||||
* or <code>null</code> if no address was specified in the certificate.
|
|
||||||
*
|
|
||||||
* @return The network address of the directory authority this certificate
|
|
||||||
* belongs to, or <code>null</code> if not available.
|
|
||||||
*/
|
|
||||||
IPv4Address getDirectoryAddress();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the port on which this directory authority answers
|
|
||||||
* directory requests or 0 if no port was specified in the certificate.
|
|
||||||
*
|
|
||||||
* @return The port of this directory authority listens on or 0 if
|
|
||||||
* no port was specified in the certificate.
|
|
||||||
*/
|
|
||||||
int getDirectoryPort();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return fingerprint of the authority identity key as specified in
|
|
||||||
* the certificate.
|
|
||||||
*
|
|
||||||
* @return The authority identity key fingerprint.
|
|
||||||
*/
|
|
||||||
HexDigest getAuthorityFingerprint();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the authority identity public key from the certificate.
|
|
||||||
*
|
|
||||||
* @return The authority identity public key.
|
|
||||||
*/
|
|
||||||
TorPublicKey getAuthorityIdentityKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the authority signing public key from the certificate.
|
|
||||||
*
|
|
||||||
* @return The authority signing public key.
|
|
||||||
*/
|
|
||||||
TorPublicKey getAuthoritySigningKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the time when this document and corresponding keys were
|
|
||||||
* generated.
|
|
||||||
*
|
|
||||||
* @return The time this document was generated and published.
|
|
||||||
*/
|
|
||||||
Timestamp getKeyPublishedTime();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the time after which this document and signing key are
|
|
||||||
* no longer valid.
|
|
||||||
*
|
|
||||||
* @return The expiry time of this document and signing key.
|
|
||||||
*/
|
|
||||||
Timestamp getKeyExpiryTime();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return <code>true</code> if the current time is past the key
|
|
||||||
* expiry time of this certificate.
|
|
||||||
*
|
|
||||||
* @return True if this certificate is currently expired.
|
|
||||||
*/
|
|
||||||
boolean isExpired();
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public class OpenFailedException extends Exception {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1989001056577214666L;
|
|
||||||
|
|
||||||
public OpenFailedException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenFailedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public interface RelayCell extends Cell {
|
|
||||||
|
|
||||||
final static int LENGTH_OFFSET = 12;
|
|
||||||
final static int RECOGNIZED_OFFSET = 4;
|
|
||||||
final static int DIGEST_OFFSET = 8;
|
|
||||||
final static int HEADER_SIZE = 14;
|
|
||||||
|
|
||||||
final static int RELAY_BEGIN = 1;
|
|
||||||
final static int RELAY_DATA = 2;
|
|
||||||
final static int RELAY_END = 3;
|
|
||||||
final static int RELAY_CONNECTED = 4;
|
|
||||||
final static int RELAY_SENDME = 5;
|
|
||||||
final static int RELAY_EXTEND = 6;
|
|
||||||
final static int RELAY_EXTENDED = 7;
|
|
||||||
final static int RELAY_TRUNCATE = 8;
|
|
||||||
final static int RELAY_TRUNCATED = 9;
|
|
||||||
final static int RELAY_DROP = 10;
|
|
||||||
final static int RELAY_RESOLVE = 11;
|
|
||||||
final static int RELAY_RESOLVED = 12;
|
|
||||||
final static int RELAY_BEGIN_DIR = 13;
|
|
||||||
final static int RELAY_EXTEND2 = 14;
|
|
||||||
final static int RELAY_EXTENDED2 = 15;
|
|
||||||
|
|
||||||
final static int RELAY_COMMAND_ESTABLISH_INTRO = 32;
|
|
||||||
final static int RELAY_COMMAND_ESTABLISH_RENDEZVOUS = 33;
|
|
||||||
final static int RELAY_COMMAND_INTRODUCE1 = 34;
|
|
||||||
final static int RELAY_COMMAND_INTRODUCE2 = 35;
|
|
||||||
final static int RELAY_COMMAND_RENDEZVOUS1 = 36;
|
|
||||||
final static int RELAY_COMMAND_RENDEZVOUS2 = 37;
|
|
||||||
final static int RELAY_COMMAND_INTRO_ESTABLISHED = 38;
|
|
||||||
final static int RELAY_COMMAND_RENDEZVOUS_ESTABLISHED = 39;
|
|
||||||
final static int RELAY_COMMAND_INTRODUCE_ACK = 40;
|
|
||||||
|
|
||||||
final static int REASON_MISC = 1;
|
|
||||||
final static int REASON_RESOLVEFAILED = 2;
|
|
||||||
final static int REASON_CONNECTREFUSED = 3;
|
|
||||||
final static int REASON_EXITPOLICY = 4;
|
|
||||||
final static int REASON_DESTROY = 5;
|
|
||||||
final static int REASON_DONE = 6;
|
|
||||||
final static int REASON_TIMEOUT = 7;
|
|
||||||
final static int REASON_NOROUTE = 8;
|
|
||||||
final static int REASON_HIBERNATING = 9;
|
|
||||||
final static int REASON_INTERNAL = 10;
|
|
||||||
final static int REASON_RESOURCELIMIT = 11;
|
|
||||||
final static int REASON_CONNRESET = 12;
|
|
||||||
final static int REASON_TORPROTOCOL = 13;
|
|
||||||
final static int REASON_NOTDIRECTORY = 14;
|
|
||||||
|
|
||||||
int getStreamId();
|
|
||||||
int getRelayCommand();
|
|
||||||
/**
|
|
||||||
* Return the circuit node this cell was received from for outgoing cells or the destination circuit node
|
|
||||||
* for outgoing cells.
|
|
||||||
*/
|
|
||||||
CircuitNode getCircuitNode();
|
|
||||||
ByteBuffer getPayloadBuffer();
|
|
||||||
void setLength();
|
|
||||||
void setDigest(byte[] digest);
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
public class Revision {
|
|
||||||
private final static String REVISION_FILE_PATH = "/build-revision";
|
|
||||||
|
|
||||||
public static String getBuildRevision() {
|
|
||||||
final InputStream input = tryResourceOpen();
|
|
||||||
if(input == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return readFirstLine(input);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InputStream tryResourceOpen() {
|
|
||||||
return Revision.class.getResourceAsStream(REVISION_FILE_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String readFirstLine(InputStream input) throws IOException {
|
|
||||||
try {
|
|
||||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
|
|
||||||
return reader.readLine();
|
|
||||||
} finally {
|
|
||||||
input.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
|
|
||||||
public interface Router {
|
|
||||||
|
|
||||||
String getNickname();
|
|
||||||
String getCountryCode();
|
|
||||||
IPv4Address getAddress();
|
|
||||||
int getOnionPort();
|
|
||||||
int getDirectoryPort();
|
|
||||||
TorPublicKey getIdentityKey();
|
|
||||||
HexDigest getIdentityHash();
|
|
||||||
boolean isDescriptorDownloadable();
|
|
||||||
|
|
||||||
String getVersion();
|
|
||||||
Descriptor getCurrentDescriptor();
|
|
||||||
HexDigest getDescriptorDigest();
|
|
||||||
HexDigest getMicrodescriptorDigest();
|
|
||||||
|
|
||||||
TorPublicKey getOnionKey();
|
|
||||||
byte[] getNTorOnionKey();
|
|
||||||
|
|
||||||
boolean hasBandwidth();
|
|
||||||
int getEstimatedBandwidth();
|
|
||||||
int getMeasuredBandwidth();
|
|
||||||
|
|
||||||
Set<String> getFamilyMembers();
|
|
||||||
int getAverageBandwidth();
|
|
||||||
int getBurstBandwidth();
|
|
||||||
int getObservedBandwidth();
|
|
||||||
boolean isHibernating();
|
|
||||||
boolean isRunning();
|
|
||||||
boolean isValid();
|
|
||||||
boolean isBadExit();
|
|
||||||
boolean isPossibleGuard();
|
|
||||||
boolean isExit();
|
|
||||||
boolean isFast();
|
|
||||||
boolean isStable();
|
|
||||||
boolean isHSDirectory();
|
|
||||||
boolean exitPolicyAccepts(IPv4Address address, int port);
|
|
||||||
boolean exitPolicyAccepts(int port);
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.Timestamp;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitPolicy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directory information about a single onion router. This interface
|
|
||||||
* provides access to the fields of a router descriptor document which
|
|
||||||
* has been published through to Tor directory system.
|
|
||||||
*/
|
|
||||||
public interface RouterDescriptor extends Descriptor {
|
|
||||||
/**
|
|
||||||
* Returns the nickname of this router.
|
|
||||||
*
|
|
||||||
* @return The nickname of this router.
|
|
||||||
*/
|
|
||||||
String getNickname();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the port on which this router provides directory related
|
|
||||||
* HTTP connections, or 0 if this node does not provide directory
|
|
||||||
* services.
|
|
||||||
*
|
|
||||||
* @return The directory service port, or 0 if not a directory server.
|
|
||||||
*/
|
|
||||||
int getDirectoryPort();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the volume of traffic in bytes per second that this router
|
|
||||||
* is willing to sustain over long periods.
|
|
||||||
*
|
|
||||||
* @return The average bandwidth of this router in bytes per second.
|
|
||||||
*/
|
|
||||||
int getAverageBandwidth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the volume of traffic in bytes per second that this router
|
|
||||||
* is willing to sustain in very short intervals.
|
|
||||||
*
|
|
||||||
* @return The burst bandwidth of this router in bytes per second.
|
|
||||||
*/
|
|
||||||
int getBurstBandwidth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the volume of traffic in bytes per second that this router
|
|
||||||
* is estimated to be able to sustain.
|
|
||||||
*
|
|
||||||
* @return The observed bandwidth capacity of this router in bytes per second.
|
|
||||||
*/
|
|
||||||
int getObservedBandwidth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a human-readable string describing the system on which this router
|
|
||||||
* is running, including possibly the operating system version and Tor
|
|
||||||
* implementation version.
|
|
||||||
*
|
|
||||||
* @return A string describing the platform this router is running on.
|
|
||||||
*/
|
|
||||||
String getPlatform();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the time this descriptor was generated.
|
|
||||||
*
|
|
||||||
* @return The time this descriptor was generated.
|
|
||||||
*/
|
|
||||||
Timestamp getPublishedTime();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a fingerprint of the public key of this router. The fingerprint
|
|
||||||
* is an optional field, so this method may return null if the descriptor
|
|
||||||
* of the router did not include the 'fingerprint' field.
|
|
||||||
*
|
|
||||||
* @return The fingerprint of this router, or null if no fingerprint is available.
|
|
||||||
*/
|
|
||||||
HexDigest getFingerprint();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of seconds this router has been running.
|
|
||||||
*
|
|
||||||
* @return The number of seconds this router has been running.
|
|
||||||
*/
|
|
||||||
int getUptime();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the long-term identity and signing public key for this
|
|
||||||
* router.
|
|
||||||
*
|
|
||||||
* @return The long-term identity and signing public key for this router.
|
|
||||||
*/
|
|
||||||
TorPublicKey getIdentityKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a string which describes how to contact the server's administrator.
|
|
||||||
* This is an optional field, so this method will return null if the descriptor
|
|
||||||
* of this router did not include the 'contact' field.
|
|
||||||
*
|
|
||||||
* @return The contact information for this router, or null if not available.
|
|
||||||
*/
|
|
||||||
String getContact();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if this router is currently hibernating and not suitable for
|
|
||||||
* building new circuits.
|
|
||||||
*
|
|
||||||
* @return True if this router is currently hibernating.
|
|
||||||
*/
|
|
||||||
boolean isHibernating();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this router stores and serves hidden service descriptors.
|
|
||||||
*
|
|
||||||
* @return True if this router is a hidden service directory.
|
|
||||||
*/
|
|
||||||
boolean isHiddenServiceDirectory();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if this router is running a version of Tor which supports the
|
|
||||||
* newer enhanced DNS logic. If false, this router should be used for reverse
|
|
||||||
* hostname lookups.
|
|
||||||
*
|
|
||||||
* @return True if this router supports newer enhanced DNS logic.
|
|
||||||
*/
|
|
||||||
boolean supportsEventDNS();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this router is a directory cache that provides extra-info
|
|
||||||
* documents.
|
|
||||||
*
|
|
||||||
* @return True if this router provides an extra-info document directory service.
|
|
||||||
*/
|
|
||||||
boolean cachesExtraInfo();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a digest of this router's extra-info document, or null if not
|
|
||||||
* available. This is an optional field and will only be present if the
|
|
||||||
* 'extra-info-digest' field was present in the original router descriptor.
|
|
||||||
*
|
|
||||||
* @return The digest of the router extra-info-document, or null if not available.
|
|
||||||
*/
|
|
||||||
HexDigest getExtraInfoDigest();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if this router allows single-hop circuits to make exit connections.
|
|
||||||
*
|
|
||||||
* @return True if this router allows single-hop circuits to make exit connections.
|
|
||||||
*/
|
|
||||||
boolean allowsSingleHopExits();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two router descriptors and return true if this router descriptor was published
|
|
||||||
* at a later time than the <code>other</code> descriptor.
|
|
||||||
*
|
|
||||||
* @param other Another router descriptor to compare.
|
|
||||||
* @return True if this descriptor was published later than <code>other</code>
|
|
||||||
*/
|
|
||||||
boolean isNewerThan(RouterDescriptor other);
|
|
||||||
|
|
||||||
ExitPolicy getExitPolicy();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
public interface RouterMicrodescriptor extends Descriptor {
|
|
||||||
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.data.Timestamp;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitPorts;
|
|
||||||
|
|
||||||
public interface RouterStatus {
|
|
||||||
String getNickname();
|
|
||||||
HexDigest getIdentity();
|
|
||||||
HexDigest getDescriptorDigest();
|
|
||||||
HexDigest getMicrodescriptorDigest();
|
|
||||||
Timestamp getPublicationTime();
|
|
||||||
IPv4Address getAddress();
|
|
||||||
int getRouterPort();
|
|
||||||
boolean isDirectory();
|
|
||||||
int getDirectoryPort();
|
|
||||||
boolean hasFlag(String flag);
|
|
||||||
String getVersion();
|
|
||||||
boolean hasBandwidth();
|
|
||||||
int getEstimatedBandwidth();
|
|
||||||
int getMeasuredBandwidth();
|
|
||||||
ExitPorts getExitPorts();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public interface SocksPortListener {
|
|
||||||
void addListeningPort(int port);
|
|
||||||
void stop();
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public interface Stream {
|
|
||||||
/**
|
|
||||||
* Returns the {@link Circuit} this stream belongs to.
|
|
||||||
*
|
|
||||||
* @return The {@link Circuit} this stream belongs to.
|
|
||||||
*/
|
|
||||||
Circuit getCircuit();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the stream id value of this stream.
|
|
||||||
*
|
|
||||||
* @return The stream id value of this stream.
|
|
||||||
*/
|
|
||||||
int getStreamId();
|
|
||||||
|
|
||||||
|
|
||||||
CircuitNode getTargetNode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close this stream.
|
|
||||||
*/
|
|
||||||
void close();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an {@link InputStream} for sending data on this stream.
|
|
||||||
*
|
|
||||||
* @return An {@link InputStream} for transferring data on this stream.
|
|
||||||
*/
|
|
||||||
InputStream getInputStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an {@link OutputStream} for receiving data from this stream.
|
|
||||||
*
|
|
||||||
* @return An {@link OutputStream} for receiving data from this stream.
|
|
||||||
*/
|
|
||||||
OutputStream getOutputStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the circuit and stream level packaging windows are open for this stream
|
|
||||||
* this method returns immediately, otherwise it blocks until both windows are
|
|
||||||
* open or the stream is closed.
|
|
||||||
*/
|
|
||||||
void waitForSendWindow();
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
public class StreamConnectFailedException extends Exception {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 8103571310659595097L;
|
|
||||||
private final int reason;
|
|
||||||
|
|
||||||
public StreamConnectFailedException(int reason) {
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReason() {
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isReasonRetryable() {
|
|
||||||
return isRetryableReason(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copied from edge_reason_is_retriable() since this is not specified */
|
|
||||||
private static boolean isRetryableReason(int reasonCode) {
|
|
||||||
switch(reasonCode) {
|
|
||||||
case RelayCell.REASON_HIBERNATING:
|
|
||||||
case RelayCell.REASON_RESOURCELIMIT:
|
|
||||||
case RelayCell.REASON_RESOLVEFAILED:
|
|
||||||
case RelayCell.REASON_EXITPOLICY:
|
|
||||||
case RelayCell.REASON_MISC:
|
|
||||||
case RelayCell.REASON_NOROUTE:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.CycleDetectingLockFactory;
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by android on 8/22/14.
|
|
||||||
*/
|
|
||||||
public class Threading {
|
|
||||||
static {
|
|
||||||
// Default policy goes here. If you want to change this, use one of the static methods before
|
|
||||||
// instantiating any orchid objects. The policy change will take effect only on new objects
|
|
||||||
// from that point onwards.
|
|
||||||
throwOnLockCycles();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CycleDetectingLockFactory.Policy policy;
|
|
||||||
public static CycleDetectingLockFactory factory;
|
|
||||||
|
|
||||||
public static ReentrantLock lock(String name) {
|
|
||||||
return factory.newReentrantLock(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void warnOnLockCycles() {
|
|
||||||
setPolicy(CycleDetectingLockFactory.Policies.WARN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void throwOnLockCycles() {
|
|
||||||
setPolicy(CycleDetectingLockFactory.Policies.THROW);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ignoreLockCycles() {
|
|
||||||
setPolicy(CycleDetectingLockFactory.Policies.DISABLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setPolicy(CycleDetectingLockFactory.Policy policy) {
|
|
||||||
Threading.policy = policy;
|
|
||||||
factory = CycleDetectingLockFactory.newInstance(policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CycleDetectingLockFactory.Policy getPolicy() {
|
|
||||||
return policy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExecutorService newPool(final String name) {
|
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder()
|
|
||||||
.setDaemon(true)
|
|
||||||
.setNameFormat(name + "-%d").build();
|
|
||||||
return Executors.newCachedThreadPool(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ScheduledExecutorService newSingleThreadScheduledPool(final String name) {
|
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder()
|
|
||||||
.setDaemon(true)
|
|
||||||
.setNameFormat(name + "-%d").build();
|
|
||||||
return Executors.newSingleThreadScheduledExecutor(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ScheduledExecutorService newScheduledPool(final String name) {
|
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder()
|
|
||||||
.setDaemon(true)
|
|
||||||
.setNameFormat(name + "-%d").build();
|
|
||||||
return Executors.newScheduledThreadPool(1, factory);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.circuits.CircuitManagerImpl;
|
|
||||||
import com.subgraph.orchid.circuits.TorInitializationTracker;
|
|
||||||
import com.subgraph.orchid.config.TorConfigProxy;
|
|
||||||
import com.subgraph.orchid.connections.ConnectionCacheImpl;
|
|
||||||
import com.subgraph.orchid.directory.DirectoryImpl;
|
|
||||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
|
||||||
import com.subgraph.orchid.socks.SocksPortListenerImpl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The <code>Tor</code> class is a collection of static methods for instantiating
|
|
||||||
* various subsystem modules.
|
|
||||||
*/
|
|
||||||
public class Tor {
|
|
||||||
private final static Logger logger = Logger.getLogger(Tor.class.getName());
|
|
||||||
|
|
||||||
public final static int BOOTSTRAP_STATUS_STARTING = 0;
|
|
||||||
public final static int BOOTSTRAP_STATUS_CONN_DIR = 5;
|
|
||||||
public final static int BOOTSTRAP_STATUS_HANDSHAKE_DIR = 10;
|
|
||||||
public final static int BOOTSTRAP_STATUS_ONEHOP_CREATE = 15;
|
|
||||||
public final static int BOOTSTRAP_STATUS_REQUESTING_STATUS = 20;
|
|
||||||
public final static int BOOTSTRAP_STATUS_LOADING_STATUS = 25;
|
|
||||||
public final static int BOOTSTRAP_STATUS_REQUESTING_KEYS = 35;
|
|
||||||
public final static int BOOTSTRAP_STATUS_LOADING_KEYS = 40;
|
|
||||||
public final static int BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS = 45;
|
|
||||||
public final static int BOOTSTRAP_STATUS_LOADING_DESCRIPTORS = 50;
|
|
||||||
public final static int BOOTSTRAP_STATUS_CONN_OR = 80;
|
|
||||||
public final static int BOOTSTRAP_STATUS_HANDSHAKE_OR = 85;
|
|
||||||
public final static int BOOTSTRAP_STATUS_CIRCUIT_CREATE = 90;
|
|
||||||
public final static int BOOTSTRAP_STATUS_DONE = 100;
|
|
||||||
|
|
||||||
|
|
||||||
private final static String implementation = "Orchid";
|
|
||||||
private final static String version = "1.0.0";
|
|
||||||
|
|
||||||
private final static Charset defaultCharset = createDefaultCharset();
|
|
||||||
|
|
||||||
private static Charset createDefaultCharset() {
|
|
||||||
return Charset.forName("ISO-8859-1");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Charset getDefaultCharset() {
|
|
||||||
return defaultCharset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBuildRevision() {
|
|
||||||
return Revision.getBuildRevision();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getImplementation() {
|
|
||||||
return implementation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFullVersion() {
|
|
||||||
final String revision = getBuildRevision();
|
|
||||||
if(revision == null || revision.isEmpty()) {
|
|
||||||
return getVersion();
|
|
||||||
} else {
|
|
||||||
return getVersion() + "." + revision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a string describing the version of this software.
|
|
||||||
*
|
|
||||||
* @return A string representation of the software version.
|
|
||||||
*/
|
|
||||||
public static String getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if running on Android by inspecting java.runtime.name property.
|
|
||||||
*
|
|
||||||
* @return True if running on Android.
|
|
||||||
*/
|
|
||||||
public static boolean isAndroidRuntime() {
|
|
||||||
final String runtime = System.getProperty("java.runtime.name");
|
|
||||||
return runtime != null && runtime.equals("Android Runtime");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and return a new <code>TorConfig</code> instance.
|
|
||||||
*
|
|
||||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
|
||||||
* before calling this method to create a <code>TorConfig</code>
|
|
||||||
* @return A new <code>TorConfig</code> instance.
|
|
||||||
* @see TorConfig
|
|
||||||
*/
|
|
||||||
static public TorConfig createConfig() {
|
|
||||||
final TorConfig config = (TorConfig) Proxy.newProxyInstance(TorConfigProxy.class.getClassLoader(), new Class[] { TorConfig.class }, new TorConfigProxy());
|
|
||||||
if(isAndroidRuntime()) {
|
|
||||||
logger.warning("Android Runtime detected, disabling V2 Link protocol");
|
|
||||||
config.setHandshakeV2Enabled(false);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public TorInitializationTracker createInitalizationTracker() {
|
|
||||||
return new TorInitializationTracker();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and return a new <code>Directory</code> instance.
|
|
||||||
*
|
|
||||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
|
||||||
* before creating a <code>Directory</code>.
|
|
||||||
* @param config This is a required dependency. You must create a <code>TorConfig</code> before
|
|
||||||
* calling this method to create a <code>Directory</code>
|
|
||||||
* @return A new <code>Directory</code> instance.
|
|
||||||
* @see Directory
|
|
||||||
*/
|
|
||||||
static public Directory createDirectory(TorConfig config, DirectoryStore customDirectoryStore) {
|
|
||||||
return new DirectoryImpl(config, customDirectoryStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public ConnectionCache createConnectionCache(TorConfig config, TorInitializationTracker tracker) {
|
|
||||||
return new ConnectionCacheImpl(config, tracker);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create and return a new <code>CircuitManager</code> instance.
|
|
||||||
*
|
|
||||||
* @return A new <code>CircuitManager</code> instance.
|
|
||||||
* @see CircuitManager
|
|
||||||
*/
|
|
||||||
static public CircuitManager createCircuitManager(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker tracker) {
|
|
||||||
return new CircuitManagerImpl(config, directoryDownloader, directory, connectionCache, tracker);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and return a new <code>SocksPortListener</code> instance.
|
|
||||||
*
|
|
||||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
|
||||||
* before calling this method to create a <code>SocksPortListener</code>.
|
|
||||||
* @param circuitManager This is a required dependency. You must create a <code>CircuitManager</code>
|
|
||||||
* before calling this method to create a <code>SocksPortListener</code>.
|
|
||||||
* @return A new <code>SocksPortListener</code> instance.
|
|
||||||
* @see SocksPortListener
|
|
||||||
*/
|
|
||||||
static public SocksPortListener createSocksPortListener(TorConfig config, CircuitManager circuitManager) {
|
|
||||||
return new SocksPortListenerImpl(config, circuitManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and return a new <code>DirectoryDownloader</code> instance.
|
|
||||||
*
|
|
||||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
|
||||||
* before calling this method to create a <code>DirectoryDownloader</code>.
|
|
||||||
|
|
||||||
* @param directory This is a required dependency. You must create a <code>Directory</code>
|
|
||||||
* before calling this method to create a <code>DirectoryDownloader</code>
|
|
||||||
*
|
|
||||||
* @param circuitManager This is a required dependency. You must create a <code>CircuitManager</code>
|
|
||||||
* before calling this method to create a <code>DirectoryDownloader</code>.
|
|
||||||
*
|
|
||||||
* @return A new <code>DirectoryDownloader</code> instance.
|
|
||||||
* @see DirectoryDownloaderImpl
|
|
||||||
*/
|
|
||||||
static public DirectoryDownloaderImpl createDirectoryDownloader(TorConfig config, TorInitializationTracker initializationTracker) {
|
|
||||||
return new DirectoryDownloaderImpl(config, initializationTracker);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.net.SocketFactory;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.circuits.TorInitializationTracker;
|
|
||||||
import com.subgraph.orchid.crypto.PRNGFixes;
|
|
||||||
import com.subgraph.orchid.dashboard.Dashboard;
|
|
||||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
|
||||||
import com.subgraph.orchid.sockets.OrchidSocketFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is the main entry-point for running a Tor proxy
|
|
||||||
* or client.
|
|
||||||
*/
|
|
||||||
public class TorClient {
|
|
||||||
private final static Logger logger = Logger.getLogger(TorClient.class.getName());
|
|
||||||
private final TorConfig config;
|
|
||||||
private final Directory directory;
|
|
||||||
private final TorInitializationTracker initializationTracker;
|
|
||||||
private final ConnectionCache connectionCache;
|
|
||||||
private final CircuitManager circuitManager;
|
|
||||||
private final SocksPortListener socksListener;
|
|
||||||
private final DirectoryDownloaderImpl directoryDownloader;
|
|
||||||
private final Dashboard dashboard;
|
|
||||||
|
|
||||||
private boolean isStarted = false;
|
|
||||||
private boolean isStopped = false;
|
|
||||||
|
|
||||||
private final CountDownLatch readyLatch;
|
|
||||||
|
|
||||||
public TorClient() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorClient(DirectoryStore customDirectoryStore) {
|
|
||||||
if(Tor.isAndroidRuntime()) {
|
|
||||||
PRNGFixes.apply();
|
|
||||||
}
|
|
||||||
config = Tor.createConfig();
|
|
||||||
directory = Tor.createDirectory(config, customDirectoryStore);
|
|
||||||
initializationTracker = Tor.createInitalizationTracker();
|
|
||||||
initializationTracker.addListener(createReadyFlagInitializationListener());
|
|
||||||
connectionCache = Tor.createConnectionCache(config, initializationTracker);
|
|
||||||
directoryDownloader = Tor.createDirectoryDownloader(config, initializationTracker);
|
|
||||||
circuitManager = Tor.createCircuitManager(config, directoryDownloader, directory, connectionCache, initializationTracker);
|
|
||||||
socksListener = Tor.createSocksPortListener(config, circuitManager);
|
|
||||||
readyLatch = new CountDownLatch(1);
|
|
||||||
dashboard = new Dashboard();
|
|
||||||
dashboard.addRenderables(circuitManager, directoryDownloader, socksListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorConfig getConfig() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketFactory getSocketFactory() {
|
|
||||||
return new OrchidSocketFactory(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start running the Tor client service.
|
|
||||||
*/
|
|
||||||
public synchronized void start() {
|
|
||||||
if(isStarted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(isStopped) {
|
|
||||||
throw new IllegalStateException("Cannot restart a TorClient instance. Create a new instance instead.");
|
|
||||||
}
|
|
||||||
logger.info("Starting Orchid (version: "+ Tor.getFullVersion() +")");
|
|
||||||
verifyUnlimitedStrengthPolicyInstalled();
|
|
||||||
directoryDownloader.start(directory);
|
|
||||||
circuitManager.startBuildingCircuits();
|
|
||||||
if(dashboard.isEnabledByProperty()) {
|
|
||||||
dashboard.startListening();
|
|
||||||
}
|
|
||||||
isStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void stop() {
|
|
||||||
if(!isStarted || isStopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
socksListener.stop();
|
|
||||||
if(dashboard.isListening()) {
|
|
||||||
dashboard.stopListening();
|
|
||||||
}
|
|
||||||
directoryDownloader.stop();
|
|
||||||
circuitManager.stopBuildingCircuits(true);
|
|
||||||
directory.close();
|
|
||||||
connectionCache.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, "Unexpected exception while shutting down TorClient instance: "+ e, e);
|
|
||||||
} finally {
|
|
||||||
isStopped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Directory getDirectory() {
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionCache getConnectionCache() {
|
|
||||||
return connectionCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitManager getCircuitManager() {
|
|
||||||
return circuitManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitUntilReady() throws InterruptedException {
|
|
||||||
readyLatch.await();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitUntilReady(long timeout) throws InterruptedException, TimeoutException {
|
|
||||||
if(!readyLatch.await(timeout, TimeUnit.MILLISECONDS)) {
|
|
||||||
throw new TimeoutException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openExitStreamTo(String hostname, int port) throws InterruptedException, TimeoutException, OpenFailedException {
|
|
||||||
ensureStarted();
|
|
||||||
return circuitManager.openExitStreamTo(hostname, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void ensureStarted() {
|
|
||||||
if(!isStarted) {
|
|
||||||
throw new IllegalStateException("Must call start() first");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableSocksListener(int port) {
|
|
||||||
socksListener.addListeningPort(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableSocksListener() {
|
|
||||||
enableSocksListener(9150);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableDashboard() {
|
|
||||||
if(!dashboard.isListening()) {
|
|
||||||
dashboard.startListening();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableDashboard(int port) {
|
|
||||||
dashboard.setListeningPort(port);
|
|
||||||
enableDashboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disableDashboard() {
|
|
||||||
if(dashboard.isListening()) {
|
|
||||||
dashboard.stopListening();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addInitializationListener(TorInitializationListener listener) {
|
|
||||||
initializationTracker.addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeInitializationListener(TorInitializationListener listener) {
|
|
||||||
initializationTracker.removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TorInitializationListener createReadyFlagInitializationListener() {
|
|
||||||
return new TorInitializationListener() {
|
|
||||||
public void initializationProgress(String message, int percent) {}
|
|
||||||
public void initializationCompleted() {
|
|
||||||
readyLatch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
final TorClient client = new TorClient();
|
|
||||||
client.addInitializationListener(createInitalizationListner());
|
|
||||||
client.start();
|
|
||||||
client.enableSocksListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TorInitializationListener createInitalizationListner() {
|
|
||||||
return new TorInitializationListener() {
|
|
||||||
|
|
||||||
public void initializationProgress(String message, int percent) {
|
|
||||||
System.out.println(">>> [ "+ percent + "% ]: "+ message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initializationCompleted() {
|
|
||||||
System.out.println("Tor is ready to go!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyUnlimitedStrengthPolicyInstalled() {
|
|
||||||
try {
|
|
||||||
if(Cipher.getMaxAllowedKeyLength("AES") < 256) {
|
|
||||||
final String message = "Unlimited Strength Jurisdiction Policy Files are required but not installed.";
|
|
||||||
logger.severe(message);
|
|
||||||
throw new TorException(message);
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
logger.log(Level.SEVERE, "No AES provider found");
|
|
||||||
throw new TorException(e);
|
|
||||||
} catch (NoSuchMethodError e) {
|
|
||||||
logger.info("Skipped check for Unlimited Strength Jurisdiction Policy Files");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie;
|
|
||||||
import com.subgraph.orchid.config.TorConfigBridgeLine;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
|
|
||||||
|
|
||||||
public interface TorConfig {
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.PATH, defaultValue="~/.orchid")
|
|
||||||
File getDataDirectory();
|
|
||||||
void setDataDirectory(File directory);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="60 seconds")
|
|
||||||
long getCircuitBuildTimeout();
|
|
||||||
void setCircuitBuildTimeout(long time, TimeUnit unit);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="0")
|
|
||||||
long getCircuitStreamTimeout();
|
|
||||||
void setCircuitStreamTimeout(long time, TimeUnit unit);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="1 hour")
|
|
||||||
long getCircuitIdleTimeout();
|
|
||||||
void setCircuitIdleTimeout(long time, TimeUnit unit);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="30 seconds")
|
|
||||||
long getNewCircuitPeriod();
|
|
||||||
void setNewCircuitPeriod(long time, TimeUnit unit);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="10 minutes")
|
|
||||||
long getMaxCircuitDirtiness();
|
|
||||||
void setMaxCircuitDirtiness(long time, TimeUnit unit);
|
|
||||||
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTEGER, defaultValue="32")
|
|
||||||
int getMaxClientCircuitsPending();
|
|
||||||
void setMaxClientCircuitsPending(int value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getEnforceDistinctSubnets();
|
|
||||||
void setEnforceDistinctSubnets(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="2 minutes")
|
|
||||||
long getSocksTimeout();
|
|
||||||
void setSocksTimeout(long value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.INTEGER, defaultValue="3")
|
|
||||||
int getNumEntryGuards();
|
|
||||||
void setNumEntryGuards(int value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getUseEntryGuards();
|
|
||||||
void setUseEntryGuards(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.PORTLIST, defaultValue="21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300")
|
|
||||||
List<Integer> getLongLivedPorts();
|
|
||||||
void setLongLivedPorts(List<Integer> ports);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
|
||||||
List<String> getExcludeNodes();
|
|
||||||
void setExcludeNodes(List<String> nodes);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
|
||||||
List<String> getExcludeExitNodes();
|
|
||||||
|
|
||||||
void setExcludeExitNodes(List<String> nodes);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
|
||||||
List<String> getExitNodes();
|
|
||||||
void setExitNodes(List<String> nodes);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
|
||||||
List<String> getEntryNodes();
|
|
||||||
void setEntryNodes(List<String> nodes);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
|
||||||
boolean getStrictNodes();
|
|
||||||
void setStrictNodes(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
|
||||||
boolean getFascistFirewall();
|
|
||||||
void setFascistFirewall(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.PORTLIST, defaultValue="80,443")
|
|
||||||
List<Integer> getFirewallPorts();
|
|
||||||
void setFirewallPorts(List<Integer> ports);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
|
||||||
boolean getSafeSocks();
|
|
||||||
void setSafeSocks(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getSafeLogging();
|
|
||||||
void setSafeLogging(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getWarnUnsafeSocks();
|
|
||||||
void setWarnUnsafeSocks(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getClientRejectInternalAddress();
|
|
||||||
void setClientRejectInternalAddress(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getHandshakeV3Enabled();
|
|
||||||
void setHandshakeV3Enabled(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
|
||||||
boolean getHandshakeV2Enabled();
|
|
||||||
void setHandshakeV2Enabled(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.HS_AUTH)
|
|
||||||
HSDescriptorCookie getHidServAuth(String key);
|
|
||||||
void addHidServAuth(String key, String value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.AUTOBOOL, defaultValue="auto")
|
|
||||||
AutoBoolValue getUseNTorHandshake();
|
|
||||||
void setUseNTorHandshake(AutoBoolValue value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.AUTOBOOL, defaultValue="auto")
|
|
||||||
AutoBoolValue getUseMicrodescriptors();
|
|
||||||
void setUseMicrodescriptors(AutoBoolValue value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
|
||||||
boolean getUseBridges();
|
|
||||||
void setUseBridges(boolean value);
|
|
||||||
|
|
||||||
@ConfigVar(type=ConfigVarType.BRIDGE_LINE)
|
|
||||||
List<TorConfigBridgeLine> getBridges();
|
|
||||||
void addBridge(IPv4Address address, int port);
|
|
||||||
void addBridge(IPv4Address address, int port, HexDigest fingerprint);
|
|
||||||
|
|
||||||
enum ConfigVarType { INTEGER, STRING, HS_AUTH, BOOLEAN, INTERVAL, PORTLIST, STRINGLIST, PATH, AUTOBOOL, BRIDGE_LINE };
|
|
||||||
enum AutoBoolValue { TRUE, FALSE, AUTO }
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@interface ConfigVar {
|
|
||||||
ConfigVarType type();
|
|
||||||
String defaultValue() default "";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public class TorException extends RuntimeException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2462760291055303580L;
|
|
||||||
|
|
||||||
public TorException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorException(String message, Throwable ex) {
|
|
||||||
super(message, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorException(Throwable ex) {
|
|
||||||
super(ex);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
public interface TorInitializationListener {
|
|
||||||
void initializationProgress(String message, int percent);
|
|
||||||
void initializationCompleted();
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
|
|
||||||
public class TorParsingException extends TorException {
|
|
||||||
public TorParsingException(String string) {
|
|
||||||
super(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorParsingException(String string, Throwable ex) {
|
|
||||||
super(string, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4997757416476363399L;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package com.subgraph.orchid;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.directory.consensus.DirectorySignature;
|
|
||||||
|
|
||||||
public interface VoteAuthorityEntry {
|
|
||||||
String getNickname();
|
|
||||||
HexDigest getIdentity();
|
|
||||||
String getHostname();
|
|
||||||
IPv4Address getAddress();
|
|
||||||
int getDirectoryPort();
|
|
||||||
int getRouterPort();
|
|
||||||
String getContact();
|
|
||||||
HexDigest getVoteDigest();
|
|
||||||
List<DirectorySignature> getSignatures();
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.Connection;
|
|
||||||
import com.subgraph.orchid.ConnectionCache;
|
|
||||||
import com.subgraph.orchid.ConnectionFailedException;
|
|
||||||
import com.subgraph.orchid.ConnectionHandshakeException;
|
|
||||||
import com.subgraph.orchid.ConnectionTimeoutException;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Tor;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
|
||||||
|
|
||||||
public class CircuitBuildTask implements Runnable {
|
|
||||||
private final static Logger logger = Logger.getLogger(CircuitBuildTask.class.getName());
|
|
||||||
private final CircuitCreationRequest creationRequest;
|
|
||||||
private final ConnectionCache connectionCache;
|
|
||||||
private final TorInitializationTracker initializationTracker;
|
|
||||||
private final CircuitImpl circuit;
|
|
||||||
private final CircuitExtender extender;
|
|
||||||
|
|
||||||
private Connection connection = null;
|
|
||||||
|
|
||||||
public CircuitBuildTask(CircuitCreationRequest request, ConnectionCache connectionCache, boolean ntorEnabled) {
|
|
||||||
this(request, connectionCache, ntorEnabled, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitBuildTask(CircuitCreationRequest request, ConnectionCache connectionCache, boolean ntorEnabled, TorInitializationTracker initializationTracker) {
|
|
||||||
this.creationRequest = request;
|
|
||||||
this.connectionCache = connectionCache;
|
|
||||||
this.initializationTracker = initializationTracker;
|
|
||||||
this.circuit = request.getCircuit();
|
|
||||||
this.extender = new CircuitExtender(request.getCircuit(), ntorEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
Router firstRouter = null;
|
|
||||||
try {
|
|
||||||
circuit.notifyCircuitBuildStart();
|
|
||||||
creationRequest.choosePath();
|
|
||||||
if(logger.isLoggable(Level.FINE)) {
|
|
||||||
logger.fine("Opening a new circuit to "+ pathToString(creationRequest));
|
|
||||||
}
|
|
||||||
firstRouter = creationRequest.getPathElement(0);
|
|
||||||
openEntryNodeConnection(firstRouter);
|
|
||||||
buildCircuit(firstRouter);
|
|
||||||
circuit.notifyCircuitBuildCompleted();
|
|
||||||
} catch (ConnectionTimeoutException e) {
|
|
||||||
connectionFailed("Timeout connecting to "+ firstRouter);
|
|
||||||
} catch (ConnectionFailedException e) {
|
|
||||||
connectionFailed("Connection failed to "+ firstRouter + " : " + e.getMessage());
|
|
||||||
} catch (ConnectionHandshakeException e) {
|
|
||||||
connectionFailed("Handshake error connecting to "+ firstRouter + " : " + e.getMessage());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
circuitBuildFailed("Circuit building thread interrupted");
|
|
||||||
} catch(PathSelectionFailedException e) {
|
|
||||||
circuitBuildFailed(e.getMessage());
|
|
||||||
} catch (TorException e) {
|
|
||||||
circuitBuildFailed(e.getMessage());
|
|
||||||
} catch(Exception e) {
|
|
||||||
circuitBuildFailed("Unexpected exception: "+ e);
|
|
||||||
logger.log(Level.WARNING, "Unexpected exception while building circuit: "+ e, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String pathToString(CircuitCreationRequest ccr) {
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("[");
|
|
||||||
for(Router r: ccr.getPath()) {
|
|
||||||
if(sb.length() > 1)
|
|
||||||
sb.append(",");
|
|
||||||
sb.append(r.getNickname());
|
|
||||||
}
|
|
||||||
sb.append("]");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectionFailed(String message) {
|
|
||||||
creationRequest.connectionFailed(message);
|
|
||||||
circuit.notifyCircuitBuildFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void circuitBuildFailed(String message) {
|
|
||||||
creationRequest.circuitBuildFailed(message);
|
|
||||||
circuit.notifyCircuitBuildFailed();
|
|
||||||
if(connection != null) {
|
|
||||||
connection.removeCircuit(circuit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openEntryNodeConnection(Router firstRouter) throws ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException, InterruptedException {
|
|
||||||
connection = connectionCache.getConnectionTo(firstRouter, creationRequest.isDirectoryCircuit());
|
|
||||||
circuit.bindToConnection(connection);
|
|
||||||
creationRequest.connectionCompleted(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildCircuit(Router firstRouter) throws TorException {
|
|
||||||
notifyInitialization();
|
|
||||||
final CircuitNode firstNode = extender.createFastTo(firstRouter);
|
|
||||||
creationRequest.nodeAdded(firstNode);
|
|
||||||
|
|
||||||
for(int i = 1; i < creationRequest.getPathLength(); i++) {
|
|
||||||
final CircuitNode extendedNode = extender.extendTo(creationRequest.getPathElement(i));
|
|
||||||
creationRequest.nodeAdded(extendedNode);
|
|
||||||
}
|
|
||||||
creationRequest.circuitBuildCompleted(circuit);
|
|
||||||
notifyDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyInitialization() {
|
|
||||||
if(initializationTracker != null) {
|
|
||||||
final int event = creationRequest.isDirectoryCircuit() ?
|
|
||||||
Tor.BOOTSTRAP_STATUS_ONEHOP_CREATE : Tor.BOOTSTRAP_STATUS_CIRCUIT_CREATE;
|
|
||||||
initializationTracker.notifyEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyDone() {
|
|
||||||
if(initializationTracker != null && !creationRequest.isDirectoryCircuit()) {
|
|
||||||
initializationTracker.notifyEvent(Tor.BOOTSTRAP_STATUS_DONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Circuit;
|
|
||||||
import com.subgraph.orchid.CircuitBuildHandler;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.Connection;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
|
||||||
|
|
||||||
public class CircuitCreationRequest implements CircuitBuildHandler {
|
|
||||||
private final CircuitImpl circuit;
|
|
||||||
private final CircuitPathChooser pathChooser;
|
|
||||||
private final CircuitBuildHandler buildHandler;
|
|
||||||
private final boolean isDirectoryCircuit;
|
|
||||||
|
|
||||||
private List<Router> path;
|
|
||||||
|
|
||||||
public CircuitCreationRequest(CircuitPathChooser pathChooser, Circuit circuit, CircuitBuildHandler buildHandler, boolean isDirectoryCircuit) {
|
|
||||||
this.pathChooser = pathChooser;
|
|
||||||
this.circuit = (CircuitImpl) circuit;
|
|
||||||
this.buildHandler = buildHandler;
|
|
||||||
this.path = Collections.emptyList();
|
|
||||||
this.isDirectoryCircuit = isDirectoryCircuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void choosePath() throws InterruptedException, PathSelectionFailedException {
|
|
||||||
if(!(circuit instanceof CircuitImpl)) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
path = ((CircuitImpl)circuit).choosePath(pathChooser);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitImpl getCircuit() {
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Router> getPath() {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getPathLength() {
|
|
||||||
return path.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
Router getPathElement(int idx) {
|
|
||||||
return path.get(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitBuildHandler getBuildHandler() {
|
|
||||||
return buildHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isDirectoryCircuit() {
|
|
||||||
return isDirectoryCircuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionCompleted(Connection connection) {
|
|
||||||
if(buildHandler != null) {
|
|
||||||
buildHandler.connectionCompleted(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionFailed(String reason) {
|
|
||||||
if(buildHandler != null) {
|
|
||||||
buildHandler.connectionFailed(reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void nodeAdded(CircuitNode node) {
|
|
||||||
if(buildHandler != null) {
|
|
||||||
buildHandler.nodeAdded(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circuitBuildCompleted(Circuit circuit) {
|
|
||||||
if(buildHandler != null) {
|
|
||||||
buildHandler.circuitBuildCompleted(circuit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circuitBuildFailed(String reason) {
|
|
||||||
if(buildHandler != null) {
|
|
||||||
buildHandler.circuitBuildFailed(reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,297 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Circuit;
|
|
||||||
import com.subgraph.orchid.CircuitBuildHandler;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.Connection;
|
|
||||||
import com.subgraph.orchid.ConnectionCache;
|
|
||||||
import com.subgraph.orchid.Directory;
|
|
||||||
import com.subgraph.orchid.ExitCircuit;
|
|
||||||
import com.subgraph.orchid.InternalCircuit;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Threading;
|
|
||||||
import com.subgraph.orchid.TorConfig;
|
|
||||||
import com.subgraph.orchid.circuits.CircuitManagerImpl.CircuitFilter;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
|
||||||
|
|
||||||
public class CircuitCreationTask implements Runnable {
|
|
||||||
private final static Logger logger = Logger.getLogger(CircuitCreationTask.class.getName());
|
|
||||||
private final static int MAX_CIRCUIT_DIRTINESS = 300; // seconds
|
|
||||||
private final static int MAX_PENDING_CIRCUITS = 4;
|
|
||||||
|
|
||||||
private final TorConfig config;
|
|
||||||
private final Directory directory;
|
|
||||||
private final ConnectionCache connectionCache;
|
|
||||||
private final CircuitManagerImpl circuitManager;
|
|
||||||
private final TorInitializationTracker initializationTracker;
|
|
||||||
private final CircuitPathChooser pathChooser;
|
|
||||||
private final Executor executor;
|
|
||||||
private final CircuitBuildHandler buildHandler;
|
|
||||||
private final CircuitBuildHandler internalBuildHandler;
|
|
||||||
// To avoid obnoxiously printing a warning every second
|
|
||||||
private int notEnoughDirectoryInformationWarningCounter = 0;
|
|
||||||
|
|
||||||
private final CircuitPredictor predictor;
|
|
||||||
|
|
||||||
private final AtomicLong lastNewCircuit;
|
|
||||||
|
|
||||||
CircuitCreationTask(TorConfig config, Directory directory, ConnectionCache connectionCache, CircuitPathChooser pathChooser, CircuitManagerImpl circuitManager, TorInitializationTracker initializationTracker) {
|
|
||||||
this.config = config;
|
|
||||||
this.directory = directory;
|
|
||||||
this.connectionCache = connectionCache;
|
|
||||||
this.circuitManager = circuitManager;
|
|
||||||
this.initializationTracker = initializationTracker;
|
|
||||||
this.pathChooser = pathChooser;
|
|
||||||
this.executor = Threading.newPool("CircuitCreationTask worker");
|
|
||||||
this.buildHandler = createCircuitBuildHandler();
|
|
||||||
this.internalBuildHandler = createInternalCircuitBuildHandler();
|
|
||||||
this.predictor = new CircuitPredictor();
|
|
||||||
this.lastNewCircuit = new AtomicLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitPredictor getCircuitPredictor() {
|
|
||||||
return predictor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
expireOldCircuits();
|
|
||||||
assignPendingStreamsToActiveCircuits();
|
|
||||||
checkExpiredPendingCircuits();
|
|
||||||
checkCircuitsForCreation();
|
|
||||||
}
|
|
||||||
|
|
||||||
void predictPort(int port) {
|
|
||||||
predictor.addExitPortRequest(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assignPendingStreamsToActiveCircuits() {
|
|
||||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
|
||||||
if(pendingExitStreams.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for(ExitCircuit c: circuitManager.getRandomlyOrderedListOfExitCircuits()) {
|
|
||||||
final Iterator<StreamExitRequest> it = pendingExitStreams.iterator();
|
|
||||||
while(it.hasNext()) {
|
|
||||||
if(attemptHandleStreamRequest(c, it.next()))
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean attemptHandleStreamRequest(ExitCircuit c, StreamExitRequest request) {
|
|
||||||
if(c.canHandleExitTo(request)) {
|
|
||||||
if(request.reserveRequest()) {
|
|
||||||
launchExitStreamTask(c, request);
|
|
||||||
}
|
|
||||||
// else request is reserved meaning another circuit is already trying to handle it
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void launchExitStreamTask(ExitCircuit circuit, StreamExitRequest exitRequest) {
|
|
||||||
final OpenExitStreamTask task = new OpenExitStreamTask(circuit, exitRequest);
|
|
||||||
executor.execute(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expireOldCircuits() {
|
|
||||||
final Set<Circuit> circuits = circuitManager.getCircuitsByFilter(new CircuitFilter() {
|
|
||||||
|
|
||||||
public boolean filter(Circuit circuit) {
|
|
||||||
return !circuit.isMarkedForClose() && circuit.getSecondsDirty() > MAX_CIRCUIT_DIRTINESS;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for(Circuit c: circuits) {
|
|
||||||
logger.fine("Closing idle dirty circuit: "+ c);
|
|
||||||
((CircuitImpl)c).markForClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void checkExpiredPendingCircuits() {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCircuitsForCreation() {
|
|
||||||
|
|
||||||
if(!directory.haveMinimumRouterInfo()) {
|
|
||||||
if(notEnoughDirectoryInformationWarningCounter % 20 == 0)
|
|
||||||
logger.info("Cannot build circuits because we don't have enough directory information");
|
|
||||||
notEnoughDirectoryInformationWarningCounter++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(lastNewCircuit.get() != 0) {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
if((now - lastNewCircuit.get()) < config.getNewCircuitPeriod()) {
|
|
||||||
// return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildCircuitIfNeeded();
|
|
||||||
maybeBuildInternalCircuit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildCircuitIfNeeded() {
|
|
||||||
if (connectionCache.isClosed()) {
|
|
||||||
logger.warning("Not building circuits, because connection cache is closed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
|
||||||
final List<PredictedPortTarget> predictedPorts = predictor.getPredictedPortTargets();
|
|
||||||
final List<ExitTarget> exitTargets = new ArrayList<ExitTarget>();
|
|
||||||
for(StreamExitRequest streamRequest: pendingExitStreams) {
|
|
||||||
if(!streamRequest.isReserved() && countCircuitsSupportingTarget(streamRequest, false) == 0) {
|
|
||||||
exitTargets.add(streamRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(PredictedPortTarget ppt: predictedPorts) {
|
|
||||||
if(countCircuitsSupportingTarget(ppt, true) < 2) {
|
|
||||||
exitTargets.add(ppt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildCircuitToHandleExitTargets(exitTargets);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeBuildInternalCircuit() {
|
|
||||||
final int needed = circuitManager.getNeededCleanCircuitCount(predictor.isInternalPredicted());
|
|
||||||
|
|
||||||
if(needed > 0) {
|
|
||||||
launchBuildTaskForInternalCircuit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void launchBuildTaskForInternalCircuit() {
|
|
||||||
logger.fine("Launching new internal circuit");
|
|
||||||
final InternalCircuitImpl circuit = new InternalCircuitImpl(circuitManager);
|
|
||||||
final CircuitCreationRequest request = new CircuitCreationRequest(pathChooser, circuit, internalBuildHandler, false);
|
|
||||||
final CircuitBuildTask task = new CircuitBuildTask(request, connectionCache, circuitManager.isNtorEnabled());
|
|
||||||
executor.execute(task);
|
|
||||||
circuitManager.incrementPendingInternalCircuitCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int countCircuitsSupportingTarget(final ExitTarget target, final boolean needClean) {
|
|
||||||
final CircuitFilter filter = new CircuitFilter() {
|
|
||||||
public boolean filter(Circuit circuit) {
|
|
||||||
if(!(circuit instanceof ExitCircuit)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final ExitCircuit ec = (ExitCircuit) circuit;
|
|
||||||
final boolean pendingOrConnected = circuit.isPending() || circuit.isConnected();
|
|
||||||
final boolean isCleanIfNeeded = !(needClean && !circuit.isClean());
|
|
||||||
return pendingOrConnected && isCleanIfNeeded && ec.canHandleExitTo(target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return circuitManager.getCircuitsByFilter(filter).size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildCircuitToHandleExitTargets(List<ExitTarget> exitTargets) {
|
|
||||||
if(exitTargets.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(!directory.haveMinimumRouterInfo())
|
|
||||||
return;
|
|
||||||
if(circuitManager.getPendingCircuitCount() >= MAX_PENDING_CIRCUITS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(logger.isLoggable(Level.FINE)) {
|
|
||||||
logger.fine("Building new circuit to handle "+ exitTargets.size() +" pending streams and predicted ports");
|
|
||||||
}
|
|
||||||
|
|
||||||
launchBuildTaskForTargets(exitTargets);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void launchBuildTaskForTargets(List<ExitTarget> exitTargets) {
|
|
||||||
final Router exitRouter = pathChooser.chooseExitNodeForTargets(exitTargets);
|
|
||||||
if(exitRouter == null) {
|
|
||||||
logger.warning("Failed to select suitable exit node for targets");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Circuit circuit = circuitManager.createNewExitCircuit(exitRouter);
|
|
||||||
final CircuitCreationRequest request = new CircuitCreationRequest(pathChooser, circuit, buildHandler, false);
|
|
||||||
final CircuitBuildTask task = new CircuitBuildTask(request, connectionCache, circuitManager.isNtorEnabled(), initializationTracker);
|
|
||||||
executor.execute(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitBuildHandler createCircuitBuildHandler() {
|
|
||||||
return new CircuitBuildHandler() {
|
|
||||||
|
|
||||||
public void circuitBuildCompleted(Circuit circuit) {
|
|
||||||
logger.fine("Circuit completed to: "+ circuit);
|
|
||||||
circuitOpenedHandler(circuit);
|
|
||||||
lastNewCircuit.set(System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circuitBuildFailed(String reason) {
|
|
||||||
logger.fine("Circuit build failed: "+ reason);
|
|
||||||
buildCircuitIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionCompleted(Connection connection) {
|
|
||||||
logger.finer("Circuit connection completed to "+ connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionFailed(String reason) {
|
|
||||||
logger.fine("Circuit connection failed: "+ reason);
|
|
||||||
buildCircuitIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void nodeAdded(CircuitNode node) {
|
|
||||||
logger.finer("Node added to circuit: "+ node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void circuitOpenedHandler(Circuit circuit) {
|
|
||||||
if(!(circuit instanceof ExitCircuit)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final ExitCircuit ec = (ExitCircuit) circuit;
|
|
||||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
|
||||||
for(StreamExitRequest req: pendingExitStreams) {
|
|
||||||
if(ec.canHandleExitTo(req) && req.reserveRequest()) {
|
|
||||||
launchExitStreamTask(ec, req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitBuildHandler createInternalCircuitBuildHandler() {
|
|
||||||
return new CircuitBuildHandler() {
|
|
||||||
|
|
||||||
public void nodeAdded(CircuitNode node) {
|
|
||||||
logger.finer("Node added to internal circuit: "+ node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionFailed(String reason) {
|
|
||||||
logger.fine("Circuit connection failed: "+ reason);
|
|
||||||
circuitManager.decrementPendingInternalCircuitCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionCompleted(Connection connection) {
|
|
||||||
logger.finer("Circuit connection completed to "+ connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circuitBuildFailed(String reason) {
|
|
||||||
logger.fine("Circuit build failed: "+ reason);
|
|
||||||
circuitManager.decrementPendingInternalCircuitCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circuitBuildCompleted(Circuit circuit) {
|
|
||||||
logger.fine("Internal circuit build completed: "+ circuit);
|
|
||||||
lastNewCircuit.set(System.currentTimeMillis());
|
|
||||||
circuitManager.addCleanInternalCircuit((InternalCircuit) circuit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
import com.subgraph.orchid.circuits.cells.CellImpl;
|
|
||||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
|
||||||
import com.subgraph.orchid.crypto.TorCreateFastKeyAgreement;
|
|
||||||
import com.subgraph.orchid.crypto.TorKeyAgreement;
|
|
||||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
|
||||||
import com.subgraph.orchid.crypto.TorStreamCipher;
|
|
||||||
|
|
||||||
public class CircuitExtender {
|
|
||||||
private final static Logger logger = Logger.getLogger(CircuitExtender.class.getName());
|
|
||||||
|
|
||||||
private final static int DH_BYTES = 1024 / 8;
|
|
||||||
private final static int PKCS1_OAEP_PADDING_OVERHEAD = 42;
|
|
||||||
private final static int CIPHER_KEY_LEN = TorStreamCipher.KEY_LEN;
|
|
||||||
final static int TAP_ONIONSKIN_LEN = PKCS1_OAEP_PADDING_OVERHEAD + CIPHER_KEY_LEN + DH_BYTES;
|
|
||||||
final static int TAP_ONIONSKIN_REPLY_LEN = DH_BYTES + TorMessageDigest.TOR_DIGEST_SIZE;
|
|
||||||
|
|
||||||
|
|
||||||
private final CircuitImpl circuit;
|
|
||||||
private final boolean ntorEnabled;
|
|
||||||
|
|
||||||
|
|
||||||
CircuitExtender(CircuitImpl circuit, boolean ntorEnabled) {
|
|
||||||
this.circuit = circuit;
|
|
||||||
this.ntorEnabled = ntorEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CircuitNode createFastTo(Router targetRouter) {
|
|
||||||
logger.fine("Creating 'fast' to "+ targetRouter);
|
|
||||||
final TorCreateFastKeyAgreement kex = new TorCreateFastKeyAgreement();
|
|
||||||
sendCreateFastCell(kex);
|
|
||||||
return receiveAndProcessCreateFastResponse(targetRouter, kex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendCreateFastCell(TorCreateFastKeyAgreement kex) {
|
|
||||||
final Cell cell = CellImpl.createCell(circuit.getCircuitId(), Cell.CREATE_FAST);
|
|
||||||
cell.putByteArray(kex.createOnionSkin());
|
|
||||||
circuit.sendCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode receiveAndProcessCreateFastResponse(Router targetRouter, TorKeyAgreement kex) {
|
|
||||||
final Cell cell = circuit.receiveControlCellResponse();
|
|
||||||
if(cell == null) {
|
|
||||||
throw new TorException("Timeout building circuit waiting for CREATE_FAST response from "+ targetRouter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processCreatedFastCell(targetRouter, cell, kex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode processCreatedFastCell(Router targetRouter, Cell cell, TorKeyAgreement kex) {
|
|
||||||
final byte[] payload = new byte[TorMessageDigest.TOR_DIGEST_SIZE * 2];
|
|
||||||
final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE];
|
|
||||||
final byte[] verifyHash = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
|
||||||
cell.getByteArray(payload);
|
|
||||||
if(!kex.deriveKeysFromHandshakeResponse(payload, keyMaterial, verifyHash)) {
|
|
||||||
// XXX
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final CircuitNode node = CircuitNodeImpl.createFirstHop(targetRouter, keyMaterial, verifyHash);
|
|
||||||
circuit.appendNode(node);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitNode extendTo(Router targetRouter) {
|
|
||||||
if(circuit.getCircuitLength() == 0) {
|
|
||||||
throw new TorException("Cannot EXTEND an empty circuit");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(useNtor(targetRouter)) {
|
|
||||||
final NTorCircuitExtender nce = new NTorCircuitExtender(this, targetRouter);
|
|
||||||
return nce.extendTo();
|
|
||||||
} else {
|
|
||||||
final TapCircuitExtender tce = new TapCircuitExtender(this, targetRouter);
|
|
||||||
return tce.extendTo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean useNtor(Router targetRouter) {
|
|
||||||
return ntorEnabled && targetRouter.getNTorOnionKey() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logProtocolViolation(String sourceName, Router targetRouter) {
|
|
||||||
final String version = (targetRouter == null) ? "(none)" : targetRouter.getVersion();
|
|
||||||
final String targetName = (targetRouter == null) ? "(none)" : targetRouter.getNickname();
|
|
||||||
logger.warning("Protocol error extending circuit from ("+ sourceName +") to ("+ targetName +") [version: "+ version +"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String nodeToName(CircuitNode node) {
|
|
||||||
if(node == null || node.getRouter() == null) {
|
|
||||||
return "(null)";
|
|
||||||
}
|
|
||||||
final Router router = node.getRouter();
|
|
||||||
return router.getNickname();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void sendRelayCell(RelayCell cell) {
|
|
||||||
circuit.sendRelayCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public RelayCell receiveRelayResponse(int expectedCommand, Router extendTarget) {
|
|
||||||
final RelayCell cell = circuit.receiveRelayCell();
|
|
||||||
if(cell == null) {
|
|
||||||
throw new TorException("Timeout building circuit");
|
|
||||||
}
|
|
||||||
final int command = cell.getRelayCommand();
|
|
||||||
if(command == RelayCell.RELAY_TRUNCATED) {
|
|
||||||
final int code = cell.getByte() & 0xFF;
|
|
||||||
final String msg = CellImpl.errorToDescription(code);
|
|
||||||
final String source = nodeToName(cell.getCircuitNode());
|
|
||||||
if(code == Cell.ERROR_PROTOCOL) {
|
|
||||||
logProtocolViolation(source, extendTarget);
|
|
||||||
}
|
|
||||||
throw new TorException("Error from ("+ source +") while extending to ("+ extendTarget.getNickname() + "): "+ msg);
|
|
||||||
} else if(command != expectedCommand) {
|
|
||||||
final String expected = RelayCellImpl.commandToDescription(expectedCommand);
|
|
||||||
final String received = RelayCellImpl.commandToDescription(command);
|
|
||||||
throw new TorException("Received incorrect extend response, expecting "+ expected + " but received "+ received);
|
|
||||||
} else {
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public CircuitNode createNewNode(Router r, byte[] keyMaterial, byte[] verifyDigest) {
|
|
||||||
final CircuitNode node = CircuitNodeImpl.createNode(r, circuit.getFinalCircuitNode(), keyMaterial, verifyDigest);
|
|
||||||
logger.fine("Adding new circuit node for "+ r.getNickname());
|
|
||||||
circuit.appendNode(node);
|
|
||||||
return node;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public RelayCell createRelayCell(int command) {
|
|
||||||
return new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), 0, command, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Router getFinalRouter() {
|
|
||||||
final CircuitNode node = circuit.getFinalCircuitNode();
|
|
||||||
if(node != null) {
|
|
||||||
return node.getRouter();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,351 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.Connection;
|
|
||||||
import com.subgraph.orchid.ConnectionIOException;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.Threading;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
import com.subgraph.orchid.circuits.cells.CellImpl;
|
|
||||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
|
||||||
|
|
||||||
public class CircuitIO implements DashboardRenderable {
|
|
||||||
private static final Logger logger = Logger.getLogger(CircuitIO.class.getName());
|
|
||||||
private final static long CIRCUIT_BUILD_TIMEOUT_MS = 30 * 1000;
|
|
||||||
private final static long CIRCUIT_RELAY_RESPONSE_TIMEOUT = 20 * 1000;
|
|
||||||
|
|
||||||
private final CircuitImpl circuit;
|
|
||||||
private final Connection connection;
|
|
||||||
private final int circuitId;
|
|
||||||
|
|
||||||
private final BlockingQueue<RelayCell> relayCellResponseQueue;
|
|
||||||
private final BlockingQueue<Cell> controlCellResponseQueue;
|
|
||||||
private final Map<Integer, StreamImpl> streamMap;
|
|
||||||
private final ReentrantLock streamLock = Threading.lock("stream");
|
|
||||||
private final ReentrantLock relaySendLock = Threading.lock("relaySend");
|
|
||||||
|
|
||||||
private boolean isMarkedForClose;
|
|
||||||
private boolean isClosed;
|
|
||||||
|
|
||||||
CircuitIO(CircuitImpl circuit, Connection connection, int circuitId) {
|
|
||||||
this.circuit = circuit;
|
|
||||||
this.connection = connection;
|
|
||||||
this.circuitId = circuitId;
|
|
||||||
|
|
||||||
this.relayCellResponseQueue = new LinkedBlockingQueue<RelayCell>();
|
|
||||||
this.controlCellResponseQueue = new LinkedBlockingQueue<Cell>();
|
|
||||||
this.streamMap = new HashMap<Integer, StreamImpl>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getCircuitId() {
|
|
||||||
return circuitId;
|
|
||||||
}
|
|
||||||
|
|
||||||
RelayCell dequeueRelayResponseCell() {
|
|
||||||
try {
|
|
||||||
final long timeout = getReceiveTimeout();
|
|
||||||
return relayCellResponseQueue.poll(timeout, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RelayCell decryptRelayCell(Cell cell) {
|
|
||||||
for(CircuitNode node: circuit.getNodeList()) {
|
|
||||||
if(node.decryptBackwardCell(cell)) {
|
|
||||||
return RelayCellImpl.createFromCell(node, cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destroyCircuit();
|
|
||||||
throw new TorException("Could not decrypt relay cell");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return null on timeout
|
|
||||||
Cell receiveControlCellResponse() {
|
|
||||||
try {
|
|
||||||
final long timeout = getReceiveTimeout();
|
|
||||||
return controlCellResponseQueue.poll(timeout, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private long getReceiveTimeout() {
|
|
||||||
if(circuit.getStatus().isBuilding())
|
|
||||||
return remainingBuildTime();
|
|
||||||
else
|
|
||||||
return CIRCUIT_RELAY_RESPONSE_TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long remainingBuildTime() {
|
|
||||||
final long elapsed = circuit.getStatus().getMillisecondsElapsedSinceCreated();
|
|
||||||
if(elapsed == 0 || elapsed >= CIRCUIT_BUILD_TIMEOUT_MS)
|
|
||||||
return 0;
|
|
||||||
return CIRCUIT_BUILD_TIMEOUT_MS - elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is called by the cell reading thread in ConnectionImpl to deliver control cells
|
|
||||||
* associated with this circuit (CREATED, CREATED_FAST, or DESTROY).
|
|
||||||
*/
|
|
||||||
void deliverControlCell(Cell cell) {
|
|
||||||
if(cell.getCommand() == Cell.DESTROY) {
|
|
||||||
processDestroyCell(cell.getByte());
|
|
||||||
} else {
|
|
||||||
controlCellResponseQueue.add(cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processDestroyCell(int reason) {
|
|
||||||
logger.fine("DESTROY cell received ("+ CellImpl.errorToDescription(reason) +") on "+ circuit);
|
|
||||||
destroyCircuit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is called by the cell reading thread in ConnectionImpl to deliver RELAY cells. */
|
|
||||||
void deliverRelayCell(Cell cell) {
|
|
||||||
circuit.getStatus().updateDirtyTimestamp();
|
|
||||||
final RelayCell relayCell = decryptRelayCell(cell);
|
|
||||||
logRelayCell("Dispatching: ", relayCell);
|
|
||||||
switch(relayCell.getRelayCommand()) {
|
|
||||||
case RelayCell.RELAY_EXTENDED:
|
|
||||||
case RelayCell.RELAY_EXTENDED2:
|
|
||||||
case RelayCell.RELAY_RESOLVED:
|
|
||||||
case RelayCell.RELAY_TRUNCATED:
|
|
||||||
case RelayCell.RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
|
|
||||||
case RelayCell.RELAY_COMMAND_INTRODUCE_ACK:
|
|
||||||
case RelayCell.RELAY_COMMAND_RENDEZVOUS2:
|
|
||||||
relayCellResponseQueue.add(relayCell);
|
|
||||||
break;
|
|
||||||
case RelayCell.RELAY_DATA:
|
|
||||||
case RelayCell.RELAY_END:
|
|
||||||
case RelayCell.RELAY_CONNECTED:
|
|
||||||
processRelayDataCell(relayCell);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RelayCell.RELAY_SENDME:
|
|
||||||
if(relayCell.getStreamId() != 0)
|
|
||||||
processRelayDataCell(relayCell);
|
|
||||||
else
|
|
||||||
processCircuitSendme(relayCell);
|
|
||||||
break;
|
|
||||||
case RelayCell.RELAY_BEGIN:
|
|
||||||
case RelayCell.RELAY_BEGIN_DIR:
|
|
||||||
case RelayCell.RELAY_EXTEND:
|
|
||||||
case RelayCell.RELAY_RESOLVE:
|
|
||||||
case RelayCell.RELAY_TRUNCATE:
|
|
||||||
destroyCircuit();
|
|
||||||
throw new TorException("Unexpected 'forward' direction relay cell type: "+ relayCell.getRelayCommand());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Runs in the context of the connection cell reading thread */
|
|
||||||
private void processRelayDataCell(RelayCell cell) {
|
|
||||||
if(cell.getRelayCommand() == RelayCell.RELAY_DATA) {
|
|
||||||
cell.getCircuitNode().decrementDeliverWindow();
|
|
||||||
if(cell.getCircuitNode().considerSendingSendme()) {
|
|
||||||
final RelayCell sendme = createRelayCell(RelayCell.RELAY_SENDME, 0, cell.getCircuitNode());
|
|
||||||
sendRelayCellTo(sendme, sendme.getCircuitNode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
final StreamImpl stream = streamMap.get(cell.getStreamId());
|
|
||||||
// It's not unusual for the stream to not be found. For example, if a RELAY_CONNECTED arrives after
|
|
||||||
// the client has stopped waiting for it, the stream will never be tracked and eventually the edge node
|
|
||||||
// will send a RELAY_END for this stream.
|
|
||||||
if(stream != null) {
|
|
||||||
stream.addInputCell(cell);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode) {
|
|
||||||
return new RelayCellImpl(targetNode, circuitId, streamId, relayCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendRelayCellTo(RelayCell cell, CircuitNode targetNode) {
|
|
||||||
relaySendLock.lock();
|
|
||||||
try {
|
|
||||||
logRelayCell("Sending: ", cell);
|
|
||||||
cell.setLength();
|
|
||||||
targetNode.updateForwardDigest(cell);
|
|
||||||
cell.setDigest(targetNode.getForwardDigestBytes());
|
|
||||||
|
|
||||||
for(CircuitNode node = targetNode; node != null; node = node.getPreviousNode())
|
|
||||||
node.encryptForwardCell(cell);
|
|
||||||
|
|
||||||
if(cell.getRelayCommand() == RelayCell.RELAY_DATA)
|
|
||||||
targetNode.waitForSendWindowAndDecrement();
|
|
||||||
|
|
||||||
sendCell(cell);
|
|
||||||
} finally {
|
|
||||||
relaySendLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void logRelayCell(String message, RelayCell cell) {
|
|
||||||
final Level level = getLogLevelForCell(cell);
|
|
||||||
if(!logger.isLoggable(level)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.log(level, message + cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Level getLogLevelForCell(RelayCell cell) {
|
|
||||||
switch(cell.getRelayCommand()) {
|
|
||||||
case RelayCell.RELAY_DATA:
|
|
||||||
case RelayCell.RELAY_SENDME:
|
|
||||||
return Level.FINEST;
|
|
||||||
default:
|
|
||||||
return Level.FINER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendCell(Cell cell) {
|
|
||||||
final CircuitStatus status = circuit.getStatus();
|
|
||||||
if(!(status.isConnected() || status.isBuilding()))
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
status.updateDirtyTimestamp();
|
|
||||||
connection.sendCell(cell);
|
|
||||||
} catch (ConnectionIOException e) {
|
|
||||||
destroyCircuit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void markForClose() {
|
|
||||||
boolean shouldClose;
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
if(isMarkedForClose) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isMarkedForClose = true;
|
|
||||||
shouldClose = streamMap.isEmpty();
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
if(shouldClose)
|
|
||||||
closeCircuit();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isMarkedForClose() {
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
return isMarkedForClose;
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeCircuit() {
|
|
||||||
logger.fine("Closing circuit "+ circuit);
|
|
||||||
sendDestroyCell(Cell.ERROR_NONE);
|
|
||||||
connection.removeCircuit(circuit);
|
|
||||||
circuit.setStateDestroyed();
|
|
||||||
isClosed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendDestroyCell(int reason) {
|
|
||||||
Cell destroy = CellImpl.createCell(circuitId, Cell.DESTROY);
|
|
||||||
destroy.putByte(reason);
|
|
||||||
try {
|
|
||||||
connection.sendCell(destroy);
|
|
||||||
} catch (ConnectionIOException e) {
|
|
||||||
logger.warning("Connection IO error sending DESTROY cell: "+ e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processCircuitSendme(RelayCell cell) {
|
|
||||||
cell.getCircuitNode().incrementSendWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroyCircuit() {
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
if(isClosed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
circuit.setStateDestroyed();
|
|
||||||
connection.removeCircuit(circuit);
|
|
||||||
final List<StreamImpl> tmpList = new ArrayList<StreamImpl>(streamMap.values());
|
|
||||||
for(StreamImpl s: tmpList) {
|
|
||||||
s.close();
|
|
||||||
}
|
|
||||||
isClosed = true;
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamImpl createNewStream(boolean autoclose) {
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
final int streamId = circuit.getStatus().nextStreamId();
|
|
||||||
final StreamImpl stream = new StreamImpl(circuit, circuit.getFinalCircuitNode(), streamId, autoclose);
|
|
||||||
streamMap.put(streamId, stream);
|
|
||||||
return stream;
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeStream(StreamImpl stream) {
|
|
||||||
boolean shouldClose;
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
streamMap.remove(stream.getStreamId());
|
|
||||||
shouldClose = streamMap.isEmpty() && isMarkedForClose;
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
if(shouldClose)
|
|
||||||
closeCircuit();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Stream> getActiveStreams() {
|
|
||||||
streamLock.lock();
|
|
||||||
try {
|
|
||||||
return new ArrayList<Stream>(streamMap.values());
|
|
||||||
} finally {
|
|
||||||
streamLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
|
||||||
if((flags & DASHBOARD_STREAMS) == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(Stream s: getActiveStreams()) {
|
|
||||||
renderer.renderComponent(writer, flags, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
import com.subgraph.orchid.Circuit;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.Connection;
|
|
||||||
import com.subgraph.orchid.DirectoryCircuit;
|
|
||||||
import com.subgraph.orchid.ExitCircuit;
|
|
||||||
import com.subgraph.orchid.InternalCircuit;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents an established circuit through the Tor network.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class CircuitImpl implements Circuit, DashboardRenderable {
|
|
||||||
protected final static Logger logger = Logger.getLogger(CircuitImpl.class.getName());
|
|
||||||
|
|
||||||
static ExitCircuit createExitCircuit(CircuitManagerImpl circuitManager, Router exitRouter) {
|
|
||||||
return new ExitCircuitImpl(circuitManager, exitRouter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ExitCircuit createExitCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
return new ExitCircuitImpl(circuitManager, prechosenPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
static DirectoryCircuit createDirectoryCircuit(CircuitManagerImpl circuitManager) {
|
|
||||||
return new DirectoryCircuitImpl(circuitManager, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
static DirectoryCircuit createDirectoryCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
return new DirectoryCircuitImpl(circuitManager, prechosenPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
static InternalCircuit createInternalCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
return new InternalCircuitImpl(circuitManager, prechosenPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final CircuitManagerImpl circuitManager;
|
|
||||||
protected final List<Router> prechosenPath;
|
|
||||||
|
|
||||||
private final List<CircuitNode> nodeList;
|
|
||||||
private final CircuitStatus status;
|
|
||||||
|
|
||||||
private CircuitIO io;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected CircuitImpl(CircuitManagerImpl circuitManager) {
|
|
||||||
this(circuitManager, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
nodeList = new ArrayList<CircuitNode>();
|
|
||||||
this.circuitManager = circuitManager;
|
|
||||||
this.prechosenPath = prechosenPath;
|
|
||||||
status = new CircuitStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Router> choosePath(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
|
|
||||||
if(prechosenPath != null) {
|
|
||||||
return new ArrayList<Router>(prechosenPath);
|
|
||||||
} else {
|
|
||||||
return choosePathForCircuit(pathChooser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException;
|
|
||||||
|
|
||||||
void bindToConnection(Connection connection) {
|
|
||||||
if(io != null) {
|
|
||||||
throw new IllegalStateException("Circuit already bound to a connection");
|
|
||||||
}
|
|
||||||
final int id = connection.bindCircuit(this);
|
|
||||||
io = new CircuitIO(this, connection, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markForClose() {
|
|
||||||
if(io != null) {
|
|
||||||
io.markForClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMarkedForClose() {
|
|
||||||
if(io == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return io.isMarkedForClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitStatus getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConnected() {
|
|
||||||
return status.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPending() {
|
|
||||||
return status.isBuilding();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isClean() {
|
|
||||||
return !status.isDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSecondsDirty() {
|
|
||||||
return (int) (status.getMillisecondsDirty() / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyCircuitBuildStart() {
|
|
||||||
if(!status.isUnconnected()) {
|
|
||||||
throw new IllegalStateException("Can only connect UNCONNECTED circuits");
|
|
||||||
}
|
|
||||||
status.updateCreatedTimestamp();
|
|
||||||
status.setStateBuilding();
|
|
||||||
circuitManager.addActiveCircuit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyCircuitBuildFailed() {
|
|
||||||
status.setStateFailed();
|
|
||||||
circuitManager.removeActiveCircuit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyCircuitBuildCompleted() {
|
|
||||||
status.setStateOpen();
|
|
||||||
status.updateCreatedTimestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Connection getConnection() {
|
|
||||||
if(!isConnected())
|
|
||||||
throw new TorException("Circuit is not connected.");
|
|
||||||
return io.getConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCircuitId() {
|
|
||||||
if(io == null) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return io.getCircuitId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendRelayCell(RelayCell cell) {
|
|
||||||
io.sendRelayCellTo(cell, cell.getCircuitNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendRelayCellToFinalNode(RelayCell cell) {
|
|
||||||
io.sendRelayCellTo(cell, getFinalCircuitNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendNode(CircuitNode node) {
|
|
||||||
nodeList.add(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CircuitNode> getNodeList() {
|
|
||||||
return nodeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getCircuitLength() {
|
|
||||||
return nodeList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitNode getFinalCircuitNode() {
|
|
||||||
if(nodeList.isEmpty())
|
|
||||||
throw new TorException("getFinalCircuitNode() called on empty circuit");
|
|
||||||
return nodeList.get( getCircuitLength() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode) {
|
|
||||||
return io.createRelayCell(relayCommand, streamId, targetNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RelayCell receiveRelayCell() {
|
|
||||||
return io.dequeueRelayResponseCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendCell(Cell cell) {
|
|
||||||
io.sendCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cell receiveControlCellResponse() {
|
|
||||||
return io.receiveControlCellResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is called by the cell reading thread in ConnectionImpl to deliver control cells
|
|
||||||
* associated with this circuit (CREATED or CREATED_FAST).
|
|
||||||
*/
|
|
||||||
public void deliverControlCell(Cell cell) {
|
|
||||||
io.deliverControlCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is called by the cell reading thread in ConnectionImpl to deliver RELAY cells. */
|
|
||||||
public void deliverRelayCell(Cell cell) {
|
|
||||||
io.deliverRelayCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected StreamImpl createNewStream(boolean autoclose) {
|
|
||||||
return io.createNewStream(autoclose);
|
|
||||||
}
|
|
||||||
protected StreamImpl createNewStream() {
|
|
||||||
return createNewStream(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStateDestroyed() {
|
|
||||||
status.setStateDestroyed();
|
|
||||||
circuitManager.removeActiveCircuit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroyCircuit() {
|
|
||||||
// We might not have bound this circuit yet
|
|
||||||
if (io != null) {
|
|
||||||
io.destroyCircuit();
|
|
||||||
}
|
|
||||||
circuitManager.removeActiveCircuit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removeStream(StreamImpl stream) {
|
|
||||||
io.removeStream(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Stream processStreamOpenException(Exception e) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
if(e instanceof InterruptedException) {
|
|
||||||
throw (InterruptedException) e;
|
|
||||||
} else if(e instanceof TimeoutException) {
|
|
||||||
throw(TimeoutException) e;
|
|
||||||
} else if(e instanceof StreamConnectFailedException) {
|
|
||||||
throw(StreamConnectFailedException) e;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String getCircuitTypeLabel();
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return " Circuit ("+ getCircuitTypeLabel() + ") id="+ getCircuitId() +" state=" + status.getStateAsString() +" "+ pathToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected String pathToString() {
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("[");
|
|
||||||
for(CircuitNode node: nodeList) {
|
|
||||||
if(sb.length() > 1)
|
|
||||||
sb.append(",");
|
|
||||||
sb.append(node.toString());
|
|
||||||
}
|
|
||||||
sb.append("]");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Stream> getActiveStreams() {
|
|
||||||
if(io == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
return io.getActiveStreams();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
|
||||||
if(io != null) {
|
|
||||||
writer.println(toString());
|
|
||||||
renderer.renderComponent(writer, flags, io);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,443 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Circuit;
|
|
||||||
import com.subgraph.orchid.CircuitBuildHandler;
|
|
||||||
import com.subgraph.orchid.CircuitManager;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.Connection;
|
|
||||||
import com.subgraph.orchid.ConnectionCache;
|
|
||||||
import com.subgraph.orchid.ConsensusDocument;
|
|
||||||
import com.subgraph.orchid.Directory;
|
|
||||||
import com.subgraph.orchid.DirectoryCircuit;
|
|
||||||
import com.subgraph.orchid.ExitCircuit;
|
|
||||||
import com.subgraph.orchid.InternalCircuit;
|
|
||||||
import com.subgraph.orchid.OpenFailedException;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.Threading;
|
|
||||||
import com.subgraph.orchid.Tor;
|
|
||||||
import com.subgraph.orchid.TorConfig;
|
|
||||||
import com.subgraph.orchid.circuits.guards.EntryGuards;
|
|
||||||
import com.subgraph.orchid.circuits.hs.HiddenServiceManager;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.crypto.TorRandom;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
|
||||||
|
|
||||||
public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
|
|
||||||
private final static int OPEN_DIRECTORY_STREAM_RETRY_COUNT = 5;
|
|
||||||
private final static int OPEN_DIRECTORY_STREAM_TIMEOUT = 10 * 1000;
|
|
||||||
|
|
||||||
interface CircuitFilter {
|
|
||||||
boolean filter(Circuit circuit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TorConfig config;
|
|
||||||
private final Directory directory;
|
|
||||||
private final ConnectionCache connectionCache;
|
|
||||||
private final Set<CircuitImpl> activeCircuits;
|
|
||||||
private final Queue<InternalCircuit> cleanInternalCircuits;
|
|
||||||
private int requestedInternalCircuitCount = 0;
|
|
||||||
private int pendingInternalCircuitCount = 0;
|
|
||||||
private final TorRandom random;
|
|
||||||
private final PendingExitStreams pendingExitStreams;
|
|
||||||
private final ScheduledExecutorService scheduledExecutor = Threading.newSingleThreadScheduledPool("CircuitManager worker");
|
|
||||||
private final CircuitCreationTask circuitCreationTask;
|
|
||||||
private final TorInitializationTracker initializationTracker;
|
|
||||||
private final CircuitPathChooser pathChooser;
|
|
||||||
private final HiddenServiceManager hiddenServiceManager;
|
|
||||||
private final ReentrantLock lock = Threading.lock("circuitManager");
|
|
||||||
|
|
||||||
private boolean isBuilding = false;
|
|
||||||
|
|
||||||
public CircuitManagerImpl(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker initializationTracker) {
|
|
||||||
this.config = config;
|
|
||||||
this.directory = directory;
|
|
||||||
this.connectionCache = connectionCache;
|
|
||||||
this.pathChooser = CircuitPathChooser.create(config, directory);
|
|
||||||
if(config.getUseEntryGuards() || config.getUseBridges()) {
|
|
||||||
this.pathChooser.enableEntryGuards(new EntryGuards(config, connectionCache, directoryDownloader, directory));
|
|
||||||
}
|
|
||||||
this.pendingExitStreams = new PendingExitStreams(config);
|
|
||||||
this.circuitCreationTask = new CircuitCreationTask(config, directory, connectionCache, pathChooser, this, initializationTracker);
|
|
||||||
this.activeCircuits = new HashSet<CircuitImpl>();
|
|
||||||
this.cleanInternalCircuits = new LinkedList<InternalCircuit>();
|
|
||||||
this.random = new TorRandom();
|
|
||||||
|
|
||||||
this.initializationTracker = initializationTracker;
|
|
||||||
this.hiddenServiceManager = new HiddenServiceManager(config, directory, this);
|
|
||||||
|
|
||||||
directoryDownloader.setCircuitManager(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startBuildingCircuits() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
isBuilding = true;
|
|
||||||
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopBuildingCircuits(boolean killCircuits) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
isBuilding = false;
|
|
||||||
scheduledExecutor.shutdownNow();
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (killCircuits) {
|
|
||||||
ArrayList<CircuitImpl> circuits;
|
|
||||||
synchronized (activeCircuits) {
|
|
||||||
circuits = new ArrayList<CircuitImpl>(activeCircuits);
|
|
||||||
}
|
|
||||||
for (CircuitImpl c : circuits) {
|
|
||||||
c.destroyCircuit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExitCircuit createNewExitCircuit(Router exitRouter) {
|
|
||||||
return CircuitImpl.createExitCircuit(this, exitRouter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addActiveCircuit(CircuitImpl circuit) {
|
|
||||||
synchronized (activeCircuits) {
|
|
||||||
activeCircuits.add(circuit);
|
|
||||||
activeCircuits.notifyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean doDestroy;
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
doDestroy = !isBuilding;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doDestroy) {
|
|
||||||
// we were asked to stop since this circuit was started
|
|
||||||
circuit.destroyCircuit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeActiveCircuit(CircuitImpl circuit) {
|
|
||||||
synchronized (activeCircuits) {
|
|
||||||
activeCircuits.remove(circuit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int getActiveCircuitCount() {
|
|
||||||
synchronized (activeCircuits) {
|
|
||||||
return activeCircuits.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Circuit> getPendingCircuits() {
|
|
||||||
return getCircuitsByFilter(new CircuitFilter() {
|
|
||||||
public boolean filter(Circuit circuit) {
|
|
||||||
return circuit.isPending();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
int getPendingCircuitCount() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
return getPendingCircuits().size();
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Circuit> getCircuitsByFilter(CircuitFilter filter) {
|
|
||||||
final Set<Circuit> result = new HashSet<Circuit>();
|
|
||||||
final Set<CircuitImpl> circuits = new HashSet<CircuitImpl>();
|
|
||||||
|
|
||||||
synchronized (activeCircuits) {
|
|
||||||
// the filter might lock additional objects, causing a deadlock, so don't
|
|
||||||
// call it inside the monitor
|
|
||||||
circuits.addAll(activeCircuits);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(CircuitImpl c: circuits) {
|
|
||||||
if(filter == null || filter.filter(c)) {
|
|
||||||
result.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ExitCircuit> getRandomlyOrderedListOfExitCircuits() {
|
|
||||||
final Set<Circuit> notDirectory = getCircuitsByFilter(new CircuitFilter() {
|
|
||||||
|
|
||||||
public boolean filter(Circuit circuit) {
|
|
||||||
final boolean exitType = circuit instanceof ExitCircuit;
|
|
||||||
return exitType && !circuit.isMarkedForClose() && circuit.isConnected();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final ArrayList<ExitCircuit> ac = new ArrayList<ExitCircuit>();
|
|
||||||
for(Circuit c: notDirectory) {
|
|
||||||
if(c instanceof ExitCircuit) {
|
|
||||||
ac.add((ExitCircuit) c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final int sz = ac.size();
|
|
||||||
for(int i = 0; i < sz; i++) {
|
|
||||||
final ExitCircuit tmp = ac.get(i);
|
|
||||||
final int swapIdx = random.nextInt(sz);
|
|
||||||
ac.set(i, ac.get(swapIdx));
|
|
||||||
ac.set(swapIdx, tmp);
|
|
||||||
}
|
|
||||||
return ac;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openExitStreamTo(String hostname, int port)
|
|
||||||
throws InterruptedException, TimeoutException, OpenFailedException {
|
|
||||||
if(hostname.endsWith(".onion")) {
|
|
||||||
return hiddenServiceManager.getStreamTo(hostname, port);
|
|
||||||
}
|
|
||||||
validateHostname(hostname);
|
|
||||||
circuitCreationTask.predictPort(port);
|
|
||||||
return pendingExitStreams.openExitStream(hostname, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateHostname(String hostname) throws OpenFailedException {
|
|
||||||
maybeRejectInternalAddress(hostname);
|
|
||||||
if(hostname.toLowerCase().endsWith(".onion")) {
|
|
||||||
throw new OpenFailedException("Hidden services not supported");
|
|
||||||
} else if(hostname.toLowerCase().endsWith(".exit")) {
|
|
||||||
throw new OpenFailedException(".exit addresses are not supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeRejectInternalAddress(String hostname) throws OpenFailedException {
|
|
||||||
if(IPv4Address.isValidIPv4AddressString(hostname)) {
|
|
||||||
maybeRejectInternalAddress(IPv4Address.createFromString(hostname));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeRejectInternalAddress(IPv4Address address) throws OpenFailedException {
|
|
||||||
final InetAddress inetAddress = address.toInetAddress();
|
|
||||||
if(inetAddress.isSiteLocalAddress() && config.getClientRejectInternalAddress()) {
|
|
||||||
throw new OpenFailedException("Rejecting stream target with internal address: "+ address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public Stream openExitStreamTo(IPv4Address address, int port)
|
|
||||||
throws InterruptedException, TimeoutException, OpenFailedException {
|
|
||||||
maybeRejectInternalAddress(address);
|
|
||||||
circuitCreationTask.predictPort(port);
|
|
||||||
return pendingExitStreams.openExitStream(address, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<StreamExitRequest> getPendingExitStreams() {
|
|
||||||
return pendingExitStreams.getUnreservedPendingRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openDirectoryStream() throws OpenFailedException, InterruptedException, TimeoutException {
|
|
||||||
return openDirectoryStream(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openDirectoryStream(int purpose) throws OpenFailedException, InterruptedException {
|
|
||||||
final int requestEventCode = purposeToEventCode(purpose, false);
|
|
||||||
final int loadingEventCode = purposeToEventCode(purpose, true);
|
|
||||||
|
|
||||||
int failCount = 0;
|
|
||||||
while(failCount < OPEN_DIRECTORY_STREAM_RETRY_COUNT) {
|
|
||||||
final DirectoryCircuit circuit = openDirectoryCircuit();
|
|
||||||
if(requestEventCode > 0) {
|
|
||||||
initializationTracker.notifyEvent(requestEventCode);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final Stream stream = circuit.openDirectoryStream(OPEN_DIRECTORY_STREAM_TIMEOUT, true);
|
|
||||||
if(loadingEventCode > 0) {
|
|
||||||
initializationTracker.notifyEvent(loadingEventCode);
|
|
||||||
}
|
|
||||||
return stream;
|
|
||||||
} catch (StreamConnectFailedException e) {
|
|
||||||
circuit.markForClose();
|
|
||||||
failCount += 1;
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
circuit.markForClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new OpenFailedException("Retry count exceeded opening directory stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryCircuit openDirectoryCircuit() throws OpenFailedException {
|
|
||||||
int failCount = 0;
|
|
||||||
while(failCount < OPEN_DIRECTORY_STREAM_RETRY_COUNT) {
|
|
||||||
final DirectoryCircuit circuit = CircuitImpl.createDirectoryCircuit(this);
|
|
||||||
if(tryOpenCircuit(circuit, true, true)) {
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
failCount += 1;
|
|
||||||
}
|
|
||||||
throw new OpenFailedException("Could not create circuit for directory stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int purposeToEventCode(int purpose, boolean getLoadingEvent) {
|
|
||||||
switch(purpose) {
|
|
||||||
case DIRECTORY_PURPOSE_CONSENSUS:
|
|
||||||
return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_STATUS : Tor.BOOTSTRAP_STATUS_REQUESTING_STATUS;
|
|
||||||
case DIRECTORY_PURPOSE_CERTIFICATES:
|
|
||||||
return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_KEYS : Tor.BOOTSTRAP_STATUS_REQUESTING_KEYS;
|
|
||||||
case DIRECTORY_PURPOSE_DESCRIPTORS:
|
|
||||||
return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_DESCRIPTORS : Tor.BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DirectoryCircuitResult implements CircuitBuildHandler {
|
|
||||||
|
|
||||||
private boolean isFailed;
|
|
||||||
|
|
||||||
public void connectionCompleted(Connection connection) {}
|
|
||||||
public void nodeAdded(CircuitNode node) {}
|
|
||||||
public void circuitBuildCompleted(Circuit circuit) {}
|
|
||||||
|
|
||||||
public void connectionFailed(String reason) {
|
|
||||||
isFailed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circuitBuildFailed(String reason) {
|
|
||||||
isFailed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isSuccessful() {
|
|
||||||
return !isFailed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
|
||||||
if((flags & DASHBOARD_CIRCUITS) == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
renderer.renderComponent(writer, flags, connectionCache);
|
|
||||||
renderer.renderComponent(writer, flags, circuitCreationTask.getCircuitPredictor());
|
|
||||||
writer.println("[Circuit Manager]");
|
|
||||||
writer.println();
|
|
||||||
for(Circuit c: getCircuitsByFilter(null)) {
|
|
||||||
renderer.renderComponent(writer, flags, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InternalCircuit getCleanInternalCircuit() throws InterruptedException {
|
|
||||||
synchronized(cleanInternalCircuits) {
|
|
||||||
try {
|
|
||||||
requestedInternalCircuitCount += 1;
|
|
||||||
while(cleanInternalCircuits.isEmpty()) {
|
|
||||||
cleanInternalCircuits.wait();
|
|
||||||
}
|
|
||||||
return cleanInternalCircuits.remove();
|
|
||||||
} finally {
|
|
||||||
requestedInternalCircuitCount -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int getNeededCleanCircuitCount(boolean isPredicted) {
|
|
||||||
synchronized (cleanInternalCircuits) {
|
|
||||||
final int predictedCount = (isPredicted) ? 2 : 0;
|
|
||||||
final int needed = Math.max(requestedInternalCircuitCount, predictedCount) - (pendingInternalCircuitCount + cleanInternalCircuits.size());
|
|
||||||
if(needed < 0) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return needed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void incrementPendingInternalCircuitCount() {
|
|
||||||
synchronized (cleanInternalCircuits) {
|
|
||||||
pendingInternalCircuitCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void decrementPendingInternalCircuitCount() {
|
|
||||||
synchronized (cleanInternalCircuits) {
|
|
||||||
pendingInternalCircuitCount -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addCleanInternalCircuit(InternalCircuit circuit) {
|
|
||||||
synchronized(cleanInternalCircuits) {
|
|
||||||
pendingInternalCircuitCount -= 1;
|
|
||||||
cleanInternalCircuits.add(circuit);
|
|
||||||
cleanInternalCircuits.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isNtorEnabled() {
|
|
||||||
switch(config.getUseNTorHandshake()) {
|
|
||||||
case AUTO:
|
|
||||||
return isNtorEnabledInConsensus();
|
|
||||||
case FALSE:
|
|
||||||
return false;
|
|
||||||
case TRUE:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("getUseNTorHandshake() returned "+ config.getUseNTorHandshake());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isNtorEnabledInConsensus() {
|
|
||||||
ConsensusDocument consensus = directory.getCurrentConsensusDocument();
|
|
||||||
return (consensus != null) && (consensus.getUseNTorHandshake());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryCircuit openDirectoryCircuitTo(List<Router> path) throws OpenFailedException {
|
|
||||||
final DirectoryCircuit circuit = CircuitImpl.createDirectoryCircuitTo(this, path);
|
|
||||||
if(!tryOpenCircuit(circuit, true, false)) {
|
|
||||||
throw new OpenFailedException("Could not create directory circuit for path");
|
|
||||||
}
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExitCircuit openExitCircuitTo(List<Router> path) throws OpenFailedException {
|
|
||||||
final ExitCircuit circuit = CircuitImpl.createExitCircuitTo(this, path);
|
|
||||||
if(!tryOpenCircuit(circuit, false, false)) {
|
|
||||||
throw new OpenFailedException("Could not create exit circuit for path");
|
|
||||||
}
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InternalCircuit openInternalCircuitTo(List<Router> path) throws OpenFailedException {
|
|
||||||
final InternalCircuit circuit = CircuitImpl.createInternalCircuitTo(this, path);
|
|
||||||
if(!tryOpenCircuit(circuit, false, false)) {
|
|
||||||
throw new OpenFailedException("Could not create internal circuit for path");
|
|
||||||
}
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryOpenCircuit(Circuit circuit, boolean isDirectory, boolean trackInitialization) {
|
|
||||||
final DirectoryCircuitResult result = new DirectoryCircuitResult();
|
|
||||||
final CircuitCreationRequest req = new CircuitCreationRequest(pathChooser, circuit, result, isDirectory);
|
|
||||||
final CircuitBuildTask task = new CircuitBuildTask(req, connectionCache, isNtorEnabled(), (trackInitialization) ? (initializationTracker) : (null));
|
|
||||||
task.run();
|
|
||||||
return result.isSuccessful();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
|
||||||
import com.subgraph.orchid.crypto.TorStreamCipher;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
|
|
||||||
public class CircuitNodeCryptoState {
|
|
||||||
public final static int KEY_MATERIAL_SIZE = TorMessageDigest.TOR_DIGEST_SIZE * 2 + TorStreamCipher.KEY_LEN * 2;
|
|
||||||
|
|
||||||
public static CircuitNodeCryptoState createFromKeyMaterial(byte[] keyMaterial, byte[] verifyDigest) {
|
|
||||||
return new CircuitNodeCryptoState(keyMaterial, verifyDigest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HexDigest checksumDigest;
|
|
||||||
private final TorMessageDigest forwardDigest;
|
|
||||||
private final TorMessageDigest backwardDigest;
|
|
||||||
private final TorStreamCipher forwardCipher;
|
|
||||||
private final TorStreamCipher backwardCipher;
|
|
||||||
|
|
||||||
static private byte[] extractDigestBytes(byte[] keyMaterial, int offset) {
|
|
||||||
final byte[] digestBytes = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
|
||||||
System.arraycopy(keyMaterial, offset, digestBytes, 0, TorMessageDigest.TOR_DIGEST_SIZE);
|
|
||||||
return digestBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static private byte[] extractCipherKey(byte[] keyMaterial, int offset) {
|
|
||||||
final byte[] keyBytes = new byte[TorStreamCipher.KEY_LEN];
|
|
||||||
System.arraycopy(keyMaterial, offset, keyBytes, 0, TorStreamCipher.KEY_LEN);
|
|
||||||
return keyBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNodeCryptoState(byte[] keyMaterial, byte[] verifyDigest) {
|
|
||||||
checksumDigest = HexDigest.createFromDigestBytes(verifyDigest);
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
forwardDigest = new TorMessageDigest();
|
|
||||||
forwardDigest.update(extractDigestBytes(keyMaterial, offset));
|
|
||||||
offset += TorMessageDigest.TOR_DIGEST_SIZE;
|
|
||||||
|
|
||||||
backwardDigest = new TorMessageDigest();
|
|
||||||
backwardDigest.update(extractDigestBytes(keyMaterial, offset));
|
|
||||||
offset += TorMessageDigest.TOR_DIGEST_SIZE;
|
|
||||||
|
|
||||||
forwardCipher = TorStreamCipher.createFromKeyBytes(extractCipherKey(keyMaterial, offset));
|
|
||||||
offset += TorStreamCipher.KEY_LEN;
|
|
||||||
|
|
||||||
backwardCipher = TorStreamCipher.createFromKeyBytes(extractCipherKey(keyMaterial, offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean verifyPacketDigest(HexDigest packetDigest) {
|
|
||||||
return checksumDigest.equals(packetDigest);
|
|
||||||
}
|
|
||||||
|
|
||||||
void encryptForwardCell(Cell cell) {
|
|
||||||
forwardCipher.encrypt(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean decryptBackwardCell(Cell cell) {
|
|
||||||
backwardCipher.encrypt(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
|
||||||
return isRecognizedCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateForwardDigest(Cell cell) {
|
|
||||||
forwardDigest.update(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] getForwardDigestBytes() {
|
|
||||||
return forwardDigest.getDigestBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRecognizedCell(Cell cell) {
|
|
||||||
if(cell.getShortAt(RelayCell.RECOGNIZED_OFFSET) != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
final byte[] digest = extractRelayDigest(cell);
|
|
||||||
final byte[] peek = backwardDigest.peekDigest(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
|
||||||
for(int i = 0; i < 4; i++)
|
|
||||||
if(digest[i] != peek[i]) {
|
|
||||||
replaceRelayDigest(cell, digest);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
backwardDigest.update(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
|
||||||
replaceRelayDigest(cell, digest);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] extractRelayDigest(Cell cell) {
|
|
||||||
final byte[] digest = new byte[4];
|
|
||||||
for(int i = 0; i < 4; i++) {
|
|
||||||
digest[i] = (byte) cell.getByteAt(i + RelayCell.DIGEST_OFFSET);
|
|
||||||
cell.putByteAt(i + RelayCell.DIGEST_OFFSET, 0);
|
|
||||||
}
|
|
||||||
return digest;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void replaceRelayDigest(Cell cell, byte[] digest) {
|
|
||||||
for(int i = 0; i < 4; i++)
|
|
||||||
cell.putByteAt(i + RelayCell.DIGEST_OFFSET, digest[i] & 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
|
|
||||||
public class CircuitNodeImpl implements CircuitNode {
|
|
||||||
|
|
||||||
public static CircuitNode createAnonymous(CircuitNode previous, byte[] keyMaterial, byte[] verifyDigest) {
|
|
||||||
return createNode(null, previous, keyMaterial, verifyDigest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CircuitNode createFirstHop(Router r, byte[] keyMaterial, byte[] verifyDigest) {
|
|
||||||
return createNode(r, null, keyMaterial, verifyDigest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CircuitNode createNode(Router r, CircuitNode previous, byte[] keyMaterial, byte[] verifyDigest) {
|
|
||||||
final CircuitNodeCryptoState cs = CircuitNodeCryptoState.createFromKeyMaterial(keyMaterial, verifyDigest);
|
|
||||||
return new CircuitNodeImpl(r, previous, cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static int CIRCWINDOW_START = 1000;
|
|
||||||
private final static int CIRCWINDOW_INCREMENT = 100;
|
|
||||||
|
|
||||||
private final Router router;
|
|
||||||
private final CircuitNodeCryptoState cryptoState;
|
|
||||||
private final CircuitNode previousNode;
|
|
||||||
|
|
||||||
private final Object windowLock;
|
|
||||||
private int packageWindow;
|
|
||||||
private int deliverWindow;
|
|
||||||
|
|
||||||
private CircuitNodeImpl(Router router, CircuitNode previous, CircuitNodeCryptoState cryptoState) {
|
|
||||||
previousNode = previous;
|
|
||||||
this.router = router;
|
|
||||||
this.cryptoState = cryptoState;
|
|
||||||
windowLock = new Object();
|
|
||||||
packageWindow = CIRCWINDOW_START;
|
|
||||||
deliverWindow = CIRCWINDOW_START;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Router getRouter() {
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitNode getPreviousNode() {
|
|
||||||
return previousNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encryptForwardCell(RelayCell cell) {
|
|
||||||
cryptoState.encryptForwardCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean decryptBackwardCell(Cell cell) {
|
|
||||||
return cryptoState.decryptBackwardCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateForwardDigest(RelayCell cell) {
|
|
||||||
cryptoState.updateForwardDigest(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getForwardDigestBytes() {
|
|
||||||
return cryptoState.getForwardDigestBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
if(router != null) {
|
|
||||||
return "|"+ router.getNickname() + "|";
|
|
||||||
} else {
|
|
||||||
return "|()|";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrementDeliverWindow() {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
deliverWindow--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean considerSendingSendme() {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
if(deliverWindow <= (CIRCWINDOW_START - CIRCWINDOW_INCREMENT)) {
|
|
||||||
deliverWindow += CIRCWINDOW_INCREMENT;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForSendWindow() {
|
|
||||||
waitForSendWindow(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForSendWindowAndDecrement() {
|
|
||||||
waitForSendWindow(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waitForSendWindow(boolean decrement) {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
while(packageWindow == 0) {
|
|
||||||
try {
|
|
||||||
windowLock.wait();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new TorException("Thread interrupted while waiting for circuit send window");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(decrement)
|
|
||||||
packageWindow--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void incrementSendWindow() {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
packageWindow += CIRCWINDOW_INCREMENT;
|
|
||||||
windowLock.notifyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
|
||||||
|
|
||||||
public class CircuitPredictor implements DashboardRenderable {
|
|
||||||
|
|
||||||
private final static Integer INTERNAL_CIRCUIT_PORT_VALUE = 0;
|
|
||||||
private final static long TIMEOUT_MS = 60 * 60 * 1000; // One hour
|
|
||||||
|
|
||||||
private final Map<Integer, Long> portsSeen;
|
|
||||||
|
|
||||||
public CircuitPredictor() {
|
|
||||||
portsSeen = new HashMap<Integer,Long>();
|
|
||||||
addExitPortRequest(80);
|
|
||||||
addInternalRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addExitPortRequest(int port) {
|
|
||||||
synchronized (portsSeen) {
|
|
||||||
portsSeen.put(port, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInternalRequest() {
|
|
||||||
addExitPortRequest(INTERNAL_CIRCUIT_PORT_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isEntryExpired(Entry<Integer, Long> e, long now) {
|
|
||||||
return (now - e.getValue()) > TIMEOUT_MS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeExpiredPorts() {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
final Iterator<Entry<Integer, Long>> it = portsSeen.entrySet().iterator();
|
|
||||||
while(it.hasNext()) {
|
|
||||||
if(isEntryExpired(it.next(), now)) {
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isInternalPredicted() {
|
|
||||||
synchronized (portsSeen) {
|
|
||||||
removeExpiredPorts();
|
|
||||||
return portsSeen.containsKey(INTERNAL_CIRCUIT_PORT_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Integer> getPredictedPorts() {
|
|
||||||
synchronized (portsSeen) {
|
|
||||||
removeExpiredPorts();
|
|
||||||
final Set<Integer> result = new HashSet<Integer>(portsSeen.keySet());
|
|
||||||
result.remove(INTERNAL_CIRCUIT_PORT_VALUE);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PredictedPortTarget> getPredictedPortTargets() {
|
|
||||||
final List<PredictedPortTarget> targets = new ArrayList<PredictedPortTarget>();
|
|
||||||
for(int p: getPredictedPorts()) {
|
|
||||||
targets.add(new PredictedPortTarget(p));
|
|
||||||
}
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
if((flags & DASHBOARD_PREDICTED_PORTS) == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writer.println("[Predicted Ports] ");
|
|
||||||
for(int port : portsSeen.keySet()) {
|
|
||||||
writer.write(" "+ port);
|
|
||||||
Long lastSeen = portsSeen.get(port);
|
|
||||||
if(lastSeen != null) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
long ms = now - lastSeen;
|
|
||||||
writer.write(" (last seen "+ TimeUnit.MINUTES.convert(ms, TimeUnit.MILLISECONDS) +" minutes ago)");
|
|
||||||
}
|
|
||||||
writer.println();
|
|
||||||
}
|
|
||||||
writer.println();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.crypto.TorRandom;
|
|
||||||
|
|
||||||
public class CircuitStatus {
|
|
||||||
|
|
||||||
enum CircuitState {
|
|
||||||
UNCONNECTED("Unconnected"),
|
|
||||||
BUILDING("Building"),
|
|
||||||
FAILED("Failed"),
|
|
||||||
OPEN("Open"),
|
|
||||||
DESTROYED("Destroyed");
|
|
||||||
String name;
|
|
||||||
CircuitState(String name) { this.name = name; }
|
|
||||||
public String toString() { return name; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private long timestampCreated;
|
|
||||||
private long timestampDirty;
|
|
||||||
private int currentStreamId;
|
|
||||||
private Object streamIdLock = new Object();
|
|
||||||
private volatile CircuitState state = CircuitState.UNCONNECTED;
|
|
||||||
|
|
||||||
CircuitStatus() {
|
|
||||||
initializeCurrentStreamId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeCurrentStreamId() {
|
|
||||||
final TorRandom random = new TorRandom();
|
|
||||||
currentStreamId = random.nextInt(0xFFFF) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void updateCreatedTimestamp() {
|
|
||||||
timestampCreated = System.currentTimeMillis();
|
|
||||||
timestampDirty = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void updateDirtyTimestamp() {
|
|
||||||
if(timestampDirty == 0 && state != CircuitState.BUILDING) {
|
|
||||||
timestampDirty = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized long getMillisecondsElapsedSinceCreated() {
|
|
||||||
return millisecondsElapsedSince(timestampCreated);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized long getMillisecondsDirty() {
|
|
||||||
return millisecondsElapsedSince(timestampDirty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long millisecondsElapsedSince(long then) {
|
|
||||||
if(then == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
return now - then;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized boolean isDirty() {
|
|
||||||
return timestampDirty != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStateBuilding() {
|
|
||||||
state = CircuitState.BUILDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStateFailed() {
|
|
||||||
state = CircuitState.FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStateOpen() {
|
|
||||||
state = CircuitState.OPEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStateDestroyed() {
|
|
||||||
state = CircuitState.DESTROYED;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isBuilding() {
|
|
||||||
return state == CircuitState.BUILDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isConnected() {
|
|
||||||
return state == CircuitState.OPEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isUnconnected() {
|
|
||||||
return state == CircuitState.UNCONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getStateAsString() {
|
|
||||||
if(state == CircuitState.OPEN) {
|
|
||||||
return state.toString() + " ["+ getDirtyString() + "]";
|
|
||||||
}
|
|
||||||
return state.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDirtyString() {
|
|
||||||
if(!isDirty()) {
|
|
||||||
return "Clean";
|
|
||||||
} else {
|
|
||||||
return "Dirty "+ (getMillisecondsDirty() / 1000) +"s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int nextStreamId() {
|
|
||||||
synchronized(streamIdLock) {
|
|
||||||
currentStreamId++;
|
|
||||||
if(currentStreamId > 0xFFFF)
|
|
||||||
currentStreamId = 1;
|
|
||||||
return currentStreamId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.DirectoryCircuit;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
|
||||||
|
|
||||||
public class DirectoryCircuitImpl extends CircuitImpl implements DirectoryCircuit {
|
|
||||||
|
|
||||||
protected DirectoryCircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
super(circuitManager, prechosenPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openDirectoryStream(long timeout, boolean autoclose) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
final StreamImpl stream = createNewStream(autoclose);
|
|
||||||
try {
|
|
||||||
stream.openDirectory(timeout);
|
|
||||||
return stream;
|
|
||||||
} catch (Exception e) {
|
|
||||||
removeStream(stream);
|
|
||||||
return processStreamOpenException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
|
|
||||||
if(prechosenPath != null) {
|
|
||||||
return prechosenPath;
|
|
||||||
}
|
|
||||||
return pathChooser.chooseDirectoryPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCircuitTypeLabel() {
|
|
||||||
return "Directory";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.ExitCircuit;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
|
||||||
|
|
||||||
public class ExitCircuitImpl extends CircuitImpl implements ExitCircuit {
|
|
||||||
|
|
||||||
private final Router exitRouter;
|
|
||||||
private final Set<ExitTarget> failedExitRequests;
|
|
||||||
|
|
||||||
ExitCircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
super(circuitManager, prechosenPath);
|
|
||||||
this.exitRouter = prechosenPath.get(prechosenPath.size() - 1);
|
|
||||||
this.failedExitRequests = new HashSet<ExitTarget>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ExitCircuitImpl(CircuitManagerImpl circuitManager, Router exitRouter) {
|
|
||||||
super(circuitManager);
|
|
||||||
this.exitRouter = exitRouter;
|
|
||||||
this.failedExitRequests = new HashSet<ExitTarget>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openExitStream(IPv4Address address, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
return openExitStream(address.toString(), port, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openExitStream(String target, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
final StreamImpl stream = createNewStream();
|
|
||||||
try {
|
|
||||||
stream.openExit(target, port, timeout);
|
|
||||||
return stream;
|
|
||||||
} catch (Exception e) {
|
|
||||||
removeStream(stream);
|
|
||||||
return processStreamOpenException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void recordFailedExitTarget(ExitTarget target) {
|
|
||||||
synchronized(failedExitRequests) {
|
|
||||||
failedExitRequests.add(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canHandleExitTo(ExitTarget target) {
|
|
||||||
synchronized(failedExitRequests) {
|
|
||||||
if(failedExitRequests.contains(target)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isMarkedForClose()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.isAddressTarget()) {
|
|
||||||
return exitRouter.exitPolicyAccepts(target.getAddress(), target.getPort());
|
|
||||||
} else {
|
|
||||||
return exitRouter.exitPolicyAccepts(target.getPort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canHandleExitToPort(int port) {
|
|
||||||
return exitRouter.exitPolicyAccepts(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
|
|
||||||
return pathChooser.choosePathWithExit(exitRouter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCircuitTypeLabel() {
|
|
||||||
return "Exit";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Circuit;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.DirectoryCircuit;
|
|
||||||
import com.subgraph.orchid.HiddenServiceCircuit;
|
|
||||||
import com.subgraph.orchid.InternalCircuit;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
|
||||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
|
||||||
|
|
||||||
public class InternalCircuitImpl extends CircuitImpl implements InternalCircuit, DirectoryCircuit, HiddenServiceCircuit {
|
|
||||||
|
|
||||||
private enum InternalType { UNUSED, HS_INTRODUCTION, HS_DIRECTORY, HS_CIRCUIT }
|
|
||||||
|
|
||||||
private InternalType type;
|
|
||||||
private boolean ntorEnabled;
|
|
||||||
|
|
||||||
InternalCircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
|
||||||
super(circuitManager, prechosenPath);
|
|
||||||
this.type = InternalType.UNUSED;
|
|
||||||
this.ntorEnabled = circuitManager.isNtorEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InternalCircuitImpl(CircuitManagerImpl circuitManager) {
|
|
||||||
this(circuitManager, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Router> choosePathForCircuit(CircuitPathChooser pathChooser)
|
|
||||||
throws InterruptedException, PathSelectionFailedException {
|
|
||||||
return pathChooser.chooseInternalPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Circuit cannibalizeToIntroductionPoint(Router target) {
|
|
||||||
cannibalizeTo(target);
|
|
||||||
type = InternalType.HS_INTRODUCTION;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cannibalizeTo(Router target) {
|
|
||||||
if(type != InternalType.UNUSED) {
|
|
||||||
throw new IllegalStateException("Cannot cannibalize internal circuit with type "+ type);
|
|
||||||
|
|
||||||
}
|
|
||||||
final CircuitExtender extender = new CircuitExtender(this, ntorEnabled);
|
|
||||||
extender.extendTo(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openDirectoryStream(long timeout, boolean autoclose) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
if(type != InternalType.HS_DIRECTORY) {
|
|
||||||
throw new IllegalStateException("Cannot open directory stream on internal circuit with type "+ type);
|
|
||||||
}
|
|
||||||
final StreamImpl stream = createNewStream();
|
|
||||||
try {
|
|
||||||
stream.openDirectory(timeout);
|
|
||||||
return stream;
|
|
||||||
} catch (Exception e) {
|
|
||||||
removeStream(stream);
|
|
||||||
return processStreamOpenException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public DirectoryCircuit cannibalizeToDirectory(Router target) {
|
|
||||||
cannibalizeTo(target);
|
|
||||||
type = InternalType.HS_DIRECTORY;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public HiddenServiceCircuit connectHiddenService(CircuitNode node) {
|
|
||||||
if(type != InternalType.UNUSED) {
|
|
||||||
throw new IllegalStateException("Cannot connect hidden service from internal circuit type "+ type);
|
|
||||||
}
|
|
||||||
appendNode(node);
|
|
||||||
type = InternalType.HS_CIRCUIT;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream openStream(int port, long timeout)
|
|
||||||
throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
if(type != InternalType.HS_CIRCUIT) {
|
|
||||||
throw new IllegalStateException("Cannot open stream to hidden service from internal circuit type "+ type);
|
|
||||||
}
|
|
||||||
final StreamImpl stream = createNewStream();
|
|
||||||
try {
|
|
||||||
stream.openExit("", port, timeout);
|
|
||||||
return stream;
|
|
||||||
} catch (Exception e) {
|
|
||||||
removeStream(stream);
|
|
||||||
return processStreamOpenException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCircuitTypeLabel() {
|
|
||||||
switch(type) {
|
|
||||||
case HS_CIRCUIT:
|
|
||||||
return "Hidden Service";
|
|
||||||
case HS_DIRECTORY:
|
|
||||||
return "HS Directory";
|
|
||||||
case HS_INTRODUCTION:
|
|
||||||
return "HS Introduction";
|
|
||||||
case UNUSED:
|
|
||||||
return "Internal";
|
|
||||||
default:
|
|
||||||
return "(null)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
|
||||||
import com.subgraph.orchid.crypto.TorNTorKeyAgreement;
|
|
||||||
|
|
||||||
public class NTorCircuitExtender {
|
|
||||||
private final static Logger logger = Logger.getLogger(NTorCircuitExtender.class.getName());
|
|
||||||
|
|
||||||
private final CircuitExtender extender;
|
|
||||||
private final Router router;
|
|
||||||
private final TorNTorKeyAgreement kex;
|
|
||||||
|
|
||||||
public NTorCircuitExtender(CircuitExtender extender, Router router) {
|
|
||||||
this.extender = extender;
|
|
||||||
this.router = router;
|
|
||||||
this.kex = new TorNTorKeyAgreement(router.getIdentityHash(), router.getNTorOnionKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitNode extendTo() {
|
|
||||||
final byte[] onion = kex.createOnionSkin();
|
|
||||||
if(finalRouterSupportsExtend2()) {
|
|
||||||
logger.fine("Extending circuit to "+ router.getNickname() + " with NTor inside RELAY_EXTEND2");
|
|
||||||
return extendWithExtend2(onion);
|
|
||||||
} else {
|
|
||||||
logger.fine("Extending circuit to "+ router.getNickname() + " with NTor inside RELAY_EXTEND");
|
|
||||||
return extendWithTunneledExtend(onion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode extendWithExtend2(byte[] onion) {
|
|
||||||
final RelayCell cell = createExtend2Cell(onion);
|
|
||||||
extender.sendRelayCell(cell);
|
|
||||||
final RelayCell response = extender.receiveRelayResponse(RelayCell.RELAY_EXTENDED2, router);
|
|
||||||
return processExtended2(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode extendWithTunneledExtend(byte[] onion) {
|
|
||||||
final RelayCell cell = createExtendCell(onion, kex.getNtorCreateMagic());
|
|
||||||
extender.sendRelayCell(cell);
|
|
||||||
final RelayCell response = extender.receiveRelayResponse(RelayCell.RELAY_EXTENDED, router);
|
|
||||||
return processExtended(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean finalRouterSupportsExtend2() {
|
|
||||||
return extender.getFinalRouter().getNTorOnionKey() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RelayCell createExtend2Cell(byte[] ntorOnionskin) {
|
|
||||||
final RelayCell cell = extender.createRelayCell(RelayCell.RELAY_EXTEND2);
|
|
||||||
|
|
||||||
cell.putByte(2);
|
|
||||||
|
|
||||||
cell.putByte(0);
|
|
||||||
cell.putByte(6);
|
|
||||||
cell.putByteArray(router.getAddress().getAddressDataBytes());
|
|
||||||
cell.putShort(router.getOnionPort());
|
|
||||||
|
|
||||||
cell.putByte(2);
|
|
||||||
cell.putByte(20);
|
|
||||||
cell.putByteArray(router.getIdentityHash().getRawBytes());
|
|
||||||
|
|
||||||
cell.putShort(0x0002);
|
|
||||||
cell.putShort(ntorOnionskin.length);
|
|
||||||
cell.putByteArray(ntorOnionskin);
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RelayCell createExtendCell(byte[] ntorOnionskin, byte[] ntorMagic) {
|
|
||||||
final RelayCell cell = extender.createRelayCell(RelayCell.RELAY_EXTEND);
|
|
||||||
cell.putByteArray(router.getAddress().getAddressDataBytes());
|
|
||||||
cell.putShort(router.getOnionPort());
|
|
||||||
final int paddingLength = CircuitExtender.TAP_ONIONSKIN_LEN - (ntorOnionskin.length + ntorMagic.length);
|
|
||||||
final byte[] padding = new byte[paddingLength];
|
|
||||||
cell.putByteArray(ntorMagic);
|
|
||||||
cell.putByteArray(ntorOnionskin);
|
|
||||||
cell.putByteArray(padding);
|
|
||||||
cell.putByteArray(router.getIdentityHash().getRawBytes());
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode processExtended(RelayCell cell) {
|
|
||||||
byte[] payload = new byte[CircuitExtender.TAP_ONIONSKIN_REPLY_LEN];
|
|
||||||
cell.getByteArray(payload);
|
|
||||||
|
|
||||||
return processPayload(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private CircuitNode processExtended2(RelayCell cell) {
|
|
||||||
final int payloadLength = cell.getShort();
|
|
||||||
if(payloadLength > cell.cellBytesRemaining()) {
|
|
||||||
throw new TorException("Incorrect payload length value in RELAY_EXTENED2 cell");
|
|
||||||
}
|
|
||||||
byte[] payload = new byte[payloadLength];
|
|
||||||
cell.getByteArray(payload);
|
|
||||||
|
|
||||||
return processPayload(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode processPayload(byte[] payload) {
|
|
||||||
final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE];
|
|
||||||
final byte[] verifyDigest = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
|
||||||
if(!kex.deriveKeysFromHandshakeResponse(payload, keyMaterial, verifyDigest)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return extender.createNewNode(router, keyMaterial, verifyDigest);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.ExitCircuit;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
|
|
||||||
public class OpenExitStreamTask implements Runnable {
|
|
||||||
private final static Logger logger = Logger.getLogger(OpenExitStreamTask.class.getName());
|
|
||||||
private final ExitCircuit circuit;
|
|
||||||
private final StreamExitRequest exitRequest;
|
|
||||||
|
|
||||||
OpenExitStreamTask(ExitCircuit circuit, StreamExitRequest exitRequest) {
|
|
||||||
this.circuit = circuit;
|
|
||||||
this.exitRequest = exitRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
logger.fine("Attempting to open stream to "+ exitRequest);
|
|
||||||
try {
|
|
||||||
exitRequest.setCompletedSuccessfully(tryOpenExitStream());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
exitRequest.setInterrupted();
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
circuit.markForClose();
|
|
||||||
exitRequest.setCompletedTimeout();
|
|
||||||
} catch (StreamConnectFailedException e) {
|
|
||||||
if(!e.isReasonRetryable()) {
|
|
||||||
exitRequest.setExitFailed();
|
|
||||||
circuit.recordFailedExitTarget(exitRequest);
|
|
||||||
} else {
|
|
||||||
circuit.markForClose();
|
|
||||||
exitRequest.setStreamOpenFailure(e.getReason());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Stream tryOpenExitStream() throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
if(exitRequest.isAddressTarget()) {
|
|
||||||
return circuit.openExitStream(exitRequest.getAddress(), exitRequest.getPort(), exitRequest.getStreamTimeout());
|
|
||||||
} else {
|
|
||||||
return circuit.openExitStream(exitRequest.getHostname(), exitRequest.getPort(), exitRequest.getStreamTimeout());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.OpenFailedException;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.TorConfig;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
|
|
||||||
public class PendingExitStreams {
|
|
||||||
|
|
||||||
private final Set<StreamExitRequest> pendingRequests;
|
|
||||||
private final Object lock = new Object();
|
|
||||||
private final TorConfig config;
|
|
||||||
|
|
||||||
PendingExitStreams(TorConfig config) {
|
|
||||||
this.config = config;
|
|
||||||
pendingRequests = new HashSet<StreamExitRequest>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream openExitStream(IPv4Address address, int port) throws InterruptedException, OpenFailedException {
|
|
||||||
final StreamExitRequest request = new StreamExitRequest(lock, address, port);
|
|
||||||
return openExitStreamByRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream openExitStream(String hostname, int port) throws InterruptedException, OpenFailedException {
|
|
||||||
final StreamExitRequest request = new StreamExitRequest(lock, hostname, port);
|
|
||||||
return openExitStreamByRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Stream openExitStreamByRequest(StreamExitRequest request) throws InterruptedException, OpenFailedException {
|
|
||||||
if(config.getCircuitStreamTimeout() != 0) {
|
|
||||||
request.setStreamTimeout(config.getCircuitStreamTimeout());
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized(lock) {
|
|
||||||
pendingRequests.add(request);
|
|
||||||
try {
|
|
||||||
return handleRequest(request);
|
|
||||||
} finally {
|
|
||||||
pendingRequests.remove(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Stream handleRequest(StreamExitRequest request) throws InterruptedException, OpenFailedException {
|
|
||||||
while(true) {
|
|
||||||
while(!request.isCompleted()) {
|
|
||||||
lock.wait();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return request.getStream();
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
request.resetForRetry();
|
|
||||||
} catch (StreamConnectFailedException e) {
|
|
||||||
request.resetForRetry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<StreamExitRequest> getUnreservedPendingRequests() {
|
|
||||||
final List<StreamExitRequest> result = new ArrayList<StreamExitRequest>();
|
|
||||||
synchronized (lock) {
|
|
||||||
for(StreamExitRequest request: pendingRequests) {
|
|
||||||
if(!request.isReserved()) {
|
|
||||||
result.add(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
|
||||||
|
|
||||||
public class PredictedPortTarget implements ExitTarget {
|
|
||||||
|
|
||||||
final int port;
|
|
||||||
|
|
||||||
public PredictedPortTarget(int port) {
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAddressTarget() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPv4Address getAddress() {
|
|
||||||
return new IPv4Address(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHostname() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.OpenFailedException;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
|
||||||
import com.subgraph.orchid.misc.GuardedBy;
|
|
||||||
|
|
||||||
public class StreamExitRequest implements ExitTarget {
|
|
||||||
|
|
||||||
private enum CompletionStatus {NOT_COMPLETED, SUCCESS, TIMEOUT, STREAM_OPEN_FAILURE, EXIT_FAILURE, INTERRUPTED};
|
|
||||||
|
|
||||||
private final boolean isAddress;
|
|
||||||
private final IPv4Address address;
|
|
||||||
private final String hostname;
|
|
||||||
private final int port;
|
|
||||||
private final Object requestCompletionLock;
|
|
||||||
|
|
||||||
@GuardedBy("requestCompletionLock") private CompletionStatus completionStatus;
|
|
||||||
@GuardedBy("requestCompletionLock") private Stream stream;
|
|
||||||
@GuardedBy("requestCompletionLock") private int streamOpenFailReason;
|
|
||||||
|
|
||||||
@GuardedBy("this") private boolean isReserved;
|
|
||||||
@GuardedBy("this") private int retryCount;
|
|
||||||
@GuardedBy("this") private long specificTimeout;
|
|
||||||
|
|
||||||
StreamExitRequest(Object requestCompletionLock, IPv4Address address, int port) {
|
|
||||||
this(requestCompletionLock, true, "", address, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamExitRequest(Object requestCompletionLock, String hostname, int port) {
|
|
||||||
this(requestCompletionLock, false, hostname, null, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StreamExitRequest(Object requestCompletionLock, boolean isAddress, String hostname, IPv4Address address, int port) {
|
|
||||||
this.requestCompletionLock = requestCompletionLock;
|
|
||||||
this.isAddress = isAddress;
|
|
||||||
this.hostname = hostname;
|
|
||||||
this.address = address;
|
|
||||||
this.port = port;
|
|
||||||
this.completionStatus = CompletionStatus.NOT_COMPLETED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAddressTarget() {
|
|
||||||
return isAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPv4Address getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHostname() {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setStreamTimeout(long timeout) {
|
|
||||||
specificTimeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized long getStreamTimeout() {
|
|
||||||
if(specificTimeout > 0) {
|
|
||||||
return specificTimeout;
|
|
||||||
} else if(retryCount < 2) {
|
|
||||||
return 10 * 1000;
|
|
||||||
} else {
|
|
||||||
return 15 * 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCompletedTimeout() {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
newStatus(CompletionStatus.TIMEOUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setExitFailed() {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
newStatus(CompletionStatus.EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStreamOpenFailure(int reason) {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
streamOpenFailReason = reason;
|
|
||||||
newStatus(CompletionStatus.STREAM_OPEN_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCompletedSuccessfully(Stream stream) {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
this.stream = stream;
|
|
||||||
newStatus(CompletionStatus.SUCCESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setInterrupted() {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
newStatus(CompletionStatus.INTERRUPTED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void newStatus(CompletionStatus newStatus) {
|
|
||||||
if(completionStatus != CompletionStatus.NOT_COMPLETED) {
|
|
||||||
throw new IllegalStateException("Attempt to set completion state to " + newStatus +" while status is "+ completionStatus);
|
|
||||||
}
|
|
||||||
completionStatus = newStatus;
|
|
||||||
requestCompletionLock.notifyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Stream getStream() throws OpenFailedException, TimeoutException, StreamConnectFailedException, InterruptedException {
|
|
||||||
synchronized(requestCompletionLock) {
|
|
||||||
switch(completionStatus) {
|
|
||||||
case NOT_COMPLETED:
|
|
||||||
throw new IllegalStateException("Request not completed");
|
|
||||||
case EXIT_FAILURE:
|
|
||||||
throw new OpenFailedException("Failure at exit node");
|
|
||||||
case TIMEOUT:
|
|
||||||
throw new TimeoutException();
|
|
||||||
case STREAM_OPEN_FAILURE:
|
|
||||||
throw new StreamConnectFailedException(streamOpenFailReason);
|
|
||||||
case INTERRUPTED:
|
|
||||||
throw new InterruptedException();
|
|
||||||
case SUCCESS:
|
|
||||||
return stream;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unknown completion status");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void resetForRetry() {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
streamOpenFailReason = 0;
|
|
||||||
completionStatus = CompletionStatus.NOT_COMPLETED;
|
|
||||||
}
|
|
||||||
retryCount += 1;
|
|
||||||
isReserved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isCompleted() {
|
|
||||||
synchronized (requestCompletionLock) {
|
|
||||||
return completionStatus != CompletionStatus.NOT_COMPLETED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized boolean reserveRequest() {
|
|
||||||
if(isReserved) return false;
|
|
||||||
isReserved = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized boolean isReserved() {
|
|
||||||
return isReserved;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
if(isAddress)
|
|
||||||
return address + ":"+ port;
|
|
||||||
else
|
|
||||||
return hostname + ":"+ port;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Circuit;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.StreamConnectFailedException;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
|
||||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
|
||||||
|
|
||||||
public class StreamImpl implements Stream, DashboardRenderable {
|
|
||||||
private final static Logger logger = Logger.getLogger(StreamImpl.class.getName());
|
|
||||||
|
|
||||||
private final static int STREAMWINDOW_START = 500;
|
|
||||||
private final static int STREAMWINDOW_INCREMENT = 50;
|
|
||||||
private final static int STREAMWINDOW_MAX_UNFLUSHED = 10;
|
|
||||||
|
|
||||||
private final CircuitImpl circuit;
|
|
||||||
|
|
||||||
private final int streamId;
|
|
||||||
private final boolean autoclose;
|
|
||||||
|
|
||||||
private final CircuitNode targetNode;
|
|
||||||
private final TorInputStream inputStream;
|
|
||||||
private final TorOutputStream outputStream;
|
|
||||||
|
|
||||||
private boolean isClosed;
|
|
||||||
private boolean relayEndReceived;
|
|
||||||
private int relayEndReason;
|
|
||||||
private boolean relayConnectedReceived;
|
|
||||||
private final Object waitConnectLock = new Object();
|
|
||||||
private final Object windowLock = new Object();
|
|
||||||
private int packageWindow;
|
|
||||||
private int deliverWindow;
|
|
||||||
|
|
||||||
private String streamTarget = "";
|
|
||||||
|
|
||||||
StreamImpl(CircuitImpl circuit, CircuitNode targetNode, int streamId, boolean autoclose) {
|
|
||||||
this.circuit = circuit;
|
|
||||||
this.targetNode = targetNode;
|
|
||||||
this.streamId = streamId;
|
|
||||||
this.autoclose = autoclose;
|
|
||||||
this.inputStream = new TorInputStream(this);
|
|
||||||
this.outputStream = new TorOutputStream(this);
|
|
||||||
packageWindow = STREAMWINDOW_START;
|
|
||||||
deliverWindow = STREAMWINDOW_START;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInputCell(RelayCell cell) {
|
|
||||||
if(isClosed)
|
|
||||||
return;
|
|
||||||
if(cell.getRelayCommand() == RelayCell.RELAY_END) {
|
|
||||||
synchronized(waitConnectLock) {
|
|
||||||
relayEndReason = cell.getByte();
|
|
||||||
relayEndReceived = true;
|
|
||||||
inputStream.addEndCell(cell);
|
|
||||||
waitConnectLock.notifyAll();
|
|
||||||
}
|
|
||||||
} else if(cell.getRelayCommand() == RelayCell.RELAY_CONNECTED) {
|
|
||||||
synchronized(waitConnectLock) {
|
|
||||||
relayConnectedReceived = true;
|
|
||||||
waitConnectLock.notifyAll();
|
|
||||||
}
|
|
||||||
} else if(cell.getRelayCommand() == RelayCell.RELAY_SENDME) {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
packageWindow += STREAMWINDOW_INCREMENT;
|
|
||||||
windowLock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
inputStream.addInputCell(cell);
|
|
||||||
synchronized(windowLock) {
|
|
||||||
deliverWindow--;
|
|
||||||
if(deliverWindow < 0)
|
|
||||||
throw new TorException("Stream has negative delivery window");
|
|
||||||
}
|
|
||||||
considerSendingSendme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void considerSendingSendme() {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
if(deliverWindow > (STREAMWINDOW_START - STREAMWINDOW_INCREMENT))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(inputStream.unflushedCellCount() >= STREAMWINDOW_MAX_UNFLUSHED)
|
|
||||||
return;
|
|
||||||
|
|
||||||
final RelayCell sendme = circuit.createRelayCell(RelayCell.RELAY_SENDME, streamId, targetNode);
|
|
||||||
circuit.sendRelayCell(sendme);
|
|
||||||
deliverWindow += STREAMWINDOW_INCREMENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Circuit getCircuit() {
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitNode getTargetNode() {
|
|
||||||
return targetNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
if(isClosed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
logger.fine("Closing stream "+ this);
|
|
||||||
|
|
||||||
isClosed = true;
|
|
||||||
inputStream.close();
|
|
||||||
outputStream.close();
|
|
||||||
circuit.removeStream(this);
|
|
||||||
if(autoclose) {
|
|
||||||
circuit.markForClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!relayEndReceived) {
|
|
||||||
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_END);
|
|
||||||
cell.putByte(RelayCell.REASON_DONE);
|
|
||||||
circuit.sendRelayCellToFinalNode(cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openDirectory(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
streamTarget = "[Directory]";
|
|
||||||
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN_DIR);
|
|
||||||
circuit.sendRelayCellToFinalNode(cell);
|
|
||||||
waitForRelayConnected(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void openExit(String target, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
streamTarget = target + ":"+ port;
|
|
||||||
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN);
|
|
||||||
cell.putString(target + ":"+ port);
|
|
||||||
circuit.sendRelayCellToFinalNode(cell);
|
|
||||||
waitForRelayConnected(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waitForRelayConnected(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
|
||||||
final long start = System.currentTimeMillis();
|
|
||||||
long elapsed = 0;
|
|
||||||
synchronized(waitConnectLock) {
|
|
||||||
while(!relayConnectedReceived) {
|
|
||||||
|
|
||||||
if(relayEndReceived) {
|
|
||||||
throw new StreamConnectFailedException(relayEndReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(elapsed >= timeout) {
|
|
||||||
throw new TimeoutException();
|
|
||||||
}
|
|
||||||
|
|
||||||
waitConnectLock.wait(timeout - elapsed);
|
|
||||||
|
|
||||||
elapsed = System.currentTimeMillis() - start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream getOutputStream() {
|
|
||||||
return outputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForSendWindowAndDecrement() {
|
|
||||||
waitForSendWindow(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForSendWindow() {
|
|
||||||
waitForSendWindow(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForSendWindow(boolean decrement) {
|
|
||||||
synchronized(windowLock) {
|
|
||||||
while(packageWindow == 0) {
|
|
||||||
try {
|
|
||||||
windowLock.wait();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new TorException("Thread interrupted while waiting for stream package window");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(decrement)
|
|
||||||
packageWindow--;
|
|
||||||
}
|
|
||||||
targetNode.waitForSendWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "[Stream stream_id="+ streamId + " circuit="+ circuit +" target="+ streamTarget +"]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
|
||||||
writer.print(" ");
|
|
||||||
writer.print("[Stream stream_id="+ streamId + " cid="+ circuit.getCircuitId());
|
|
||||||
if(relayConnectedReceived) {
|
|
||||||
writer.print(" sent="+outputStream.getBytesSent() + " recv="+ inputStream.getBytesReceived());
|
|
||||||
} else {
|
|
||||||
writer.print(" (waiting connect)");
|
|
||||||
}
|
|
||||||
writer.print(" target="+ streamTarget);
|
|
||||||
writer.println("]");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
|
||||||
import com.subgraph.orchid.crypto.TorTapKeyAgreement;
|
|
||||||
|
|
||||||
public class TapCircuitExtender {
|
|
||||||
private final static Logger logger = Logger.getLogger(TapCircuitExtender.class.getName());
|
|
||||||
|
|
||||||
private final CircuitExtender extender;
|
|
||||||
private final TorTapKeyAgreement kex;
|
|
||||||
private final Router router;
|
|
||||||
|
|
||||||
public TapCircuitExtender(CircuitExtender extender, Router router) {
|
|
||||||
this.extender = extender;
|
|
||||||
this.router = router;
|
|
||||||
this.kex = new TorTapKeyAgreement(router.getOnionKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitNode extendTo() {
|
|
||||||
logger.fine("Extending to "+ router.getNickname() + " with TAP");
|
|
||||||
final RelayCell cell = createRelayExtendCell();
|
|
||||||
extender.sendRelayCell(cell);
|
|
||||||
final RelayCell response = extender.receiveRelayResponse(RelayCell.RELAY_EXTENDED, router);
|
|
||||||
if(response == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return processExtendResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CircuitNode processExtendResponse(RelayCell response) {
|
|
||||||
final byte[] handshakeResponse = new byte[TorTapKeyAgreement.DH_LEN + TorMessageDigest.TOR_DIGEST_SIZE];
|
|
||||||
response.getByteArray(handshakeResponse);
|
|
||||||
|
|
||||||
final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE];
|
|
||||||
final byte[] verifyDigest = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
|
||||||
if(!kex.deriveKeysFromHandshakeResponse(handshakeResponse, keyMaterial, verifyDigest)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return extender.createNewNode(router, keyMaterial, verifyDigest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RelayCell createRelayExtendCell() {
|
|
||||||
final RelayCell cell = extender.createRelayCell(RelayCell.RELAY_EXTEND);
|
|
||||||
cell.putByteArray(router.getAddress().getAddressDataBytes());
|
|
||||||
cell.putShort(router.getOnionPort());
|
|
||||||
cell.putByteArray(kex.createOnionSkin());
|
|
||||||
cell.putByteArray(router.getIdentityHash().getRawBytes());
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Tor;
|
|
||||||
import com.subgraph.orchid.TorInitializationListener;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class TorInitializationTracker {
|
|
||||||
private final static Logger logger = Logger.getLogger(TorInitializationTracker.class.getName());
|
|
||||||
private final static Map<Integer, String> messageMap = new HashMap<Integer, String>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_STARTING, "Starting");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_CONN_DIR, "Connecting to directory server");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_HANDSHAKE_DIR, "Finishing handshake with directory server");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_ONEHOP_CREATE, "Establishing an encrypted directory connection");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_REQUESTING_STATUS, "Asking for network status consensus");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_LOADING_STATUS, "Loading network status consensus");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_REQUESTING_KEYS, "Asking for authority key certs");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_LOADING_KEYS, "Loading authority key certs");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, "Asking for relay descriptors");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, "Loading relay descriptors");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_CONN_OR, "Connecting to the Tor network");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_HANDSHAKE_OR, "Finished Handshake with first hop");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_CIRCUIT_CREATE, "Establishing a Tor circuit");
|
|
||||||
messageMap.put(Tor.BOOTSTRAP_STATUS_DONE, "Done");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<TorInitializationListener> listeners = new ArrayList<TorInitializationListener>();
|
|
||||||
|
|
||||||
private final Object stateLock = new Object();
|
|
||||||
private int bootstrapState = Tor.BOOTSTRAP_STATUS_STARTING;
|
|
||||||
|
|
||||||
|
|
||||||
public void addListener(TorInitializationListener listener) {
|
|
||||||
synchronized(listeners) {
|
|
||||||
if(!listeners.contains(listener)) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeListener(TorInitializationListener listener) {
|
|
||||||
synchronized(listeners) {
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBootstrapState() {
|
|
||||||
return bootstrapState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
synchronized (stateLock) {
|
|
||||||
bootstrapState = Tor.BOOTSTRAP_STATUS_STARTING;
|
|
||||||
notifyListeners(Tor.BOOTSTRAP_STATUS_STARTING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void notifyEvent(int eventCode) {
|
|
||||||
synchronized(stateLock) {
|
|
||||||
if(eventCode <= bootstrapState || eventCode > 100) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bootstrapState = eventCode;
|
|
||||||
notifyListeners(eventCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyListeners(int code) {
|
|
||||||
final String message = getMessageForCode(code);
|
|
||||||
for(TorInitializationListener listener: getListeners()) {
|
|
||||||
try {
|
|
||||||
listener.initializationProgress(message, code);
|
|
||||||
if(code >= 100) {
|
|
||||||
listener.initializationCompleted();
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
logger.log(Level.SEVERE, "Exception occurred in TorInitializationListener callback: "+ e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getMessageForCode(int code) {
|
|
||||||
if(messageMap.containsKey(code)) {
|
|
||||||
return messageMap.get(code);
|
|
||||||
} else {
|
|
||||||
return "Unknown state";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TorInitializationListener> getListeners() {
|
|
||||||
synchronized (listeners) {
|
|
||||||
return new ArrayList<TorInitializationListener>(listeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,228 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.Stream;
|
|
||||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
|
||||||
import com.subgraph.orchid.misc.GuardedBy;
|
|
||||||
import com.subgraph.orchid.misc.ThreadSafe;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
public class TorInputStream extends InputStream {
|
|
||||||
|
|
||||||
private final static RelayCell CLOSE_SENTINEL = new RelayCellImpl(null, 0, 0, 0);
|
|
||||||
private final static ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
|
|
||||||
|
|
||||||
private final Stream stream;
|
|
||||||
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
/** Queue of RelayCells that have been received on this stream */
|
|
||||||
@GuardedBy("lock") private final Queue<RelayCell> incomingCells;
|
|
||||||
|
|
||||||
/** Number of unread data bytes in current buffer and in RELAY_DATA cells on queue */
|
|
||||||
@GuardedBy("lock") private int availableBytes;
|
|
||||||
|
|
||||||
/** Total number of data bytes received in RELAY_DATA cells on this stream */
|
|
||||||
@GuardedBy("lock") private long bytesReceived;
|
|
||||||
|
|
||||||
/** Bytes of data from the RELAY_DATA cell currently being consumed */
|
|
||||||
@GuardedBy("lock") private ByteBuffer currentBuffer;
|
|
||||||
|
|
||||||
/** Set when a RELAY_END cell is received */
|
|
||||||
@GuardedBy("lock") private boolean isEOF;
|
|
||||||
|
|
||||||
/** Set when close() is called on this stream */
|
|
||||||
@GuardedBy("lock") private boolean isClosed;
|
|
||||||
|
|
||||||
TorInputStream(Stream stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
this.incomingCells = new LinkedList<RelayCell>();
|
|
||||||
this.currentBuffer = EMPTY_BUFFER;
|
|
||||||
}
|
|
||||||
|
|
||||||
long getBytesReceived() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return bytesReceived;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if(isClosed) {
|
|
||||||
throw new IOException("Stream closed");
|
|
||||||
}
|
|
||||||
refillBufferIfNeeded();
|
|
||||||
if(isEOF) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
availableBytes -= 1;
|
|
||||||
return currentBuffer.get() & 0xFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int read(byte[] b) throws IOException {
|
|
||||||
return read(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if(isClosed) {
|
|
||||||
throw new IOException("Stream closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
checkReadArguments(b, off, len);
|
|
||||||
|
|
||||||
if(len == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
refillBufferIfNeeded();
|
|
||||||
if(isEOF) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesRead = 0;
|
|
||||||
int bytesRemaining = len;
|
|
||||||
|
|
||||||
while(bytesRemaining > 0 && !isEOF) {
|
|
||||||
refillBufferIfNeeded();
|
|
||||||
bytesRead += readFromCurrentBuffer(b, off + bytesRead, len - bytesRead);
|
|
||||||
bytesRemaining = len - bytesRead;
|
|
||||||
if(availableBytes == 0) {
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private int readFromCurrentBuffer(byte[] b, int off, int len) {
|
|
||||||
final int readLength = (currentBuffer.remaining() >= len) ? (len) : (currentBuffer.remaining());
|
|
||||||
currentBuffer.get(b, off, readLength);
|
|
||||||
availableBytes -= readLength;
|
|
||||||
return readLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkReadArguments(byte[] b, int off, int len) {
|
|
||||||
if(b == null) {
|
|
||||||
throw new NullPointerException();
|
|
||||||
}
|
|
||||||
if( (off < 0) || (off >= b.length) || (len < 0) ||
|
|
||||||
((off + len) > b.length) || ((off + len) < 0)) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int available() {
|
|
||||||
synchronized(lock) {
|
|
||||||
return availableBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if(isClosed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isClosed = true;
|
|
||||||
|
|
||||||
incomingCells.add(CLOSE_SENTINEL);
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addEndCell(RelayCell cell) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if(isClosed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
incomingCells.add(cell);
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInputCell(RelayCell cell) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if(isClosed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
incomingCells.add(cell);
|
|
||||||
bytesReceived += cell.cellBytesRemaining();
|
|
||||||
availableBytes += cell.cellBytesRemaining();
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
// When this method (or fillBuffer()) returns either isEOF is set or currentBuffer has at least one byte to read
|
|
||||||
private void refillBufferIfNeeded() throws IOException {
|
|
||||||
if(!isEOF) {
|
|
||||||
if(currentBuffer.hasRemaining()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fillBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private void fillBuffer() throws IOException {
|
|
||||||
while(true) {
|
|
||||||
processIncomingCell(getNextCell());
|
|
||||||
if(isEOF || currentBuffer.hasRemaining()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private void processIncomingCell(RelayCell nextCell) throws IOException {
|
|
||||||
if(isClosed || nextCell == CLOSE_SENTINEL) {
|
|
||||||
throw new IOException("Input stream closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(nextCell.getRelayCommand()) {
|
|
||||||
case RelayCell.RELAY_DATA:
|
|
||||||
currentBuffer = nextCell.getPayloadBuffer();
|
|
||||||
break;
|
|
||||||
case RelayCell.RELAY_END:
|
|
||||||
currentBuffer = EMPTY_BUFFER;
|
|
||||||
isEOF = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IOException("Unexpected RelayCell command type in TorInputStream queue: "+ nextCell.getRelayCommand());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private RelayCell getNextCell() throws IOException {
|
|
||||||
try {
|
|
||||||
while(incomingCells.isEmpty()) {
|
|
||||||
lock.wait();
|
|
||||||
}
|
|
||||||
return incomingCells.remove();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new IOException("Read interrupted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int unflushedCellCount() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return incomingCells.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "TorInputStream stream="+ stream.getStreamId() +" node="+ stream.getTargetNode();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
|
||||||
|
|
||||||
public class TorOutputStream extends OutputStream {
|
|
||||||
|
|
||||||
private final StreamImpl stream;
|
|
||||||
private RelayCell currentOutputCell;
|
|
||||||
private volatile boolean isClosed;
|
|
||||||
private long bytesSent;
|
|
||||||
|
|
||||||
TorOutputStream(StreamImpl stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
this.bytesSent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void flushCurrentOutputCell() {
|
|
||||||
if(currentOutputCell != null && currentOutputCell.cellBytesConsumed() > RelayCell.HEADER_SIZE) {
|
|
||||||
stream.waitForSendWindowAndDecrement();
|
|
||||||
stream.getCircuit().sendRelayCell(currentOutputCell);
|
|
||||||
bytesSent += (currentOutputCell.cellBytesConsumed() - RelayCell.HEADER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentOutputCell = new RelayCellImpl(stream.getTargetNode(), stream.getCircuit().getCircuitId(),
|
|
||||||
stream.getStreamId(), RelayCell.RELAY_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
long getBytesSent() {
|
|
||||||
return bytesSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void write(int b) throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
if(currentOutputCell == null || currentOutputCell.cellBytesRemaining() == 0)
|
|
||||||
flushCurrentOutputCell();
|
|
||||||
currentOutputCell.putByte(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void write(byte[] data, int offset, int length) throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
if(currentOutputCell == null || currentOutputCell.cellBytesRemaining() == 0)
|
|
||||||
flushCurrentOutputCell();
|
|
||||||
|
|
||||||
while(length > 0) {
|
|
||||||
if(length < currentOutputCell.cellBytesRemaining()) {
|
|
||||||
currentOutputCell.putByteArray(data, offset, length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int writeCount = currentOutputCell.cellBytesRemaining();
|
|
||||||
currentOutputCell.putByteArray(data, offset, writeCount);
|
|
||||||
flushCurrentOutputCell();
|
|
||||||
offset += writeCount;
|
|
||||||
length -= writeCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkOpen() throws IOException {
|
|
||||||
if(isClosed)
|
|
||||||
throw new IOException("Output stream is closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void flush() {
|
|
||||||
if(isClosed)
|
|
||||||
return;
|
|
||||||
flushCurrentOutputCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void close() {
|
|
||||||
if(isClosed)
|
|
||||||
return;
|
|
||||||
flush();
|
|
||||||
isClosed = true;
|
|
||||||
currentOutputCell = null;
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "TorOutputStream stream="+ stream.getStreamId() +" node="+ stream.getTargetNode();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,215 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.cells;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
|
|
||||||
public class CellImpl implements Cell {
|
|
||||||
|
|
||||||
public static CellImpl createCell(int circuitId, int command) {
|
|
||||||
return new CellImpl(circuitId, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CellImpl createVarCell(int circuitId, int command, int payloadLength) {
|
|
||||||
return new CellImpl(circuitId, command, payloadLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CellImpl readFromInputStream(InputStream input) throws IOException {
|
|
||||||
final ByteBuffer header = readHeaderFromInputStream(input);
|
|
||||||
final int circuitId = header.getShort() & 0xFFFF;
|
|
||||||
final int command = header.get() & 0xFF;
|
|
||||||
|
|
||||||
if(command == VERSIONS || command > 127) {
|
|
||||||
return readVarCell(circuitId, command, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
final CellImpl cell = new CellImpl(circuitId, command);
|
|
||||||
readAll(input, cell.getCellBytes(), CELL_HEADER_LEN, CELL_PAYLOAD_LEN);
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ByteBuffer readHeaderFromInputStream(InputStream input) throws IOException {
|
|
||||||
final byte[] cellHeader = new byte[CELL_HEADER_LEN];
|
|
||||||
readAll(input, cellHeader);
|
|
||||||
return ByteBuffer.wrap(cellHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CellImpl readVarCell(int circuitId, int command, InputStream input) throws IOException {
|
|
||||||
final byte[] lengthField = new byte[2];
|
|
||||||
readAll(input, lengthField);
|
|
||||||
final int length = ((lengthField[0] & 0xFF) << 8) | (lengthField[1] & 0xFF);
|
|
||||||
CellImpl cell = new CellImpl(circuitId, command, length);
|
|
||||||
readAll(input, cell.getCellBytes(), CELL_VAR_HEADER_LEN, length);
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void readAll(InputStream input, byte[] buffer) throws IOException {
|
|
||||||
readAll(input, buffer, 0, buffer.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void readAll(InputStream input, byte[] buffer, int offset, int length) throws IOException {
|
|
||||||
int bytesRead = 0;
|
|
||||||
while(bytesRead < length) {
|
|
||||||
final int n = input.read(buffer, offset + bytesRead, length - bytesRead);
|
|
||||||
if(n == -1)
|
|
||||||
throw new EOFException();
|
|
||||||
bytesRead += n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int circuitId;
|
|
||||||
private final int command;
|
|
||||||
protected final ByteBuffer cellBuffer;
|
|
||||||
|
|
||||||
/* Variable length cell constructor (ie: VERSIONS cells only) */
|
|
||||||
private CellImpl(int circuitId, int command, int payloadLength) {
|
|
||||||
this.circuitId = circuitId;
|
|
||||||
this.command = command;
|
|
||||||
this.cellBuffer = ByteBuffer.wrap(new byte[CELL_VAR_HEADER_LEN + payloadLength]);
|
|
||||||
cellBuffer.putShort((short)circuitId);
|
|
||||||
cellBuffer.put((byte)command);
|
|
||||||
cellBuffer.putShort((short) payloadLength);
|
|
||||||
cellBuffer.mark();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fixed length cell constructor */
|
|
||||||
protected CellImpl(int circuitId, int command) {
|
|
||||||
this.circuitId = circuitId;
|
|
||||||
this.command = command;
|
|
||||||
this.cellBuffer = ByteBuffer.wrap(new byte[CELL_LEN]);
|
|
||||||
cellBuffer.putShort((short) circuitId);
|
|
||||||
cellBuffer.put((byte) command);
|
|
||||||
cellBuffer.mark();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CellImpl(byte[] rawCell) {
|
|
||||||
this.cellBuffer = ByteBuffer.wrap(rawCell);
|
|
||||||
this.circuitId = cellBuffer.getShort() & 0xFFFF;
|
|
||||||
this.command = cellBuffer.get() & 0xFF;
|
|
||||||
cellBuffer.mark();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCircuitId() {
|
|
||||||
return circuitId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCommand() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetToPayload() {
|
|
||||||
cellBuffer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getByte() {
|
|
||||||
return cellBuffer.get() & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getByteAt(int index) {
|
|
||||||
return cellBuffer.get(index) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getShort() {
|
|
||||||
return cellBuffer.getShort() & 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInt() {
|
|
||||||
return cellBuffer.getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getShortAt(int index) {
|
|
||||||
return cellBuffer.getShort(index) & 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getByteArray(byte[] buffer) {
|
|
||||||
cellBuffer.get(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int cellBytesConsumed() {
|
|
||||||
return cellBuffer.position();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int cellBytesRemaining() {
|
|
||||||
return cellBuffer.remaining();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putByte(int value) {
|
|
||||||
cellBuffer.put((byte) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putByteAt(int index, int value) {
|
|
||||||
cellBuffer.put(index, (byte) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putShort(int value) {
|
|
||||||
cellBuffer.putShort((short) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putShortAt(int index, int value) {
|
|
||||||
cellBuffer.putShort(index, (short) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putInt(int value) {
|
|
||||||
cellBuffer.putInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putString(String string) {
|
|
||||||
final byte[] bytes = new byte[string.length() + 1];
|
|
||||||
for(int i = 0; i < string.length(); i++)
|
|
||||||
bytes[i] = (byte) string.charAt(i);
|
|
||||||
putByteArray(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putByteArray(byte[] data) {
|
|
||||||
cellBuffer.put(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putByteArray(byte[] data, int offset, int length) {
|
|
||||||
cellBuffer.put(data, offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getCellBytes() {
|
|
||||||
return cellBuffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "Cell: circuit_id="+ circuitId +" command="+ command +" payload_len="+ cellBuffer.position();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String errorToDescription(int errorCode) {
|
|
||||||
switch(errorCode) {
|
|
||||||
case ERROR_NONE:
|
|
||||||
return "No error reason given";
|
|
||||||
case ERROR_PROTOCOL:
|
|
||||||
return "Tor protocol violation";
|
|
||||||
case ERROR_INTERNAL:
|
|
||||||
return "Internal error";
|
|
||||||
case ERROR_REQUESTED:
|
|
||||||
return "Response to a TRUNCATE command sent from client";
|
|
||||||
case ERROR_HIBERNATING:
|
|
||||||
return "Not currently operating; trying to save bandwidth.";
|
|
||||||
case ERROR_RESOURCELIMIT:
|
|
||||||
return "Out of memory, sockets, or circuit IDs.";
|
|
||||||
case ERROR_CONNECTFAILED:
|
|
||||||
return "Unable to reach server.";
|
|
||||||
case ERROR_OR_IDENTITY:
|
|
||||||
return "Connected to server, but its OR identity was not as expected.";
|
|
||||||
case ERROR_OR_CONN_CLOSED:
|
|
||||||
return "The OR connection that was carrying this circuit died.";
|
|
||||||
case ERROR_FINISHED:
|
|
||||||
return "The circuit has expired for being dirty or old.";
|
|
||||||
case ERROR_TIMEOUT:
|
|
||||||
return "Circuit construction took too long.";
|
|
||||||
case ERROR_DESTROYED:
|
|
||||||
return "The circuit was destroyed without client TRUNCATE";
|
|
||||||
case ERROR_NOSUCHSERVICE:
|
|
||||||
return "Request for unknown hidden service";
|
|
||||||
default:
|
|
||||||
return "Error code "+ errorCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.cells;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.Cell;
|
|
||||||
import com.subgraph.orchid.CircuitNode;
|
|
||||||
import com.subgraph.orchid.RelayCell;
|
|
||||||
import com.subgraph.orchid.TorException;
|
|
||||||
|
|
||||||
public class RelayCellImpl extends CellImpl implements RelayCell {
|
|
||||||
|
|
||||||
public static RelayCell createFromCell(CircuitNode node, Cell cell) {
|
|
||||||
if(cell.getCommand() != Cell.RELAY)
|
|
||||||
throw new TorException("Attempted to create RelayCell from Cell type: "+ cell.getCommand());
|
|
||||||
return new RelayCellImpl(node, cell.getCellBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int streamId;
|
|
||||||
private final int relayCommand;
|
|
||||||
private final CircuitNode circuitNode;
|
|
||||||
private final boolean isOutgoing;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The payload of each unencrypted RELAY cell consists of:
|
|
||||||
* Relay command [1 byte]
|
|
||||||
* 'Recognized' [2 bytes]
|
|
||||||
* StreamID [2 bytes]
|
|
||||||
* Digest [4 bytes]
|
|
||||||
* Length [2 bytes]
|
|
||||||
* Data [CELL_LEN-14 bytes]
|
|
||||||
*/
|
|
||||||
|
|
||||||
public RelayCellImpl(CircuitNode node, int circuit, int stream, int relayCommand) {
|
|
||||||
this(node, circuit, stream, relayCommand, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RelayCellImpl(CircuitNode node, int circuit, int stream, int relayCommand, boolean isRelayEarly) {
|
|
||||||
super(circuit, (isRelayEarly) ? (Cell.RELAY_EARLY) : (Cell.RELAY));
|
|
||||||
this.circuitNode = node;
|
|
||||||
this.relayCommand = relayCommand;
|
|
||||||
this.streamId = stream;
|
|
||||||
this.isOutgoing = true;
|
|
||||||
putByte(relayCommand); // Command
|
|
||||||
putShort(0); // 'Recognized'
|
|
||||||
putShort(stream); // Stream
|
|
||||||
putInt(0); // Digest
|
|
||||||
putShort(0); // Length
|
|
||||||
}
|
|
||||||
|
|
||||||
private RelayCellImpl(CircuitNode node, byte[] rawCell) {
|
|
||||||
super(rawCell);
|
|
||||||
this.circuitNode = node;
|
|
||||||
this.relayCommand = getByte();
|
|
||||||
getShort();
|
|
||||||
this.streamId = getShort();
|
|
||||||
this.isOutgoing = false;
|
|
||||||
getInt();
|
|
||||||
int payloadLength = getShort();
|
|
||||||
cellBuffer.mark(); // End of header
|
|
||||||
if(RelayCell.HEADER_SIZE + payloadLength > rawCell.length)
|
|
||||||
throw new TorException("Header length field exceeds total size of cell");
|
|
||||||
cellBuffer.limit(RelayCell.HEADER_SIZE + payloadLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRelayCommand() {
|
|
||||||
return relayCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLength() {
|
|
||||||
putShortAt(LENGTH_OFFSET, (short) (cellBytesConsumed() - HEADER_SIZE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDigest(byte[] digest) {
|
|
||||||
for(int i = 0; i < 4; i++)
|
|
||||||
putByteAt(DIGEST_OFFSET + i, digest[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByteBuffer getPayloadBuffer() {
|
|
||||||
final ByteBuffer dup = cellBuffer.duplicate();
|
|
||||||
dup.reset();
|
|
||||||
return dup.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircuitNode getCircuitNode() {
|
|
||||||
return circuitNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
if(isOutgoing)
|
|
||||||
return "["+ commandToDescription(relayCommand) +" stream="+ streamId +" payload_len="+ (cellBytesConsumed() - HEADER_SIZE) +" dest="+ circuitNode +"]";
|
|
||||||
else
|
|
||||||
return "["+ commandToString() + " stream="+ streamId + " payload_len="+ cellBuffer.remaining() +" source="+ circuitNode + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String commandToString() {
|
|
||||||
if(relayCommand == RELAY_TRUNCATED) {
|
|
||||||
final int code = getByteAt(HEADER_SIZE);
|
|
||||||
return commandToDescription(relayCommand) + " ("+ CellImpl.errorToDescription(code) +")";
|
|
||||||
} else if(relayCommand == RELAY_END) {
|
|
||||||
final int code = getByteAt(HEADER_SIZE);
|
|
||||||
return commandToDescription(relayCommand) +" ("+ reasonToDescription(code) +")";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return commandToDescription(relayCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String reasonToDescription(int reasonCode) {
|
|
||||||
switch(reasonCode) {
|
|
||||||
case REASON_MISC:
|
|
||||||
return "Unlisted reason";
|
|
||||||
case REASON_RESOLVEFAILED:
|
|
||||||
return "Couldn't look up hostname";
|
|
||||||
case REASON_CONNECTREFUSED:
|
|
||||||
return "Remote host refused connection";
|
|
||||||
case REASON_EXITPOLICY:
|
|
||||||
return "OR refuses to connect to host or port";
|
|
||||||
case REASON_DESTROY:
|
|
||||||
return "Circuit is being destroyed";
|
|
||||||
case REASON_DONE:
|
|
||||||
return "Anonymized TCP connection was closed";
|
|
||||||
case REASON_TIMEOUT:
|
|
||||||
return "Connection timed out, or OR timed out while connecting";
|
|
||||||
case REASON_HIBERNATING:
|
|
||||||
return "OR is temporarily hibernating";
|
|
||||||
case REASON_INTERNAL:
|
|
||||||
return "Internal error at the OR";
|
|
||||||
case REASON_RESOURCELIMIT:
|
|
||||||
return "OR has no resources to fulfill request";
|
|
||||||
case REASON_CONNRESET:
|
|
||||||
return "Connection was unexpectedly reset";
|
|
||||||
case REASON_TORPROTOCOL:
|
|
||||||
return "Tor protocol violation";
|
|
||||||
case REASON_NOTDIRECTORY:
|
|
||||||
return "Client sent RELAY_BEGIN_DIR to a non-directory server.";
|
|
||||||
default:
|
|
||||||
return "Reason code "+ reasonCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String commandToDescription(int command) {
|
|
||||||
switch(command) {
|
|
||||||
case RELAY_BEGIN:
|
|
||||||
return "RELAY_BEGIN";
|
|
||||||
case RELAY_DATA:
|
|
||||||
return "RELAY_DATA";
|
|
||||||
case RELAY_END:
|
|
||||||
return "RELAY_END";
|
|
||||||
case RELAY_CONNECTED:
|
|
||||||
return "RELAY_CONNECTED";
|
|
||||||
case RELAY_SENDME:
|
|
||||||
return "RELAY_SENDME";
|
|
||||||
case RELAY_EXTEND:
|
|
||||||
return "RELAY_EXTEND";
|
|
||||||
case RELAY_EXTENDED:
|
|
||||||
return "RELAY_EXTENDED";
|
|
||||||
case RELAY_TRUNCATE:
|
|
||||||
return "RELAY_TRUNCATE";
|
|
||||||
case RELAY_TRUNCATED:
|
|
||||||
return "RELAY_TRUNCATED";
|
|
||||||
case RELAY_DROP:
|
|
||||||
return "RELAY_DROP";
|
|
||||||
case RELAY_RESOLVE:
|
|
||||||
return "RELAY_RESOLVE";
|
|
||||||
case RELAY_RESOLVED:
|
|
||||||
return "RELAY_RESOLVED";
|
|
||||||
case RELAY_BEGIN_DIR:
|
|
||||||
return "RELAY_BEGIN_DIR";
|
|
||||||
case RELAY_EXTEND2:
|
|
||||||
return "RELAY_EXTEND2";
|
|
||||||
case RELAY_EXTENDED2:
|
|
||||||
return "RELAY_EXTENDED2";
|
|
||||||
default:
|
|
||||||
return "Relay command = "+ command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.guards;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.BridgeRouter;
|
|
||||||
import com.subgraph.orchid.Descriptor;
|
|
||||||
import com.subgraph.orchid.RouterDescriptor;
|
|
||||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.IPv4Address;
|
|
||||||
import com.subgraph.orchid.geoip.CountryCodeService;
|
|
||||||
|
|
||||||
public class BridgeRouterImpl implements BridgeRouter {
|
|
||||||
private final IPv4Address address;
|
|
||||||
private final int port;
|
|
||||||
|
|
||||||
private HexDigest identity;
|
|
||||||
private Descriptor descriptor;
|
|
||||||
|
|
||||||
private volatile String cachedCountryCode;
|
|
||||||
|
|
||||||
BridgeRouterImpl(IPv4Address address, int port) {
|
|
||||||
this.address = address;
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPv4Address getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HexDigest getIdentity() {
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIdentity(HexDigest identity) {
|
|
||||||
this.identity = identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescriptor(RouterDescriptor descriptor) {
|
|
||||||
this.descriptor = descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((address == null) ? 0 : address.hashCode());
|
|
||||||
result = prime * result + port;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BridgeRouterImpl other = (BridgeRouterImpl) obj;
|
|
||||||
if (address == null) {
|
|
||||||
if (other.address != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!address.equals(other.address)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (port != other.port) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNickname() {
|
|
||||||
return toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCountryCode() {
|
|
||||||
String cc = cachedCountryCode;
|
|
||||||
if(cc == null) {
|
|
||||||
cc = CountryCodeService.getInstance().getCountryCodeForAddress(getAddress());
|
|
||||||
cachedCountryCode = cc;
|
|
||||||
}
|
|
||||||
return cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOnionPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDirectoryPort() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorPublicKey getIdentityKey() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HexDigest getIdentityHash() {
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDescriptorDownloadable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public Descriptor getCurrentDescriptor() {
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HexDigest getDescriptorDigest() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HexDigest getMicrodescriptorDigest() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TorPublicKey getOnionKey() {
|
|
||||||
if(descriptor != null) {
|
|
||||||
return descriptor.getOnionKey();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getNTorOnionKey() {
|
|
||||||
if(descriptor != null) {
|
|
||||||
return descriptor.getNTorOnionKey();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasBandwidth() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEstimatedBandwidth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMeasuredBandwidth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getFamilyMembers() {
|
|
||||||
if(descriptor != null) {
|
|
||||||
return descriptor.getFamilyMembers();
|
|
||||||
} else {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAverageBandwidth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBurstBandwidth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getObservedBandwidth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHibernating() {
|
|
||||||
if(descriptor instanceof RouterDescriptor) {
|
|
||||||
return ((RouterDescriptor)descriptor).isHibernating();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBadExit() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPossibleGuard() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isExit() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFast() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHSDirectory() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean exitPolicyAccepts(IPv4Address address, int port) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean exitPolicyAccepts(int port) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "[Bridge "+ address + ":"+ port + "]";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.guards;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.BridgeRouter;
|
|
||||||
import com.subgraph.orchid.DirectoryDownloader;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.RouterDescriptor;
|
|
||||||
import com.subgraph.orchid.TorConfig;
|
|
||||||
import com.subgraph.orchid.config.TorConfigBridgeLine;
|
|
||||||
import com.subgraph.orchid.crypto.TorRandom;
|
|
||||||
import com.subgraph.orchid.directory.downloader.DirectoryRequestFailedException;
|
|
||||||
|
|
||||||
public class Bridges {
|
|
||||||
private static final Logger logger = Logger.getLogger(Bridges.class.getName());
|
|
||||||
|
|
||||||
private class DescriptorDownloader implements Runnable {
|
|
||||||
|
|
||||||
private final BridgeRouterImpl target;
|
|
||||||
|
|
||||||
DescriptorDownloader(BridgeRouterImpl target) {
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
downloadDescriptor();
|
|
||||||
} finally {
|
|
||||||
decrementOutstandingTasks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadDescriptor() {
|
|
||||||
logger.fine("Downloading descriptor for bridge: "+ target);
|
|
||||||
try {
|
|
||||||
final RouterDescriptor descriptor = directoryDownloader.downloadBridgeDescriptor(target);
|
|
||||||
if(descriptor != null) {
|
|
||||||
logger.fine("Descriptor received for bridge "+ target +". Adding to list of usable bridges");
|
|
||||||
target.setDescriptor(descriptor);
|
|
||||||
synchronized(lock) {
|
|
||||||
bridgeRouters.add(target);
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (DirectoryRequestFailedException e) {
|
|
||||||
logger.warning("Failed to download descriptor for bridge: "+ e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decrementOutstandingTasks() {
|
|
||||||
if(outstandingDownloadTasks.decrementAndGet() == 0) {
|
|
||||||
logger.fine("Initial descriptor fetch complete");
|
|
||||||
synchronized(lock) {
|
|
||||||
bridgesInitialized = true;
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TorConfig config;
|
|
||||||
private final DirectoryDownloader directoryDownloader;
|
|
||||||
|
|
||||||
private final Set<BridgeRouterImpl> bridgeRouters;
|
|
||||||
private final TorRandom random;
|
|
||||||
private final Object lock;
|
|
||||||
|
|
||||||
/** Initialization started */
|
|
||||||
private boolean bridgesInitializing;
|
|
||||||
/** Initialization completed */
|
|
||||||
private boolean bridgesInitialized;
|
|
||||||
|
|
||||||
private AtomicInteger outstandingDownloadTasks;
|
|
||||||
|
|
||||||
Bridges(TorConfig config, DirectoryDownloader directoryDownloader) {
|
|
||||||
this.config = config;
|
|
||||||
this.directoryDownloader = directoryDownloader;
|
|
||||||
this.bridgeRouters = new HashSet<BridgeRouterImpl>();
|
|
||||||
this.random = new TorRandom();
|
|
||||||
this.lock = new Object();
|
|
||||||
this.outstandingDownloadTasks = new AtomicInteger();
|
|
||||||
}
|
|
||||||
|
|
||||||
BridgeRouter chooseRandomBridge(Set<Router> excluded) throws InterruptedException {
|
|
||||||
|
|
||||||
synchronized(lock) {
|
|
||||||
if(!bridgesInitialized && !bridgesInitializing) {
|
|
||||||
initializeBridges();
|
|
||||||
}
|
|
||||||
while(!bridgesInitialized && !hasCandidates(excluded)) {
|
|
||||||
lock.wait();
|
|
||||||
}
|
|
||||||
final List<BridgeRouter> candidates = getCandidates(excluded);
|
|
||||||
if(candidates.isEmpty()) {
|
|
||||||
logger.warning("Bridges enabled but no usable bridges configured");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return candidates.get(random.nextInt(candidates.size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasCandidates(Set<Router> excluded) {
|
|
||||||
return !(getCandidates(excluded).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<BridgeRouter> getCandidates(Set<Router> excluded) {
|
|
||||||
if(bridgeRouters.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
final List<BridgeRouter> candidates = new ArrayList<BridgeRouter>(bridgeRouters.size());
|
|
||||||
for(BridgeRouter br: bridgeRouters) {
|
|
||||||
if(!excluded.contains(br)) {
|
|
||||||
candidates.add(br);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeBridges() {
|
|
||||||
logger.fine("Initializing bridges...");
|
|
||||||
synchronized(lock) {
|
|
||||||
if(bridgesInitializing || bridgesInitialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(directoryDownloader == null) {
|
|
||||||
throw new IllegalStateException("Cannot download bridge descriptors because DirectoryDownload instance not initialized");
|
|
||||||
}
|
|
||||||
bridgesInitializing = true;
|
|
||||||
startAllDownloadTasks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Runnable> createDownloadTasks() {
|
|
||||||
final List<Runnable> tasks = new ArrayList<Runnable>();
|
|
||||||
for(TorConfigBridgeLine line: config.getBridges()) {
|
|
||||||
tasks.add(new DescriptorDownloader(createBridgeFromLine(line)));
|
|
||||||
}
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startAllDownloadTasks() {
|
|
||||||
final List<Runnable> tasks = createDownloadTasks();
|
|
||||||
outstandingDownloadTasks.set(tasks.size());
|
|
||||||
for(Runnable r: tasks) {
|
|
||||||
final Thread thread = new Thread(r);
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BridgeRouterImpl createBridgeFromLine(TorConfigBridgeLine line) {
|
|
||||||
final BridgeRouterImpl bridge = new BridgeRouterImpl(line.getAddress(), line.getPort());
|
|
||||||
if(line.getFingerprint() != null) {
|
|
||||||
bridge.setIdentity(line.getFingerprint());
|
|
||||||
}
|
|
||||||
return bridge;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,305 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.guards;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.ConnectionCache;
|
|
||||||
import com.subgraph.orchid.Directory;
|
|
||||||
import com.subgraph.orchid.DirectoryDownloader;
|
|
||||||
import com.subgraph.orchid.GuardEntry;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
import com.subgraph.orchid.Threading;
|
|
||||||
import com.subgraph.orchid.TorConfig;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitNodeChooser;
|
|
||||||
import com.subgraph.orchid.circuits.path.CircuitNodeChooser.WeightRule;
|
|
||||||
import com.subgraph.orchid.circuits.path.RouterFilter;
|
|
||||||
import com.subgraph.orchid.crypto.TorRandom;
|
|
||||||
|
|
||||||
public class EntryGuards {
|
|
||||||
private final static Logger logger = Logger.getLogger(EntryGuards.class.getName());
|
|
||||||
|
|
||||||
private final static int MIN_USABLE_GUARDS = 2;
|
|
||||||
private final static int NUM_ENTRY_GUARDS = 3;
|
|
||||||
|
|
||||||
private final TorConfig config;
|
|
||||||
private final TorRandom random;
|
|
||||||
private final CircuitNodeChooser nodeChooser;
|
|
||||||
private final ConnectionCache connectionCache;
|
|
||||||
private final Directory directory;
|
|
||||||
private final Set<GuardEntry> pendingProbes;
|
|
||||||
|
|
||||||
private final Bridges bridges;
|
|
||||||
private final Object lock;
|
|
||||||
private final Executor executor;
|
|
||||||
|
|
||||||
public EntryGuards(TorConfig config, ConnectionCache connectionCache, DirectoryDownloader directoryDownloader, Directory directory) {
|
|
||||||
this.config = config;
|
|
||||||
this.random = new TorRandom();
|
|
||||||
this.nodeChooser = new CircuitNodeChooser(config, directory);
|
|
||||||
this.connectionCache = connectionCache;
|
|
||||||
this.directory = directory;
|
|
||||||
this.pendingProbes = new HashSet<GuardEntry>();
|
|
||||||
this.bridges = new Bridges(config, directoryDownloader);
|
|
||||||
this.lock = new Object();
|
|
||||||
this.executor = Threading.newPool("EntryGuards worker");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUsingBridges() {
|
|
||||||
return config.getUseBridges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Router chooseRandomGuard(Set<Router> excluded) throws InterruptedException {
|
|
||||||
if(config.getUseBridges()) {
|
|
||||||
return bridges.chooseRandomBridge(excluded);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* path-spec 5.
|
|
||||||
*
|
|
||||||
* When choosing the first hop of a circuit, Tor chooses at random from among the first
|
|
||||||
* NumEntryGuards (default 3) usable guards on the list. If there are not at least 2
|
|
||||||
* usable guards on the list, Tor adds routers until there are, or until there are no
|
|
||||||
* more usable routers to add.
|
|
||||||
*/
|
|
||||||
|
|
||||||
final List<Router> usableGuards = getMinimumUsableGuards(excluded, MIN_USABLE_GUARDS);
|
|
||||||
final int n = Math.min(usableGuards.size(), NUM_ENTRY_GUARDS);
|
|
||||||
return usableGuards.get(random.nextInt(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Router> getMinimumUsableGuards(Set<Router> excluded, int minSize) throws InterruptedException {
|
|
||||||
synchronized(lock) {
|
|
||||||
testStatusOfAllGuards();
|
|
||||||
while(true) {
|
|
||||||
List<Router> usableGuards = getUsableGuardRouters(excluded);
|
|
||||||
if(usableGuards.size() >= minSize) {
|
|
||||||
return usableGuards;
|
|
||||||
} else {
|
|
||||||
maybeChooseNew(usableGuards.size(), minSize, getExcludedForChooseNew(excluded, usableGuards));
|
|
||||||
}
|
|
||||||
lock.wait(5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void probeConnectionSucceeded(GuardEntry entry) {
|
|
||||||
synchronized (lock) {
|
|
||||||
pendingProbes.remove(entry);
|
|
||||||
if(entry.isAdded()) {
|
|
||||||
retestProbeSucceeded(entry);
|
|
||||||
} else {
|
|
||||||
initialProbeSucceeded(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void probeConnectionFailed(GuardEntry entry) {
|
|
||||||
synchronized (lock) {
|
|
||||||
pendingProbes.remove(entry);
|
|
||||||
if(entry.isAdded()) {
|
|
||||||
retestProbeFailed(entry);
|
|
||||||
}
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* all methods below called holding 'lock' */
|
|
||||||
|
|
||||||
private void retestProbeSucceeded(GuardEntry entry) {
|
|
||||||
entry.clearDownSince();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialProbeSucceeded(GuardEntry entry) {
|
|
||||||
logger.fine("Probe connection to "+ entry.getRouterForEntry() + " succeeded. Adding it as a new entry guard.");
|
|
||||||
directory.addGuardEntry(entry);
|
|
||||||
retestAllUnreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void retestProbeFailed(GuardEntry entry) {
|
|
||||||
entry.markAsDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* path-spec 5.
|
|
||||||
*
|
|
||||||
* Additionally, Tor retries unreachable guards the first time it adds a new
|
|
||||||
* guard to the list, since it is possible that the old guards were only marked
|
|
||||||
* as unreachable because the network was unreachable or down.
|
|
||||||
|
|
||||||
*/
|
|
||||||
private void retestAllUnreachable() {
|
|
||||||
for(GuardEntry e: directory.getGuardEntries()) {
|
|
||||||
if(e.getDownSince() != null) {
|
|
||||||
launchEntryProbe(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testStatusOfAllGuards() {
|
|
||||||
for(GuardEntry entry: directory.getGuardEntries()) {
|
|
||||||
if(isPermanentlyUnlisted(entry) || isExpired(entry)) {
|
|
||||||
directory.removeGuardEntry(entry);
|
|
||||||
} else if(needsUnreachableTest(entry)) {
|
|
||||||
launchEntryProbe(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Router> getUsableGuardRouters(Set<Router> excluded) {
|
|
||||||
List<Router> usableRouters = new ArrayList<Router>();
|
|
||||||
for(GuardEntry entry: directory.getGuardEntries()) {
|
|
||||||
addRouterIfUsableAndNotExcluded(entry, excluded, usableRouters);
|
|
||||||
}
|
|
||||||
return usableRouters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addRouterIfUsableAndNotExcluded(GuardEntry entry, Set<Router> excluded, List<Router> routers) {
|
|
||||||
if(entry.testCurrentlyUsable() && entry.getDownSince() == null) {
|
|
||||||
final Router r = entry.getRouterForEntry();
|
|
||||||
if(r != null && !excluded.contains(r)) {
|
|
||||||
routers.add(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<Router> getExcludedForChooseNew(Set<Router> excluded, List<Router> usable) {
|
|
||||||
final Set<Router> set = new HashSet<Router>();
|
|
||||||
set.addAll(excluded);
|
|
||||||
set.addAll(usable);
|
|
||||||
addPendingInitialConnections(set);
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addPendingInitialConnections(Set<Router> routerSet) {
|
|
||||||
for(GuardEntry entry: pendingProbes) {
|
|
||||||
if(!entry.isAdded()) {
|
|
||||||
Router r = entry.getRouterForEntry();
|
|
||||||
if(r != null) {
|
|
||||||
routerSet.add(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeChooseNew(int usableSize, int minSize, Set<Router> excluded) {
|
|
||||||
int sz = usableSize + countPendingInitialProbes();
|
|
||||||
while(sz < minSize) {
|
|
||||||
Router newGuard = chooseNewGuard(excluded);
|
|
||||||
if(newGuard == null) {
|
|
||||||
logger.warning("Need to add entry guards but no suitable guard routers are available");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.fine("Testing "+ newGuard + " as a new guard since we only have "+ usableSize + " usable guards");
|
|
||||||
final GuardEntry entry = directory.createGuardEntryFor(newGuard);
|
|
||||||
launchEntryProbe(entry);
|
|
||||||
sz += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int countPendingInitialProbes() {
|
|
||||||
int count = 0;
|
|
||||||
for(GuardEntry entry: pendingProbes) {
|
|
||||||
if(!entry.isAdded()) {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Router chooseNewGuard(final Set<Router> excluded) {
|
|
||||||
return nodeChooser.chooseRandomNode(WeightRule.WEIGHT_FOR_GUARD, new RouterFilter() {
|
|
||||||
public boolean filter(Router router) {
|
|
||||||
return router.isValid() && router.isPossibleGuard() && router.isRunning() && !excluded.contains(router);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void launchEntryProbe(GuardEntry entry) {
|
|
||||||
if(!entry.testCurrentlyUsable() || pendingProbes.contains(entry)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pendingProbes.add(entry);
|
|
||||||
executor.execute(new GuardProbeTask(connectionCache, this, entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* path-spec 5.
|
|
||||||
*
|
|
||||||
* If the guard is excluded because of its status in the networkstatuses for
|
|
||||||
* over 30 days, Tor removes it from the list entirely, preserving order.
|
|
||||||
*/
|
|
||||||
private boolean isPermanentlyUnlisted(GuardEntry entry) {
|
|
||||||
final Date unlistedSince = entry.getUnlistedSince();
|
|
||||||
if(unlistedSince == null || pendingProbes.contains(entry)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final Date now = new Date();
|
|
||||||
final long unlistedTime = now.getTime() - unlistedSince.getTime();
|
|
||||||
return unlistedTime > THIRTY_DAYS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Expire guards after 60 days since creation time.
|
|
||||||
*/
|
|
||||||
private boolean isExpired(GuardEntry entry) {
|
|
||||||
final Date createdAt = entry.getCreatedTime();
|
|
||||||
final Date now = new Date();
|
|
||||||
final long createdAgo = now.getTime() - createdAt.getTime();
|
|
||||||
return createdAgo > SIXTY_DAYS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean needsUnreachableTest(GuardEntry entry) {
|
|
||||||
final Date downSince = entry.getDownSince();
|
|
||||||
if(downSince == null || !entry.testCurrentlyUsable()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final Date now = new Date();
|
|
||||||
final Date lastConnect = entry.getLastConnectAttempt();
|
|
||||||
final long timeDown = now.getTime() - downSince.getTime();
|
|
||||||
final long timeSinceLastRetest = (lastConnect == null) ? timeDown : (now.getTime() - lastConnect.getTime());
|
|
||||||
|
|
||||||
return timeSinceLastRetest > getRetestInterval(timeDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static long ONE_HOUR = hoursToMs(1);
|
|
||||||
private final static long FOUR_HOURS = hoursToMs(4);
|
|
||||||
private final static long SIX_HOURS = hoursToMs(6);
|
|
||||||
private final static long EIGHTEEN_HOURS = hoursToMs(18);
|
|
||||||
private final static long THIRTYSIX_HOURS = hoursToMs(36);
|
|
||||||
private final static long THREE_DAYS = daysToMs(3);
|
|
||||||
private final static long SEVEN_DAYS = daysToMs(7);
|
|
||||||
private final static long THIRTY_DAYS = daysToMs(30);
|
|
||||||
private final static long SIXTY_DAYS = daysToMs(60);
|
|
||||||
|
|
||||||
private static long hoursToMs(long n) {
|
|
||||||
return TimeUnit.MILLISECONDS.convert(n, TimeUnit.HOURS);
|
|
||||||
}
|
|
||||||
private static long daysToMs(long n) {
|
|
||||||
return TimeUnit.MILLISECONDS.convert(n, TimeUnit.DAYS);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* path-spec 5.
|
|
||||||
*
|
|
||||||
* If Tor fails to connect to an otherwise usable guard, it retries
|
|
||||||
* periodically: every hour for six hours, every 4 hours for 3 days, every
|
|
||||||
* 18 hours for a week, and every 36 hours thereafter.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private long getRetestInterval(long timeDown) {
|
|
||||||
if(timeDown < SIX_HOURS) {
|
|
||||||
return ONE_HOUR;
|
|
||||||
} else if(timeDown < THREE_DAYS) {
|
|
||||||
return FOUR_HOURS;
|
|
||||||
} else if(timeDown < SEVEN_DAYS) {
|
|
||||||
return EIGHTEEN_HOURS;
|
|
||||||
} else {
|
|
||||||
return THIRTYSIX_HOURS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.guards;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.ConnectionCache;
|
|
||||||
import com.subgraph.orchid.ConnectionIOException;
|
|
||||||
import com.subgraph.orchid.GuardEntry;
|
|
||||||
import com.subgraph.orchid.Router;
|
|
||||||
|
|
||||||
public class GuardProbeTask implements Runnable{
|
|
||||||
private final static Logger logger = Logger.getLogger(GuardProbeTask.class.getName());
|
|
||||||
private final ConnectionCache connectionCache;
|
|
||||||
private final EntryGuards entryGuards;
|
|
||||||
private final GuardEntry entry;
|
|
||||||
|
|
||||||
public GuardProbeTask(ConnectionCache connectionCache, EntryGuards entryGuards, GuardEntry entry) {
|
|
||||||
this.connectionCache = connectionCache;
|
|
||||||
this.entryGuards = entryGuards;
|
|
||||||
this.entry = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
final Router router = entry.getRouterForEntry();
|
|
||||||
if(router == null) {
|
|
||||||
entryGuards.probeConnectionFailed(entry);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
connectionCache.getConnectionTo(router, false);
|
|
||||||
entryGuards.probeConnectionSucceeded(entry);
|
|
||||||
return;
|
|
||||||
} catch (ConnectionIOException e) {
|
|
||||||
logger.fine("IO exception probing entry guard "+ router + " : "+ e);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} catch(Exception e) {
|
|
||||||
logger.log(Level.WARNING, "Unexpected exception probing entry guard: "+ e, e);
|
|
||||||
}
|
|
||||||
entryGuards.probeConnectionFailed(entry);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.hs;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.TorParsingException;
|
|
||||||
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie.CookieType;
|
|
||||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
|
||||||
import com.subgraph.orchid.crypto.TorStreamCipher;
|
|
||||||
|
|
||||||
public class HSAuthentication {
|
|
||||||
private final static int BASIC_ID_LENGTH = 4;
|
|
||||||
private final HSDescriptorCookie cookie;
|
|
||||||
|
|
||||||
public HSAuthentication(HSDescriptorCookie cookie) {
|
|
||||||
this.cookie = cookie;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decryptIntroductionPoints(byte[] content) throws HSAuthenticationException {
|
|
||||||
final ByteBuffer buffer = ByteBuffer.wrap(content);
|
|
||||||
final int firstByte = buffer.get() & 0xFF;
|
|
||||||
if(firstByte == 1) {
|
|
||||||
return decryptIntroductionPointsWithBasicAuth(buffer);
|
|
||||||
} else if(firstByte == 2) {
|
|
||||||
return decryptIntroductionPointsWithStealthAuth(buffer);
|
|
||||||
} else {
|
|
||||||
throw new HSAuthenticationException("Introduction points section begins with unrecognized byte ("+ firstByte +")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BasicAuthEntry {
|
|
||||||
final byte[] id;
|
|
||||||
final byte[] skey;
|
|
||||||
BasicAuthEntry(byte[] id, byte[] skey) {
|
|
||||||
this.id = id;
|
|
||||||
this.skey = skey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BasicAuthEntry createEntry(ByteBuffer bb) {
|
|
||||||
final byte[] id = new byte[BASIC_ID_LENGTH];
|
|
||||||
final byte[] skey = new byte[TorStreamCipher.KEY_LEN];
|
|
||||||
bb.get(id);
|
|
||||||
bb.get(skey);
|
|
||||||
return new BasicAuthEntry(id, skey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] decryptIntroductionPointsWithBasicAuth(ByteBuffer buffer) throws HSAuthenticationException {
|
|
||||||
if(cookie == null || cookie.getType() != CookieType.COOKIE_BASIC) {
|
|
||||||
throw new TorParsingException("Introduction points encrypted with 'basic' authentication and no cookie available to decrypt");
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<BasicAuthEntry> entries = readBasicEntries(buffer);
|
|
||||||
final byte[] iv = readAuthIV(buffer);
|
|
||||||
final byte[] id = generateAuthId(iv);
|
|
||||||
final byte[] k = findKeyInAuthEntries(entries, id);
|
|
||||||
|
|
||||||
return decryptRemaining(buffer, k, iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<BasicAuthEntry> readBasicEntries(ByteBuffer b) {
|
|
||||||
final int blockCount = b.get() & 0xFF;
|
|
||||||
final int entryCount = blockCount * 16;
|
|
||||||
final List<BasicAuthEntry> entries = new ArrayList<BasicAuthEntry>(entryCount);
|
|
||||||
for(int i = 0; i < entryCount; i++) {
|
|
||||||
entries.add( createEntry(b) );
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private byte[] readAuthIV(ByteBuffer b) {
|
|
||||||
final byte[] iv = new byte[16];
|
|
||||||
b.get(iv);
|
|
||||||
return iv;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] generateAuthId(byte[] iv) {
|
|
||||||
final TorMessageDigest md = new TorMessageDigest();
|
|
||||||
md.update(cookie.getValue());
|
|
||||||
md.update(iv);
|
|
||||||
final byte[] digest = md.getDigestBytes();
|
|
||||||
final byte[] id = new byte[BASIC_ID_LENGTH];
|
|
||||||
System.arraycopy(digest, 0, id, 0, BASIC_ID_LENGTH);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] findKeyInAuthEntries(List<BasicAuthEntry> entries, byte[] id) throws HSAuthenticationException {
|
|
||||||
for(BasicAuthEntry e: entries) {
|
|
||||||
if(Arrays.equals(id, e.id)) {
|
|
||||||
return decryptAuthEntry(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new HSAuthenticationException("Could not find matching cookie id for basic authentication");
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] decryptAuthEntry(BasicAuthEntry entry) throws HSAuthenticationException {
|
|
||||||
TorStreamCipher cipher = TorStreamCipher.createFromKeyBytes(cookie.getValue());
|
|
||||||
cipher.encrypt(entry.skey);
|
|
||||||
return entry.skey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] decryptRemaining(ByteBuffer buffer, byte[] key, byte[] iv) {
|
|
||||||
TorStreamCipher streamCipher = TorStreamCipher.createFromKeyBytesWithIV(key, iv);
|
|
||||||
final byte[] remaining = new byte[buffer.remaining()];
|
|
||||||
buffer.get(remaining);
|
|
||||||
streamCipher.encrypt(remaining);
|
|
||||||
return remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] decryptIntroductionPointsWithStealthAuth(ByteBuffer buffer) {
|
|
||||||
if(cookie == null || cookie.getType() != CookieType.COOKIE_STEALTH) {
|
|
||||||
throw new TorParsingException("Introduction points encrypted with 'stealth' authentication and no cookie available to descrypt");
|
|
||||||
}
|
|
||||||
final byte[] iv = readAuthIV(buffer);
|
|
||||||
return decryptRemaining(buffer, cookie.getValue(), iv);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.hs;
|
|
||||||
|
|
||||||
public class HSAuthenticationException extends Exception {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
HSAuthenticationException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
HSAuthenticationException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.hs;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
|
||||||
import com.subgraph.orchid.crypto.TorRandom;
|
|
||||||
import com.subgraph.orchid.data.HexDigest;
|
|
||||||
import com.subgraph.orchid.data.Timestamp;
|
|
||||||
|
|
||||||
public class HSDescriptor {
|
|
||||||
private final static long MS_24_HOURS = (24 * 60 * 60 * 1000);
|
|
||||||
private final HiddenService hiddenService;
|
|
||||||
private HexDigest descriptorId;
|
|
||||||
private Timestamp publicationTime;
|
|
||||||
private HexDigest secretIdPart;
|
|
||||||
private TorPublicKey permanentKey;
|
|
||||||
private int[] protocolVersions;
|
|
||||||
private List<IntroductionPoint> introductionPoints;
|
|
||||||
|
|
||||||
public HSDescriptor(HiddenService hiddenService) {
|
|
||||||
this.hiddenService = hiddenService;
|
|
||||||
introductionPoints = new ArrayList<IntroductionPoint>();
|
|
||||||
}
|
|
||||||
|
|
||||||
HiddenService getHiddenService() {
|
|
||||||
return hiddenService;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPublicationTime(Timestamp ts) {
|
|
||||||
this.publicationTime = ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSecretIdPart(HexDigest secretIdPart) {
|
|
||||||
this.secretIdPart = secretIdPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDescriptorId(HexDigest descriptorId) {
|
|
||||||
this.descriptorId = descriptorId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPermanentKey(TorPublicKey permanentKey) {
|
|
||||||
this.permanentKey = permanentKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setProtocolVersions(int[] protocolVersions) {
|
|
||||||
this.protocolVersions = protocolVersions;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addIntroductionPoint(IntroductionPoint ip) {
|
|
||||||
introductionPoints.add(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
HexDigest getDescriptorId() {
|
|
||||||
return descriptorId;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getVersion() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
TorPublicKey getPermanentKey() {
|
|
||||||
return permanentKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
HexDigest getSecretIdPart() {
|
|
||||||
return secretIdPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timestamp getPublicationTime() {
|
|
||||||
return publicationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] getProtocolVersions() {
|
|
||||||
return protocolVersions;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isExpired() {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
final long then = publicationTime.getTime();
|
|
||||||
return (now - then) > MS_24_HOURS;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IntroductionPoint> getIntroductionPoints() {
|
|
||||||
return new ArrayList<IntroductionPoint>(introductionPoints);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IntroductionPoint> getShuffledIntroductionPoints() {
|
|
||||||
return shuffle(getIntroductionPoints());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<IntroductionPoint> shuffle(List<IntroductionPoint> list) {
|
|
||||||
final TorRandom r = new TorRandom();
|
|
||||||
final int sz = list.size();
|
|
||||||
for(int i = 0; i < sz; i++) {
|
|
||||||
swap(list, i, r.nextInt(sz));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void swap(List<IntroductionPoint> list, int a, int b) {
|
|
||||||
if(a == b) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final IntroductionPoint tmp = list.get(a);
|
|
||||||
list.set(a, list.get(b));
|
|
||||||
list.set(b, tmp);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.subgraph.orchid.circuits.hs;
|
|
||||||
|
|
||||||
public class HSDescriptorCookie {
|
|
||||||
|
|
||||||
public enum CookieType { COOKIE_BASIC, COOKIE_STEALTH };
|
|
||||||
|
|
||||||
private final CookieType type;
|
|
||||||
private final byte[] value;
|
|
||||||
|
|
||||||
public HSDescriptorCookie(CookieType type, byte[] value) {
|
|
||||||
this.type = type;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getAuthTypeByte() {
|
|
||||||
switch(type) {
|
|
||||||
case COOKIE_BASIC:
|
|
||||||
return 1;
|
|
||||||
case COOKIE_STEALTH:
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CookieType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user