/** * 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.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.util.Date; import TorJava.Common.Queue; import TorJava.Common.TorException; import TorJava.Common.TorNoAnswerException; /** * handles the features of single TCP streams on top of circuits through the tor * network. provides functionality to send and receive data by this streams and * is publicly visible. * * @author Lexi Pimenidis * @author Tobias Koelsch * @author Michael Koellejan * @version unstable */ public class TCPStream { int queue_timeout = TorConfig.queueTimeoutStreamBuildup; // wait x seconds for answer Circuit circ; int ID; Queue queue; // receives incoming data public InetAddress resolvedAddress; public TCPStreamProperties sp; public boolean established; public boolean closed; int closed_for_reason; // set by CellRelay. descriptive Strings are in // CellRelay.reason_to_string //TCPStreamThreadTor2Java tor2java; //TCPStreamThreadJava2Tor java2tor; QueueTor2JavaHandler qhT2J; QueueFlowControlHandler qhFC; TCPStreamOutputStream outputStream; 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 streamLevelFlowControl = 500; final int streamLevelFlowControlIncrement = 50; /** * creates a stream on top of a existing circuit. users and programmers * should never call this function, but Tor.connect() instead. * * @param c * the circuit to build the stream through * @param sp * the host etc. to connect to * @see Tor * @see Circuit * @see TCPStreamProperties */ TCPStream(Circuit c, TCPStreamProperties sp) throws IOException, TorException, TorNoAnswerException { this.sp = sp; established = false; created = new Date(); last_action = created; last_cell = created; int setupDuration; // stream establishment duration long startSetupTime; // attach stream to circuit circ = c; ID = circ.assign_streamID(this); queue = new Queue(queue_timeout); closed = false; closed_for_reason = 0; Logger.logStream(Logger.VERBOSE, "TCPStream: building new stream " + print()); startSetupTime = System.currentTimeMillis(); // send RELAY-BEGIN send_cell(new CellRelayBegin(this, sp)); // wait for RELAY_CONNECTED CellRelay relay = null; try { Logger.logStream(Logger.VERBOSE, "TCPStream: Waiting for Relay-Connected Cell..."); relay = queue.receiveRelayCell(CellRelay.RELAY_CONNECTED); Logger.logStream(Logger.VERBOSE, "TCPStream: Got Relay-Connected Cell"); } catch (TorException e) { if (!closed) // only msg, if closing was unintentionally Logger.logStream(Logger.WARNING, "TCPStream: Closed:" + print() + " due to TorException:" + e.getMessage()); closed = true; // MRK: when the circuit does not work at this point: close it // Lexi: please do it soft! there might be other streams // working on this circuit... //c.close(false); // Lexi: even better: increase only a counter for this circuit // otherwise circuits will close on an average after 3 or 4 // streams. this is nothing we'd like to happen c.reportStreamFailure(this); throw e; } catch (IOException e) { closed = true; Logger.logStream(Logger.WARNING, "TCPStream: Closed:" + print() + " due to IOException:" + e.getMessage()); throw e; } setupDuration = (int) (System.currentTimeMillis() - startSetupTime); // store resolved IP in TCPStreamProperties byte[] ip = new byte[relay.length]; System.arraycopy(relay.data, 0, ip, 0, ip.length); try { sp.addr = InetAddress.getByAddress(ip); sp.addr_resolved = true; resolvedAddress = sp.addr; Logger.logStream(Logger.RAW_DATA, "TCPStream: storing resolved IP " + sp.addr.toString()); } catch (IOException e) { } // create reading threads to relay between user-side and tor-side //tor2java = new TCPStreamThreadTor2Java(this); //java2tor = new TCPStreamThreadJava2Tor(this); qhFC = new QueueFlowControlHandler(this,streamLevelFlowControl,streamLevelFlowControlIncrement); this.queue.addHandler(qhFC); qhT2J = new QueueTor2JavaHandler(this); this.queue.addHandler(qhT2J); outputStream = new TCPStreamOutputStream(this); Logger.logStream(Logger.INFO, "TCPStream: build stream " + print() + " within " + setupDuration + " ms"); // attach stream to history circ.registerStream(sp, setupDuration); established = true; // Tor.lastSuccessfulConnection = new Date(System.currentTimeMillis()); circ.tor.fireEvent(new TorEvent(TorEvent.STREAM_BUILD,this,"Stream build: "+print())); } /** called from derived ResolveStream */ TCPStream(Circuit c) { circ = c; } 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{ circ.send_cell(c); } catch(IOException e) { // if there's an error in sending a cell, close this stream this.circ.reportStreamFailure(this); close(false); throw e; } } /** send a stream-layer dummy */ void sendKeepAlive() { try { send_cell(new CellRelayDrop(this)); } catch (IOException e) { } } /** for application interaction */ public void close() { // gracefully close stream close(false); // remove from circuit Logger.logStream(Logger.RAW_DATA, "TCPStream.close(): removing stream " + print()); circ.streams.remove(new Integer(ID)); } /** * for internal usage * * @param force * if set to true, just destroy the object, without sending * END-CELLs and stuff */ void close(boolean force) { Logger.logStream(Logger.VERBOSE, "TCPStream.close(): closing stream " + print()); circ.tor.fireEvent(new TorEvent(TorEvent.STREAM_CLOSED,this,"Stream closed: "+print())); // if stream is not closed, send a RELAY-END-CELL if (!(closed || force)) { try { send_cell(new CellRelayEnd(this, (byte) 6)); // send cell with 'DONE' } catch (IOException e) { } } // terminate threads gracefully closed = true; /*if (!force) { try { this.wait(3); } catch (Exception e) { } }*/ // terminate threads if they are still alive if (outputStream != null) { try { outputStream.close(); }catch(Exception e){} }; // close queue (also removes handlers) queue.close(); // remove from circuit circ.streams.remove(new Integer(ID)); } /** * use this to receive data by the anonymous data stream * * @return a standard Java-Inputstream */ public InputStream getInputStream() { return qhT2J.sin; } /** * use this to transmit data through the Tor-network * * @return a standard Java-Outputstream */ public OutputStream getOutputStream() { return outputStream; } /** used for proxy and UI */ public String getRoute() { StringBuffer sb = new StringBuffer(); for(int i=0; i< circ.route_established ; ++i) { Server s = circ.route[i].server; sb.append(", "); sb.append(s.nickname+" ("+s.countryCode+")"); } return sb.toString(); } /** for debugging */ String print() { if (sp == null) return ID + " on circuit " + circ.print() + " to nowhere"; else { if (closed) { return ID + " on circuit " + circ.print() + " to " + sp.hostname + ":" + sp.port + " (closed)"; } else { return ID + " on circuit " + circ.print() + " to " + sp.hostname + ":" + sp.port; } } } } // vim: et