package org.torproject.jtor.circuits.impl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import javax.net.ssl.SSLSocket; import org.torproject.jtor.TorException; import org.torproject.jtor.circuits.Circuit; import org.torproject.jtor.circuits.Connection; import org.torproject.jtor.circuits.ConnectionClosedException; import org.torproject.jtor.circuits.ConnectionConnectException; import org.torproject.jtor.circuits.cells.Cell; import org.torproject.jtor.crypto.TorRandom; import org.torproject.jtor.directory.Router; import org.torproject.jtor.logging.Logger; /** * This class represents a transport link between two onion routers or * between an onion proxy and an entry router. * */ public class ConnectionImpl implements Connection { private final static int DEFAULT_CONNECT_TIMEOUT = 10000; private final SSLSocket socket; private final ConnectionManagerImpl manager; private final Logger logger; private InputStream input; private OutputStream output; private final Router router; private final Map<Integer, CircuitImpl> circuitMap; private final BlockingQueue<Cell> connectionControlCells; private int currentId = 1; private boolean isConnected; private final Thread readCellsThread; ConnectionImpl(ConnectionManagerImpl manager, Logger logger, SSLSocket socket, Router router) { this.manager = manager; this.logger = logger; this.socket = socket; this.router = router; this.circuitMap = new HashMap<Integer, CircuitImpl>(); this.readCellsThread = new Thread(createReadCellsRunnable()); this.readCellsThread.setDaemon(true); this.connectionControlCells = new LinkedBlockingQueue<Cell>(); initializeCurrentCircuitId(); } private void initializeCurrentCircuitId() { final TorRandom random = new TorRandom(); currentId = random.nextInt(0xFFFF) + 1; } public Router getRouter() { return router; } int allocateCircuitId(CircuitImpl circuit) { synchronized(circuitMap) { while(circuitMap.containsKey(currentId)) incrementNextId(); circuitMap.put(currentId, circuit); return currentId; } } private void incrementNextId() { currentId++; if(currentId > 0xFFFF) currentId = 1; } public boolean isConnected() { return isConnected; } public void connect() { if(isConnected) return; try { doConnect(); } catch (SocketTimeoutException e) { throw new ConnectionConnectException("Connect timed out"); } catch (IOException e) { throw new ConnectionConnectException(e.getMessage()); } catch (InterruptedException e) { throw new ConnectionConnectException("Handshake interrupted"); } catch (TorException e){ throw new ConnectionConnectException(e.getMessage()); } manager.addActiveConnection(this); isConnected = true; } private void doConnect() throws IOException, InterruptedException { socket.connect(routerToSocketAddress(router), DEFAULT_CONNECT_TIMEOUT); input = socket.getInputStream(); output = socket.getOutputStream(); readCellsThread.start(); final ConnectionHandshakeV2 handshake = new ConnectionHandshakeV2(this, socket); handshake.runHandshake(); } private SocketAddress routerToSocketAddress(Router router) { final InetAddress address = router.getAddress().toInetAddress(); return new InetSocketAddress(address, router.getOnionPort()); } public void sendCell(Cell cell) { if(!socket.isConnected()) throw new ConnectionClosedException("Cannot send cell because connection is not connected"); synchronized(output) { try { output.write(cell.getCellBytes()); } catch (IOException e) { closeSocket(); manager.removeActiveConnection(this); throw new ConnectionClosedException(); } } } private Cell recvCell() { try { return CellImpl.readFromInputStream(input); } catch (IOException e) { closeSocket(); manager.removeActiveConnection(this); throw new ConnectionClosedException(); } } private void closeSocket() { try { socket.close(); isConnected = false; } catch (IOException e) { logger.warning("Error closing socket: "+ e.getMessage()); } } private Runnable createReadCellsRunnable() { return new Runnable() { public void run() { readCellsLoop(); } }; } private void readCellsLoop() { while(!Thread.interrupted()) { try { processCell( recvCell() ); } catch(ConnectionClosedException e) { notifyCircuitsLinkClosed(); return; } catch(TorException e) { logger.warning("Unhandled Tor exception reading and processing cells: "+ e.getMessage()); e.printStackTrace(); } } } private void notifyCircuitsLinkClosed() { } Cell readConnectionControlCell() { try { return connectionControlCells.take(); } catch (InterruptedException e) { closeSocket(); throw new ConnectionClosedException(); } } private void processCell(Cell cell) { final int command = cell.getCommand(); if(command == Cell.RELAY) { processRelayCell(cell); return; } switch(command) { case Cell.NETINFO: case Cell.VERSIONS: connectionControlCells.add(cell); break; case Cell.CREATED: case Cell.CREATED_FAST: processControlCell(cell); break; case Cell.DESTROY: processDestroyCell(cell); break; default: // Ignore everything else break; } } private void processRelayCell(Cell cell) { synchronized(circuitMap) { final CircuitImpl circuit = circuitMap.get(cell.getCircuitId()); if(circuit == null) { logger.warning("Could not deliver relay cell for circuit id = "+ cell.getCircuitId() +" on connection "+ this +". Circuit not found"); return; } circuit.deliverRelayCell(cell); } } private void processControlCell(Cell cell) { synchronized(circuitMap) { final CircuitImpl circuit = circuitMap.get(cell.getCircuitId()); if(circuit == null) { logger.warning("Could not deliver control cell for circuit id = "+ cell.getCircuitId() +" on connection "+ this +". Circuit not found"); return; } circuit.deliverControlCell(cell); } } private void processDestroyCell(Cell cell) { logger.debug("DESTROY cell received ("+ CellImpl.errorToDescription(cell.getByte() & 0xFF) +")"); synchronized(circuitMap) { final CircuitImpl circuit = circuitMap.remove(cell.getCircuitId()); if(circuit == null) return; circuit.destroyCircuit(); } } void removeCircuit(Circuit circuit) { synchronized(circuitMap) { circuitMap.remove(circuit.getCircuitId()); if(circuitMap.size() == 0) { manager.removeActiveConnection(this); closeSocket(); } } } public String toString() { return "!" + router.getNickname() + "!"; } }