/**
* OnionCoffee - Anonymous Communication through TOR Network
* Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package TorJava;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import TorJava.Common.Encoding;
import TorJava.Common.Encryption;
import TorJava.Common.Queue;
import TorJava.Common.TorException;
/**
* handles the functionality of creating circuits, given a certain route and
* buidling tcp-streams on top of them.
*
* @author Lexi Pimenidis
* @author Tobias Koelsch
* @author Andriy Panchenko
* @author Michael Koellejan
* @version unstable
*/
public class Circuit {
TLSConnection tls; // a pointer to the TLS-layer
Node[] route; // stores the route
int route_established; // nodes in the route, where the keys have been
// established
Queue queue; // used to receive incoming data
public HashMap<Integer, TCPStream> streams; // a list of all tcp-streams relayed through this
// circuit
HashSet<Object> streamHistory; // contains URLs, InetAddresse or z-part of HS URL
// of hosts used to make contact to (or for DNS query) with this Circuit
int established_streams = 0; // counts the number of established streams
ServiceDescriptor sd; // service descriptor in case if used for rendezvous
// point
HiddenServiceProperties myHSProperties; // pointer to hidden service
// properties, if circuit used for
// communication with introduction
// point
public int ID;
public boolean established; // set to true, if route is established
public boolean closed; // set to true, if no new streams are allowed
boolean destruct; // set to true, if circuit is closed and inactive and
// may be removed from all sets
Date created;
Date last_action; // last time, a cell was send that was not a padding
// cell
Date last_cell; // last time, a cell was send
int setupDuration; // time in ms it took to establish the circuit
int ranking; // ranking index of the circuit
int sum_streams_setup_delays; // duration of all streams' setup times
int stream_counter; // overall number of streams relayed thrue the circ
int stream_fails; // overall counter of failures in streams in this circuit
final int circuitLevelFlowControl = 1000;
final int circuitLevelFlowControlIncrement = 100;
QueueFlowControlHandler qhFC;
Directory dir;
FirstNodeHandler fnh;
Tor tor;
/**
* initiates a circuit. tries to rebuild the circuit for a limited number of
* times, if first attempt fails.
*
* @param fnh
* a pointer to the TLS-Connection to the first node
* @param dir
* a pointer to the directory, in case an alternative route is
* necessary
* @param sp
* some properties for the stream that is the reason for building
* the circuit (needed if the circuit is needed to ask the
* directory for a new route)
*
* @exception TorException
* @exception IOException
*/
Circuit(Tor tor,FirstNodeHandler fnh, Directory dir, TCPStreamProperties sp)
throws IOException, TorException, InterruptedException
{
// init variables
this.dir = dir;
this.fnh = fnh;
this.tor = tor;
closed = false;
established = false;
destruct = false;
sum_streams_setup_delays = 0;
stream_counter = 0;
stream_fails = 0;
ranking = -1; // unused circs have highest priority for selection
streams = new HashMap<Integer,TCPStream>();
streamHistory = new HashSet<Object>();
created = new Date();
last_action = created;
last_cell = created;
// get a new route
Server[] init = dir.createNewRoute(sp);
if (init==null) throw new TorException("Circuit: could not build route");
// try to build a circuit
long startSetupTime = System.currentTimeMillis();
for (int misses = 1;; ++misses) {
if (Thread.interrupted())
throw new InterruptedException();
try {
// attach circuit to TLS
Logger.logCircuit(Logger.VERBOSE, "Circuit: connecting to " + init[0].nickname + " (" + init[0].countryCode + ") over tls");
tls = fnh.get_connection(init[0]);
Logger.logCircuit(Logger.VERBOSE,
"Circuit: creating queue");
queue = new Queue(TorConfig.queueTimeoutCircuit);
// FIXME: Addition to circuits-list is quite hidden here.
Logger.logCircuit(Logger.VERBOSE,
"Circuit: assigning circId");
ID = tls.assign_circID(this);
route_established = 0;
// connect to entry point = init[0]
Logger.logCircuit(Logger.VERBOSE,
"Circuit: sending create cell to " + init[0].nickname);
route = new Node[init.length];
create(init[0]);
route_established = 1;
// extend route
for (int i = 1; i < init.length; ++i) {
Logger.logCircuit(Logger.VERBOSE, "Circuit: " + print() + " extending to " + init[i].nickname + " (" + init[i].countryCode + ")");
extend(i, init[i]);
route_established += 1;
}
// finished - success
break;
} catch (IOException e) { // some error occured during the
// creating of the circuit
if (closed)
throw new IOException("Circuit: " + print()
+ " closing during buildup");
Logger.logCircuit(Logger.INFO, "Circuit: " + print() + " IOException " + misses + " :" + e.getMessage());
e.printStackTrace();
if (misses >= TorConfig.reconnectCircuit)
throw e; // enough retries, exit
// build a new route over the hosts that are known to be
// working, punish failing host
init = dir.restoreCircuit(sp, init, route_established);
} catch (TorException e) { // some error occured during the
// creating of the circuit
if (closed)
throw new IOException("Circuit: " + print()
+ " closing during buildup");
Logger.logCircuit(Logger.INFO, "Circuit: " + print() + " TorException " + misses + " :" + e.getMessage());
e.printStackTrace();
if (misses >= TorConfig.reconnectCircuit)
throw e; // enough retries, exit
// build a new route over the hosts that are known to be
// working, punish failing host
init = dir.restoreCircuit(sp, init, route_established);
}
};
setupDuration = (int) (System.currentTimeMillis() - startSetupTime);
established = true;
Logger.logCircuit(Logger.INFO, "Circuit: " + print() + " established within " + setupDuration + " ms");
qhFC = new QueueFlowControlHandler(this,circuitLevelFlowControl,circuitLevelFlowControlIncrement);
queue.addHandler(qhFC);
// fire event
tor.fireEvent(new TorEvent(TorEvent.CIRCUIT_BUILD,this,"Circuit build " + print()));
}
/**
* does exactly that: - check introduce2 for validity and connect to
* rendevous-point
*/
boolean handleIntroduce2(CellRelay cell) throws TorException, IOException {
// parse intro-cell
if (cell.length<20)
throw new TorException("Circuit.handleIntroduce2: cannot parse content, cell is too short");
byte[] identifier = new byte[20];
System.arraycopy(cell.data,0,identifier,0,20);
if (!Encoding.arraysEqual(identifier,myHSProperties.getKeys().pubKeyHash))
throw new TorException("Circuit.handleIntroduce2: onion is for unknown key-pair");
byte[] onionData = new byte[cell.length-20];
System.arraycopy(cell.data,20,onionData,0,cell.length-20);
byte[] plain_intro2 = Encryption.asym_decrypt(myHSProperties.getKeys().priv, onionData);
// TODO: deal with introduce2 version 1 - 3
if (plain_intro2.length != 168) {
Logger.logCell( Logger.ERROR, "Circuit.handleIntroduce2: cannot parse content, possibly version is not yet supported");
return false;
}
Logger.logHiddenService(Logger.INFO,"Circuit.handleIntroduce2: received Intro2-Cell");
// extract content from decoded Intro2
byte[] rendezvous_bytes = new byte[20];
byte[] cookie = new byte[20];
byte[] dh_x = new byte[128];
System.arraycopy(plain_intro2, 0, rendezvous_bytes, 0, 20);
System.arraycopy(plain_intro2, 20, cookie, 0, 20);
System.arraycopy(plain_intro2, 40, dh_x, 0, 128);
// cut the padding NULs
int rendezvousLen = 0;
do{
if (rendezvous_bytes[rendezvousLen]==0) break;
++rendezvousLen;
} while(rendezvousLen<=20);
byte[] rendezvous_byte_cropped = new byte[rendezvousLen];
System.arraycopy(rendezvous_bytes,0,rendezvous_byte_cropped,0,rendezvousLen);
String rendezvous_str = new String(rendezvous_byte_cropped);
Server rendezvousServer = dir.getByName(rendezvous_str);
if (rendezvousServer == null)
throw new TorException("Circuit.handleIntroduce2: unknown rendezvous-point '"+rendezvous_str+"'");
// build circuit to rendezvous
TCPStreamProperties sp = new TCPStreamProperties();
sp.exitPolicyRequired = false;
sp.setCustomExitpoint(rendezvous_str);
// make new circ where the last node is intro point
for(int i=0;i<sp.connect_retries;++i) {
Circuit c2rendezvous = fnh.provideSuitableNewCircuit(sp);
// send dh_y
Node virtualNode = new Node(rendezvousServer,dh_x);
c2rendezvous.send_cell(new CellRelayRendevous1(c2rendezvous,cookie,virtualNode.dh_y_bytes,virtualNode.kh));
Logger.logHiddenService(Logger.INFO,"Circuit.handleIntroduce2: connected to rendezvous '"+rendezvous_str+"' over "+c2rendezvous.print());
// extend circuit to 'virtual' next point AFTER doing the rendezvous
c2rendezvous.myHSProperties = myHSProperties;
c2rendezvous.addNode(virtualNode);
return true;
}
return false;
}
/**
* sends a cell on this circuit. Incoming data is received by the class
* TLSDispatcher and then put in the queue.
*
* @param c
* the cell
* @exception IOException
* @see TLSDispatcher
*/
void send_cell(Cell c) throws IOException {
// update 'action'-timestamp, if not padding cell
last_cell = new Date();
if (!c.isTypePadding())
last_action = last_cell;
// send cell
try{
tls.send_cell(c);
}
catch(IOException e) {
// if there's an error in sending it can only mean that the
// circuit or the TLS-connection has severe problems. better close it
if (!closed) close(false);
throw e;
}
}
/** creates and send a padding-cell down the circuit */
void sendKeepAlive() {
try {
send_cell(new CellPadding(this));
} catch (IOException e) {
Logger.logCircuit(Logger.INFO, "Send KeepAlive IO error");
}
}
/**
* initiates circuit, sends CREATE-cell. throws an error, if something went
* wrong
*/
private void create(Server init) throws IOException, TorException {
// save starting point
route[0] = new Node(init);
// send create cell, set circID
send_cell(new CellCreate(this));
// wait for answer
Cell created = queue.receiveCell(Cell.CELL_CREATED);
// finish DH-exchange
route[0].finish_dh(created.payload);
}
/**
* Extends the existing circuit one more hop. sends an EXTEND-cell.
*/
private void extend(int i, Server next) throws IOException, TorException {
// save next node
route[i] = new Node(next);
// send extend cell
send_cell(new CellRelayExtend(this, route[i]));
// wait for extended-cell
CellRelay relay = queue.receiveRelayCell(CellRelay.RELAY_EXTENDED);
// finish DH-exchange
route[i].finish_dh(relay.data);
}
/**
* adds node as the last one in the route
*
* @param n
* new node that is appended to the existing route
*/
void addNode(Node n) {
// create a new array for route that is one entry larger
Node[] newRoute = new Node[route_established + 1];
System.arraycopy(route, 0, newRoute, 0, route_established);
// add new node
newRoute[route_established] = n;
++route_established;
// route to set new array
route = newRoute;
}
/** used to report that this stream cause some trouble (either by itself,
* or the remote server, or what ever)
*/
void reportStreamFailure(TCPStream stream) {
++stream_fails;
// if it's just too much, 'soft'-close this circuit
if ((stream_fails>TorConfig.circuitClosesOnFailures)&&(stream_fails > stream_counter*3/2)) {
if (!closed)
Logger.logCircuit(Logger.INFO,"Circuit.reportStreamFailure: closing due to failures "+print());
close(false);
}
// include in ranking
updateRanking();
}
/**
* find a free stream ID, other than zero
*/
private synchronized int getFreeStreamID() throws TorException {
for (int nr=1; nr<0x10000 ; ++nr) {
int id = (nr+stream_counter) & 0xffff;
if (id!=0)
if (!streams.containsKey(new Integer(id)))
return id;
}
throw new TorException("Circuit.getFreeStreamID: " + print()
+ " has no free stream-IDs");
}
/**
* find a free stream-id.
*/
int assign_streamID(TCPStream s) throws TorException {
if (closed)
throw new TorException("Circuit.assign_streamID: " + print()
+ "is closed");
// assign stream ID and memorize stream
s.ID = getFreeStreamID();
streams.put(new Integer(s.ID), s);
return s.ID;
}
/**
* registers a stream in the history to allow bundeling streams to the same
* connection in one circuit
*/
void registerStream(TCPStreamProperties sp) throws TorException {
++established_streams;
if (sp.addr != null)
streamHistory.add(sp.addr);
if (sp.hostname != null)
streamHistory.add(sp.hostname);
}
/**
* registers a stream in the history to allow bundeling streams to the same
* connection in one circuit, wrapped for setting stream creation time
*/
void registerStream(TCPStreamProperties sp, long streamSetupDuration) throws TorException {
sum_streams_setup_delays += streamSetupDuration;
stream_counter++;
updateRanking();
registerStream(sp);
}
/**
* updates the ranking of the circuit. takes into account: setup time of circuit and
* streams. but also number of stream-failures on this circuit;
*
*/
private void updateRanking(){
// do a weighted average of all setups. weighten the setup-time of the circuit more
// then those of the single streams. thus streams will be rather unimportant at the
// beginning, but play a more important role afterwards.
ranking = (TorConfig.CIRCUIT_ESTABLISHMENT_TIME_IMPACT * setupDuration + sum_streams_setup_delays)/
(stream_counter + TorConfig.CIRCUIT_ESTABLISHMENT_TIME_IMPACT);
// take into account number of stream-failures on this circuit
// DEPRECATED: just scale this up linearly
// ranking *= 1 + stream_fails;
// NEW: be cruel! there should be something severe for 3 or 4 errors!
ranking *= Math.exp(stream_fails);
}
/**
* closes the circuit. either soft (remaining connections are kept, no new
* one allowed) or hard (everything is closed immediatly, e.g. if a destroy
* cell is received)
*/
boolean close(boolean force) {
if (!closed){
Logger.logCircuit(Logger.INFO, "Circuit.close(): closing " + print());
// int numberOfNodeOccurances;
// remove servers from list of currently used nodes
for(int i=0;i<route_established;++i){
//numberOfNodeOccurances = ((Integer) FirstNodeHandler.currentlyUsedNodes.get(route[i].server.nickname)).intValue();
//FirstNodeHandler.currentlyUsedNodes.put(route[i].server.nickname, Math.max(0, --numberOfNodeOccurances));
}
}
tor.fireEvent(new TorEvent(TorEvent.CIRCUIT_CLOSED,this,"Circuit: closing "+print()));
// mark circuit closed. do nothing more, is soft close and streams are
// left
closed = true;
established = false;
// close all streams, removed closed streams
Iterator<Integer> si = streams.keySet().iterator();
while (si.hasNext()) {
try{
Object nick = si.next();
TCPStream stream = (TCPStream) streams.get(nick);
// check if stream is still allive
if (!stream.closed) {
if (force)
stream.close(force);
else {
// check if we can time-out the stream?
if (System.currentTimeMillis() - stream.last_cell.getTime() > 10 * TorConfig.queueTimeoutStreamBuildup * 1000) {
// ok, fsck it!
Logger.logCircuit(Logger.INFO, "Circuit.close(): forcing timeout on stream");
stream.close(true);
} else {
// no way...
Logger.logCircuit(Logger.VERBOSE, "Circuit.close(): can't close due to " + stream.print());
}
}
}
if (stream.closed) si.remove();
}
catch(Exception e) {}
}
//
if ((!force) && (!streams.isEmpty()))
return false;
// gracefully kill circuit with DESTROY-cell or so
if (!force) {
if (route_established > 0) {
// send a destroy-cell to the first hop in the circuit only
Logger.logCircuit(Logger.VERBOSE, "Circuit.close(): destroying " + print());
route_established = 1;
try {
send_cell(new CellDestroy(this));
} catch (IOException e) {
}
}
;
}
;
// close circuit (also removes handlers)
queue.close();
// tls.circuits.remove(new Integer(ID));
destruct = true;
// closed
return true;
}
/** returns the route of the circuit. used to display route on a map or the like */
public Server[] getRoute() {
Server[] s = new Server[route_established];
for(int i=0;i<route_established;++i)
s[i] = route[i].server;
return s;
}
/** used for description */
public String print() {
if (tls != null && tls.server != null) {
StringBuffer sb = new StringBuffer(ID + " [" + tls.server.nickname + " (" + tls.server.countryCode + ")");
for (int i = 1; i < route_established; ++i)
sb.append(" " + route[i].server.nickname + " (" + route[i].server.countryCode + ")");
sb.append("]");
if (closed) {
sb.append(" (closed)");
} else {
if (!established) sb.append(" (establishing)");
}
return sb.toString();
} else
return "<empty>";
}
void registerEstablishedStream(int id, TCPStream s) {
streams.put(id, s);
}
public void handleBegin(CellRelay relay) throws TorException {
if (myHSProperties == null) throw new TorException("Begin received on non-service circuit");
try {
TCPStreamExit exit = new TCPStreamExit(this, relay);
this.myHSProperties.handler.accept(exit);
} catch (IOException e) {
throw new TorException(e.getMessage());
}
}
}
// vim: et