package org.torproject.jtor.circuits.impl; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.torproject.jtor.circuits.Circuit; import org.torproject.jtor.circuits.CircuitBuildHandler; import org.torproject.jtor.circuits.CircuitNode; import org.torproject.jtor.circuits.Connection; import org.torproject.jtor.directory.Directory; import org.torproject.jtor.directory.Router; import org.torproject.jtor.logging.Logger; public class CircuitCreationTask implements Runnable { private final static int MAX_PENDING_CIRCUITS = 2; private final static int DEFAULT_CLEAN_CIRCUITS = 3; private final Directory directory; private final CircuitManagerImpl circuitManager; private final Logger logger; private final NodeChooser nodeChooser; private final Executor executor; // To avoid obnoxiously printing a warning every second private int notEnoughDirectoryInformationWarningCounter = 0; CircuitCreationTask(Directory directory, CircuitManagerImpl circuitManager, Logger logger) { this.directory = directory; this.circuitManager = circuitManager; this.logger = logger; this.nodeChooser = new NodeChooser(circuitManager, directory); this.executor = Executors.newCachedThreadPool(); } public void run() { checkUnassignedPendingStreams(); checkExpiredPendingCircuits(); checkCircuitsForCreation(); } private void checkUnassignedPendingStreams() { final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams(); assignPendingStreamsToActiveCircuits(pendingExitStreams); removePendingStreamsByPendingCircuits(pendingExitStreams); buildCircuitsToHandleExitStreams(pendingExitStreams); } private void assignPendingStreamsToActiveCircuits(List<StreamExitRequest> pendingExitStreams) { if(pendingExitStreams.isEmpty()) return; for(Circuit c: circuitManager.getRandomlyOrderedListOfActiveCircuits()) { final Iterator<StreamExitRequest> it = pendingExitStreams.iterator(); while(it.hasNext()) { if(attemptHandleStreamRequest(c, it.next())) it.remove(); } } } private void removePendingStreamsByPendingCircuits(List<StreamExitRequest> pendingExitStreams) { if(pendingExitStreams.isEmpty()) return; for(Circuit c: circuitManager.getPendingCircuits()) { final Iterator<StreamExitRequest> it = pendingExitStreams.iterator(); while(it.hasNext()) { if(c.canHandleExitTo(it.next())) it.remove(); } } } private void buildCircuitsToHandleExitStreams(List<StreamExitRequest> pendingExitStreams) { if(pendingExitStreams.isEmpty()) return; System.out.println("Building new circuits to handle "+ pendingExitStreams.size() +" pending streams"); for(StreamExitRequest r: pendingExitStreams) { System.out.println("Request: "+ r); } for(StreamExitRequest req: pendingExitStreams) createCircuitForExitRequest(req); } private boolean attemptHandleStreamRequest(Circuit c, StreamExitRequest request) { if(c.canHandleExitTo(request)) { if(request.reserveRequest()) executor.execute(newExitStreamTask(c, request)); // else request is reserved meaning another circuit is already trying to handle it return true; } return false; } private OpenExitStreamTask newExitStreamTask(Circuit circuit, StreamExitRequest exitRequest) { return new OpenExitStreamTask(circuit, exitRequest, logger); } private void checkExpiredPendingCircuits() { // TODO Auto-generated method stub } private void createCircuitForExitRequest(StreamExitRequest request) { if(!directory.haveMinimumRouterInfo()) return; if(circuitManager.getPendingCircuitCount() >= MAX_PENDING_CIRCUITS) return; final Circuit circuit = circuitManager.createNewCircuit(); final NodeChoiceConstraints ncc = new NodeChoiceConstraints(); // XXX ncc.setNeedCapacity(true); ncc.setNeedUptime(true); final Router exitRouter = nodeChooser.chooseExitNodeForTarget(request, ncc); ncc.addExcludedRouter(exitRouter); final Router middleRouter = nodeChooser.chooseMiddleNode(ncc); ncc.addExcludedRouter(middleRouter); final Router entryRouter = nodeChooser.chooseEntryNode(ncc); List<Router> path = Arrays.asList(entryRouter, middleRouter, exitRouter); executor.execute(new OpenCircuitTask(circuit, path, createCircuitBuildHandler(), logger)); } private void checkCircuitsForCreation() { if(!directory.haveMinimumRouterInfo()) { if(notEnoughDirectoryInformationWarningCounter % 20 == 0) logger.warning("Cannot build circuits because we don't have enough directory information"); notEnoughDirectoryInformationWarningCounter++; return; } if((circuitManager.getCleanCircuitCount() + circuitManager.getPendingCircuitCount()) < DEFAULT_CLEAN_CIRCUITS && circuitManager.getPendingCircuitCount() < MAX_PENDING_CIRCUITS) { final List<Router> path = choosePreemptiveExitPath(); final Circuit circuit = circuitManager.createNewCircuit(); executor.execute(new OpenCircuitTask(circuit, path, createCircuitBuildHandler(), logger)); } } private List<Router> choosePreemptiveExitPath() { final NodeChoiceConstraints ncc = new NodeChoiceConstraints(); ncc.setNeedCapacity(true); ncc.setNeedUptime(true); final Router exitRouter = nodeChooser.chooseExitNodeForPort(80, ncc); ncc.addExcludedRouter(exitRouter); final Router middleRouter = nodeChooser.chooseMiddleNode(ncc); ncc.addExcludedRouter(middleRouter); final Router entryRouter = nodeChooser.chooseEntryNode(ncc); return Arrays.asList(entryRouter, middleRouter, exitRouter); } private CircuitBuildHandler createCircuitBuildHandler() { return new CircuitBuildHandler() { public void circuitBuildCompleted(Circuit circuit) { logger.debug("Circuit completed to: "+ circuit); circuitOpenedHandler(circuit); } public void circuitBuildFailed(String reason) { logger.debug("Circuit build failed: "+ reason); } public void connectionCompleted(Connection connection) { logger.debug("Circuit connection completed to "+ connection); } public void connectionFailed(String reason) { logger.debug("Circuit connection failed: "+ reason); } public void nodeAdded(CircuitNode node) { //logger.debug("Node added to circuit: "+ node); } }; } private void circuitOpenedHandler(Circuit circuit) { final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams(); for(StreamExitRequest req: pendingExitStreams) { if(circuit.canHandleExitTo(req) && req.reserveRequest()) executor.execute(newExitStreamTask(circuit, req)); } } }