/** * 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.File; import java.io.IOException; import java.net.InetAddress; import java.security.Security; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Random; import java.util.Vector; import TorJava.Common.Common; import TorJava.Common.Encoding; import TorJava.Common.StreamsAndHTTP; import TorJava.Common.TorException; import TorJava.Common.TorNoAnswerException; import TorJava.Common.TorResolveFailedException; /** * MAIN CLASS. keeps track of circuits, tls-connections and the status of * servers. Provides high level access to all needed functionality, i.e. * connecting to some remote service via Tor. * * CG - 07/07/09 Removed GeoIP * * @author Lexi Pimenidis * @author Tobias Koelsch * @author Vinh Pham * @author Andriy Panchenko * @author Michael Koellejan * @author Connell Gauld * @version $Date: 2008/04/16 16:34:55 $ $Revision: 1.2 $ */ public class Tor { Directory dir; public FirstNodeHandler fnh; public static Logger log; TorBackgroundMgmt mgmt; // management-thread TorConfig config; DirectoryServer dirserver; PrivateKeyHandler privateKeyHandler; HashMap<String,HiddenServiceProperties> hiddenServices; HashMap<String,ServiceDescriptor> cachedServiceDescriptors; HashMap<String,Circuit> hiddenServiceCircuits; long startupPhaseWithoutConnects; // used to delay connects until Tor has some time to build up circuits and stuff Vector<TorEventHandler> eventHandler; /** * init Tor with all defaults * * @exception IOException */ public Tor(boolean fastStartup) throws IOException { log = new Logger(); config = new TorConfig(true); init_phase1(false, fastStartup); initDirectory(false); init_phase3(fastStartup); } /** * start tor and try not to access any local files * * @param noLocalFileSystemAccess set to true to avoid any access to the local filesystem * @exception IOException */ public Tor(boolean noLocalFileSystemAccess, boolean fastStartup) throws IOException { log = new Logger(noLocalFileSystemAccess); config = new TorConfig(!noLocalFileSystemAccess); init_phase1(noLocalFileSystemAccess, fastStartup); initDirectory(noLocalFileSystemAccess); init_phase3(fastStartup); } /** * init Tor with all defaults * * @exception IOException */ public Tor(String configFile, boolean fastStartup) throws IOException { log = new Logger(); config = new TorConfig(configFile); init_phase1(false, fastStartup); initDirectory(false); init_phase3(fastStartup); } public void reloadConfig() { if (config!=null) config.reload(); } private void init_phase1(boolean noLocalFileSystemAccess, boolean fastStartup) throws IOException { // install BC, if not already done if(Security.getProvider("BC")==null) { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(),2); } // logger and config Logger.logGeneral(Logger.INFO,"TorJava is starting up. Please don't use this implementation for strong anonymity!"); // create identity privateKeyHandler = new PrivateKeyHandler(fastStartup); // determine end of startup-Phase startupPhaseWithoutConnects = System.currentTimeMillis() + TorConfig.startupDelaySeconds * 1000L; // init event-handler eventHandler = new Vector<TorEventHandler>(); } private void initDirectory(boolean noLocalFileSystemAccess) throws IOException { // what to do, if no local files shall be touched if (noLocalFileSystemAccess) { dir = new Directory(this,privateKeyHandler.getIdentity()); return; } // load stable dir first // log.logDirectory(Logger.INFO,"Tor.initDirectory(): load stable dir"); //dir = new Directory(this,privateKeyHandler.getIdentity(), config.TOR_STABLE_DIR_FILENAME, ""); // load cache-file over stable-dir long age = System.currentTimeMillis() - TorConfig.cacheMaxAgeSeconds * 1000L; File cacheFile = new File(TorConfig.getCacheFilename()); if (cacheFile.exists() && cacheFile.lastModified() >= age) { try{ // TODO-FIXME-SECURITY-BUG-FIX: use fingerprint of dirServerKeys //dir.readDirectoryFromFile(config.getCacheFilename(), ""); dir = new Directory(this,privateKeyHandler.getIdentity(), TorConfig.getCacheFilename(), ""); } catch(Exception e) { Logger.logDirectory(Logger.WARNING,"Tor.initDirectory(): "+e.getMessage()); } } else { dir = new Directory(this,privateKeyHandler.getIdentity()); } /* uneccessary // check if in cache-file and/or stable-file have been some tor nodes if ((dir==null) || (dir.torServers==null) || (dir.torServers.size()<1)) throw new IOException("no Tor nodes found"); */ // update from network in the background // -> will be done by TorBackgroundMgmt! } private void init_phase3(boolean fastStartup) throws IOException { // establish handler for TLS connections fnh = new FirstNodeHandler(this); // init thread to renew every now and then mgmt = new TorBackgroundMgmt(this,TorConfig.defaultIdleCircuits, fastStartup); // hidden services hiddenServices = new HashMap<String,HiddenServiceProperties>(); cachedServiceDescriptors = new HashMap<String,ServiceDescriptor>(); hiddenServiceCircuits = new HashMap<String, Circuit>(); // directory service if (TorConfig.dirserver_port > 0) dirserver = new DirectoryServer(dir, TorConfig.dirserver_port); } /** * get a factory for TOR sockets. * * @return default factory for TOR sockets. */ public TorSocketFactory getSocketFactory() { if (TorSocketFactory.getDefault() == null) return new TorSocketFactory(this); else return (TorSocketFactory) TorSocketFactory.getDefault(); } /** * makes a connection to a remote service * * @param sp * hostname, port to connect to and other stuff * @return some socket-thing */ public TCPStream connect(TCPStreamProperties sp) throws IOException, TorResolveFailedException { //Tor.log.logGeneral(Logger.VERBOSE, "Tor: Trying to connect to " + sp.hostname); if (sp.hostname == null) throw new IOException("Tor: no hostname is provided"); // check, if tor is still in startup-phase checkStartup(); // check whether the address is hidden if (sp.hostname.endsWith(".onion")) return connectToHidden(sp); boolean resolveException = false; Circuit[] cs = fnh.provideSuitableCircuits(sp,false); if (TorConfig.veryAggressiveStreamBuilding) { for (int j = 0; j < cs.length; ++j) { // start N asynchronous stream building threads try{ StreamThread[] streamThreads = new StreamThread[cs.length]; for(int i=0;i<cs.length;++i) streamThreads[i] = new StreamThread(cs[i],sp); // wait for the first stream to return int chosenStream = -1; int waitingCounter = TorConfig.queueTimeoutStreamBuildup * 1000 / 10; while((chosenStream < 0)&&(waitingCounter>=0)) { boolean atLeastOneAlive = false; for(int i=0;(i<cs.length)&&(chosenStream<0);++i) if (!streamThreads[i].isAlive()) { if ((streamThreads[i].stream != null) && (streamThreads[i].stream.established)) { chosenStream = i; } } else { atLeastOneAlive = true; } if (!atLeastOneAlive) break; Common.sleep(10); --waitingCounter; } // return one and close others if (chosenStream>=0) { TCPStream returnValue = streamThreads[chosenStream].stream; new ClosingThread(streamThreads,chosenStream); return returnValue; } } catch(Exception e) { Logger.logStream(Logger.WARNING,"Tor.connect(): " + e.getMessage() ); return null; } } } else { // build serial N streams, stop if successful for (int i = 0; i < cs.length; ++i) { try { return new TCPStream(cs[i], sp); } catch (TorResolveFailedException e) { Logger.logStream(Logger.WARNING, "Tor.connect: Resolve failed:" + e.getMessage()); resolveException = true; } catch (TorNoAnswerException e) { Logger.logStream(Logger.WARNING, "Tor.connect: Timeout on circuit:" + e.getMessage()); } catch (TorException e) { Logger.logStream(Logger.WARNING, "Tor.connect: TorException trying to reuse existing circuit:" + e.getMessage()); } catch(IOException e) { Logger.logStream(Logger.WARNING, "Tor.connect: IOException " + e.getMessage()); } } } if (resolveException) throw new TorResolveFailedException(); throw new IOException("Tor.connect: unable to connect to " + sp.hostname + ":" + sp.port + " after " + sp.connect_retries + " retries"); } private Circuit establishIntroductionPoint(HiddenServiceProperties service,TCPStreamProperties sp_intro) { Circuit circuit=null; for(int i=0;i<sp_intro.connect_retries;++i) { try { // use circuit circuit = fnh.provideSuitableNewCircuit(sp_intro); circuit.myHSProperties = service; Logger.logHiddenService(Logger.VERBOSE, "Tor.provideHiddenService: send relay_establish_intro-Cell over " + circuit.print()); circuit.send_cell(new CellRelayEstablishIntro(circuit, service)); circuit.queue.receiveRelayCell(CellRelay.RELAY_INTRO_ESTABLISHED); return circuit; } catch (Exception e) { Logger.logHiddenService(Logger.WARNING, "Tor.provideHiddenService: " + e.getMessage()); //e.printStackTrace(); if (circuit != null) circuit.close(true); } } return null; } /** * ensures that a hidden service is running. If it isn't * already running, create it. If it is already running, * reassign the handler to the handler provided * @param service the service * @return the url of the service * @throws IOException * @throws TorException */ public String provideHiddenService(HiddenServiceProperties service) throws IOException, TorException { if (hiddenServices.containsKey(service.getName())) { HiddenServiceProperties old = hiddenServices.get(service.getName()); old.reassignHandler(service.handler); return service.getName(); } else return setupHiddenService(service); } /** * initializes a new hidden service * * @param service * all data needed to init the thingy */ public String setupHiddenService(HiddenServiceProperties service) throws IOException, TorException{ if (service.handler == null) throw new TorException( "Tor.provideHiddenService: need a handler for incoming connections"); Logger.logHiddenService(Logger.INFO, "Tor.provideHiddenService: setting up hidden service " + service.getName()); // check, if tor is still in startup-phase checkStartup(); // Store the service String url = service.getName(); hiddenServices.put(url, service); // Update hidden service status service.setStatus(HiddenServiceProperties.STATUS_STARTED, true); // establish choosen introduction points Iterator<IntroductionPoint> i = service.introPoints.iterator(); while (i.hasNext()) { IntroductionPoint ip = i.next(); String ip_name = ip.getSrv().nickname; Logger.logHiddenService(Logger.INFO, "Tor.provideHiddenService: establish introduction point at " + ip_name); // create circuit properties to end the circuit at the // intro-point TCPStreamProperties sp_intro = new TCPStreamProperties(); sp_intro.setCustomExitpoint(ip_name); establishIntroductionPoint(service,sp_intro); } // choose additional random introduction points, if none given while (service.introPoints.size() < service.minimum_number_of_intro_points) { TCPStreamProperties sp_intro = new TCPStreamProperties(); sp_intro.connect_retries = 1; Circuit c = establishIntroductionPoint(service,sp_intro); if (c!=null) { String ip_name = c.route[c.route_established-1].server.nickname; Logger.logHiddenService(Logger.INFO, "Tor.provideHiddenService: establish introduction point at " + ip_name); service.addIntroPoint(ip_name,dir); }; } // Update hidden service status service.setStatus(HiddenServiceProperties.STATUS_INTROPOINTS, true); // advertise introduction points int advertise_success = 0; Iterator<String> i2 = config.trustedServers.keySet().iterator(); while (i2.hasNext()) { String nick = i2.next(); HashMap<String, Object> trustedServer = config.trustedServers.get(nick); String address = (String) trustedServer.get("ip"); int port = ((Integer) trustedServer.get("port")).intValue(); int tries = 2; while (tries > 0) { Logger.logHiddenService(Logger.VERBOSE, "Tor.provideHiddenService: advertise service at " + nick + " (" + address + ":" + port + ")"); TCPStreamProperties sp = new TCPStreamProperties(address, port); TCPStream stream = null; // advertise data for hidden service's rendevouz anonymously try { stream = connect(sp); // advertise service-descriptor with HTTP-POST byte[] body = service.sd_v0.toByteArray(); String headerStr = "POST /tor/rendezvous/publish HTTP/1.0\r\n" + "Content-Length: " + body.length + "\r\n" + "\r\n"; byte[] header = headerStr.getBytes(); byte[] out = new byte[header.length + body.length]; System.arraycopy(header, 0, out, 0, header.length); System.arraycopy(body, 0, out, header.length, body.length); String answer = StreamsAndHTTP.HTTPBinaryRequest( stream.getOutputStream(), stream.getInputStream(), out); // analyse answer if (!answer.startsWith("HTTP/1.0 200 ")) throw new TorException( "Tor.provideHiddenService: no success posting service descriptor " + answer); Logger.logHiddenService(Logger.INFO, "Successfully advertised at " + nick); stream.close(); ++advertise_success; if (advertise_success > 1) { // Update hidden service status service.setStatus(HiddenServiceProperties.STATUS_ADVERTISED, true); } break; } catch (Exception e) { Logger.logHiddenService(Logger.WARNING, "Tor.provideHiddenService: error advertising service at " + nick + " (" + address + ":" + port + ")\n" + e.getMessage()); } if (--tries <= 0) Logger.logHiddenService(Logger.WARNING, "Tor.provideHiddenService: final error advertising service at " + nick + " (" + address + ":" + port + ")"); } } // at least one advertisement? if (advertise_success < 1) throw new TorException( "Tor.provideHiddenService: no successful advertisement"); // success return url; } public int getHiddenServiceProvisionStatusPercent(String hiddenServiceUrl) throws IllegalArgumentException { if (hiddenServices.containsKey(hiddenServiceUrl)) { HiddenServiceProperties h = hiddenServices.get(hiddenServiceUrl); return h.getStatusPercent(); } else throw new IllegalArgumentException("No such hidden service"); } /** * makes a connection to a hidden service * * @param sp * hostname, port to connect to and other stuff * @return some socket-thing */ private TCPStream connectToHidden(TCPStreamProperties spo) throws IOException { // check, if tor is still in startup-phase checkStartup(); Circuit myRendezvousCirc = null; Server hiddenServer; Node hiddenNode = null; // String address, x, y; String z; // Iterator it, i, i2; byte[] cookie = new byte[20]; // boolean notFound; int j; z = (String) Encoding.parseHiddenAddress(spo.hostname).get("z"); // Do we already have a connection to this address? if (hiddenServiceCircuits.containsKey(z)) { // TODO assess suitability of this circuit System.out.println("Reusing existing circuit"); TCPStreamProperties tcpProps = new TCPStreamProperties("", spo.port); try { return new TCPStream(hiddenServiceCircuits.get(z), tcpProps); } catch (TorNoAnswerException e) { // Create a new circuit instead e.printStackTrace(); } catch (TorException e) { // Create a new circuit instead e.printStackTrace(); } } // get a copy from the service descriptor (either local cache or // retrieve form network) ServiceDescriptor sd = (ServiceDescriptor) cachedServiceDescriptors .get(z); if (sd == null || (!sd.checkTimeStampValidity())) { sd = ServiceDescriptor.loadFromDirectory(z, this); cachedServiceDescriptors.put(z, sd); // cache it } boolean establishedRendezvous = false; j = 0; // attempts counted // spo.connect_retries try to establish rendezvous while ((j < spo.connect_retries) && (!establishedRendezvous)) { j++; try { myRendezvousCirc = fnh.provideSuitableNewCircuit(new TCPStreamProperties()); String rendezvousName = myRendezvousCirc.route[myRendezvousCirc.route_established-1].server.nickname; Logger.logGeneral(Logger.INFO, "Tor.connectToHidden: establishing rendezvous point for " + z + " at "+ rendezvousName); Random rnd = new Random(); rnd.nextBytes(cookie); myRendezvousCirc.send_cell(new CellRelayEstablishRendezvous( myRendezvousCirc, cookie)); myRendezvousCirc.streamHistory.add(spo.hostname); // wait for answer CellRelay rendezvousACK = myRendezvousCirc.queue.receiveRelayCell(CellRelay.CELL_RELAY_RENDEZVOUS_ESTABLISHED); if (rendezvousACK.length > 0) { throw new TorException( "Tor.connectToHidden: Got NACK from RENDEZVOUS Point"); } myRendezvousCirc.sd = sd; hiddenServer = new Server(this,sd.publicKey); hiddenNode = new Node(hiddenServer); // between HS and // Rendezvous point establishedRendezvous = true; } catch (IOException e) { e.printStackTrace(); } catch (TorException e) { e.printStackTrace(); } } if (!establishedRendezvous) { Logger.logGeneral(Logger.WARNING, "Tor.connectToHidden: coudn't establishing rendezvous point for " + z); throw new IOException( "Tor.connectToHidden: coudn't establishing rendezvous point for " + z); } Iterator<IntroductionPoint> it3 = sd.introPoints.iterator(); String rendezvousName = myRendezvousCirc.route[myRendezvousCirc.route.length - 1].server.nickname; while (it3.hasNext()) { IntroductionPoint iPoint = (IntroductionPoint) it3.next(); Logger.logGeneral(Logger.INFO,"Tor.connectToHidden: contacting introduction point " + iPoint.getNickname() + " for " + z); // introduce rendezvous to the node TCPStreamProperties spIntro = new TCPStreamProperties(); spIntro.exitPolicyRequired = false; // Server sr = iPoint.getSrv(); spIntro.setCustomExitpoint(iPoint.getNickname() ); try { // make new circ where the last node is intro point Circuit myIntroCirc = new Circuit(this,fnh, dir, spIntro); // System.out.println(" LAST NODE IS... " + // myIntroCirc.route[myIntroCirc.route.length-1].server.name); // and CellIntro1 data encrypted with PK of Hidden Service, and _not_ of the introPoint myIntroCirc.send_cell(new CellRelayIntroduce1(myIntroCirc, cookie, sd, rendezvousName, hiddenNode)); // wait for ack CellRelay introACK = myIntroCirc.queue.receiveRelayCell(CellRelay.CELL_RELAY_COMMAND_INTRODUCE_ACK); if (introACK.length > 0) throw new TorException("Tor.connectToHidden: Got NACK from Introduction Point"); // introduce ACK is received Logger.logGeneral(Logger.RAW_DATA, "Tor.connectToHidden: Got ACK from Intro Point"); myIntroCirc.close(true); // wait for answer from the hidden service (RENDEZVOUS2) int oldTimeout = myRendezvousCirc.queue.timeout; if (oldTimeout < 120*1000) myRendezvousCirc.queue.timeout = 120*1000; CellRelay r2Relay = myRendezvousCirc.queue.receiveRelayCell(CellRelay.CELL_RELAY_RENDEZVOUS2); myRendezvousCirc.queue.timeout = oldTimeout; // finish diffie-hellman byte[] dh_gy = new byte[148]; System.arraycopy(r2Relay.data, 0, dh_gy, 0, 148); hiddenNode.finish_dh(dh_gy); myRendezvousCirc.addNode(hiddenNode); Logger.logGeneral(Logger.INFO, "Tor.connectToHidden: succesfully established rendezvous with " + z); // address in begin cell set to ""; TCPStreamProperties tcpProps = new TCPStreamProperties("", spo.port); if (hiddenServiceCircuits.containsKey(z)) hiddenServiceCircuits.remove(z); hiddenServiceCircuits.put(z, myRendezvousCirc); // connect return new TCPStream(myRendezvousCirc, tcpProps); } catch (TorException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // all intros failed... // perhaps there is something wrong with out cached service descriptor cachedServiceDescriptors.remove(z); throw new IOException( "Tor.connectToHidden: couldn't connect to an introduction point of " + z); } /** * shut down everything * * @param force * set to true, if everything shall go fast. For graceful end, * set to false */ public void close(boolean force) { Logger.logGeneral(Logger.INFO,"TorJava ist closing down"); // shutdown mgmt mgmt.close(); // shut down connections fnh.close(force); // shutdown/save directory dir.close(TorConfig.getCacheFilename()); // write config file config.close(); // close hidden services // TODO close hidden services, once they are implemented and work // kill logger Logger.logGeneral(Logger.INFO, "Tor.close(): CLOSED"); log.close(); } /** synonym for close(false); */ public void close() { close(false); } /** * anonymously resolve a hostname. * * @param host * the hostname * @return the resolved hostname */ public InetAddress resolve(String host) throws IOException { Object o = resolve_internal(host); if (o instanceof InetAddress) return (InetAddress) o; else return null; } /** * anonymously do a reverse look-up * * @param addr * the inet-address to be resolved * @return the hostname */ public String resolve(InetAddress addr) throws IOException { // build address (works only for IPv4!) byte[] a = addr.getAddress(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 4; ++i) { sb.append((int) (a[3 - i]) & 0xff); sb.append('.'); } ; sb.append("in-addr.arpa"); // resolve address Object o = resolve_internal(sb.toString()); if (o instanceof String) return (String) o; else return null; } /** * internal function to use the tor-resolve-functionality * * @param query * a hostname to be resolved, or for a reverse lookup: * A.B.C.D.in-addr.arpa * @return either an InetAddress (normal query), or a String * (reverse-DNS-lookup) */ private Object resolve_internal(String query) throws IOException { try { // check, if tor is still in startup-phase checkStartup(); // try to resolv query over all existing circuits // so iterate over all TLS-Connections Iterator<String> i = fnh.tls.keySet().iterator(); while (i.hasNext()) { TLSConnection tls = fnh.tls.get(i.next()); // and over all circuits in each TLS-Connection Iterator<Integer> i2 = tls.circuits.keySet().iterator(); while (i2.hasNext()) { Circuit circuit = (Circuit) tls.circuits.get(i2.next()); try { if (circuit.established) { // if an answer is given, we're satisfied ResolveStream rs = new ResolveStream(circuit); Object o = rs.resolve(query); rs.close(); return o; } ; } catch (Exception e) { // in case of error, do nothing, but retry with the next // circuit } } } // if no circuit could give an answer (possibly there was no // established circuit?) // build a new circuit and ask this one to resolve the query ResolveStream rs = new ResolveStream(new Circuit(this,fnh, dir, new TCPStreamProperties())); Object o = rs.resolve(query); rs.close(); return o; } catch (TorException e) { throw new IOException("Error in Tor: " + e.getMessage()); } catch (InterruptedException e) { throw new IOException("Error in Tor: " + e.getMessage()); } } public void registerEventHandler(TorEventHandler eh) { eventHandler.add(eh); } public boolean removeEventHandler(TorEventHandler eh) { return eventHandler.remove(eh); } void fireEvent(TorEvent event) { for(int i=0;i<eventHandler.size();++i) { try{ TorEventHandler eh = (TorEventHandler) eventHandler.elementAt(i); eh.ping(event); } catch(Exception e) { Logger.logGeneral(Logger.INFO, "Tor.fireEvent(): "+e.getMessage()); } } } private static boolean gaveMessage = false; private static boolean startUp = true; /** make sure that tor had some time to read the directory and build up soime circuits */ private void checkStartup() { // start up is prooven to be over if (!startUp) return; // check if startup is over long now = System.currentTimeMillis(); if (now >= startupPhaseWithoutConnects) { startUp = false; return; } // wait for startup to be over long sleep = startupPhaseWithoutConnects - System.currentTimeMillis(); if(!gaveMessage) { gaveMessage=true; Logger.logGeneral(Logger.VERBOSE,"Tor.checkStartup(): TorJava is still in startup phase, sleeping for "+(sleep/1000L)+" seconds"); } try { Thread.sleep(sleep); } catch(Exception e) {} } /** * returns a set of current established circuits * (only used by TorJava.Proxy.MainWindow to get a list of circuits to display) * */ public HashSet<Circuit> getCurrentCircuits() { HashSet<Circuit> allCircs = new HashSet<Circuit>(); Iterator<String> it = fnh.tls.keySet().iterator(); while (it.hasNext()) { TLSConnection tls = (TLSConnection) fnh.tls.get(it.next()); Iterator<Integer> i2 = tls.circuits.keySet().iterator(); while (i2.hasNext()) { Circuit circuit = tls.circuits.get(i2.next()); //if (circuit.established && (!circuit.closed)){ allCircs.add(circuit); //} } } return allCircs; } } /** * this class is used to build a TCPStream in the background * @author Lexi */ class StreamThread extends Thread { TCPStream stream; Circuit cs; TCPStreamProperties sp; boolean finished = false; /** copy data to local variables and start background thread */ StreamThread(Circuit cs,TCPStreamProperties sp) { this.cs = cs; this.sp = sp; this.finished = false; this.start(); } /** build stream in background and return. possibly the stream is closed * prematurely by another thread by having its queue closed */ public void run() { try{ this.stream = new TCPStream(cs,sp); } catch(Exception e) { if ((stream != null) && (stream.queue != null) && (!stream.queue.closed)) Logger.logStream(Logger.WARNING,"Tor.StreamThread.run(): " + e.getMessage() ); this.stream = null; } this.finished = true; } } /** this background thread closes all streams that have been build by * StreamThreads but are not used any more<br> * FIXME: cache ready streams and possibly reuse them later on * @author Lexi */ class ClosingThread extends Thread { StreamThread[] threads; int chosenOne; ClosingThread(StreamThread[] threads,int chosenOne) { this.threads = threads; this.chosenOne = chosenOne; this.start(); } public void run() { // loop and check when threads finish and then close the streams for(int i=0;i<threads.length;++i) if(i != chosenOne ) { if (threads[i].stream != null) { try{ // finish the queue threads[i].stream.closed=true; threads[i].stream.queue.close(); } catch(Exception e) { Logger.logStream(Logger.WARNING,"Tor.ClosingThread.run(): " + e.getMessage() ); } try{ threads[i].stream.close(); } catch(Exception e) { Logger.logStream(Logger.WARNING,"Tor.ClosingThread.run(): " + e.getMessage() ); } } try{ threads[i].join(); } catch(Exception e) { Logger.logStream(Logger.WARNING,"Tor.ClosingThread.run(): " + e.getMessage() ); } } } } // vim: et