Package com.subgraph.orchid.connections

Source Code of com.subgraph.orchid.connections.ConnectionImpl

package com.subgraph.orchid.connections;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
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 java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLSocket;

import com.subgraph.orchid.Cell;
import com.subgraph.orchid.Circuit;
import com.subgraph.orchid.Connection;
import com.subgraph.orchid.ConnectionFailedException;
import com.subgraph.orchid.ConnectionHandshakeException;
import com.subgraph.orchid.ConnectionIOException;
import com.subgraph.orchid.ConnectionTimeoutException;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Tor;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorException;
import com.subgraph.orchid.circuits.TorInitializationTracker;
import com.subgraph.orchid.circuits.cells.CellImpl;
import com.subgraph.orchid.crypto.TorRandom;
import com.subgraph.orchid.dashboard.DashboardRenderable;
import com.subgraph.orchid.dashboard.DashboardRenderer;

/**
* This class represents a transport link between two onion routers or
* between an onion proxy and an entry router.
*
*/
public class ConnectionImpl implements Connection, DashboardRenderable {
  private final static Logger logger = Logger.getLogger(ConnectionImpl.class.getName());
  private final static int CONNECTION_IDLE_TIMEOUT = 5 * 60 * 1000; // 5 minutes
  private final static int DEFAULT_CONNECT_TIMEOUT = 5000;
  private final static Cell connectionClosedSentinel = CellImpl.createCell(0, 0);

  private final TorConfig config;
  private final SSLSocket socket;
  private InputStream input;
  private OutputStream output;
  private final Router router;
  private final Map<Integer, Circuit> circuitMap;
  private final BlockingQueue<Cell> connectionControlCells;
  private final TorInitializationTracker initializationTracker;
  private final boolean isDirectoryConnection;
 
  private int currentId = 1;
  private boolean isConnected;
  private volatile boolean isClosed;
  private final Thread readCellsThread;
  private final Object connectLock = new Object();
  private final AtomicLong lastActivity = new AtomicLong();
 

  public ConnectionImpl(TorConfig config, SSLSocket socket, Router router, TorInitializationTracker tracker, boolean isDirectoryConnection) {
    this.config = config;
    this.socket = socket;
    this.router = router;
    this.circuitMap = new HashMap<Integer, Circuit>();
    this.readCellsThread = new Thread(createReadCellsRunnable());
    this.readCellsThread.setDaemon(true);
    this.connectionControlCells = new LinkedBlockingQueue<Cell>();
    this.initializationTracker = tracker;
    this.isDirectoryConnection = isDirectoryConnection;
    initializeCurrentCircuitId();
  }
 
  private void initializeCurrentCircuitId() {
    final TorRandom random = new TorRandom();
    currentId = random.nextInt(0xFFFF) + 1;
  }

  public Router getRouter() {
    return router;
  }

  public boolean isClosed() {
    return isClosed;
  }

  public int bindCircuit(Circuit circuit) {
    synchronized(circuitMap) {
      while(circuitMap.containsKey(currentId))
        incrementNextId();
      final int id = currentId;
      incrementNextId();
      circuitMap.put(id, circuit);
      return id;
    }
  }

  private void incrementNextId() {
    currentId++;
    if(currentId > 0xFFFF)
      currentId = 1;
  }

  void connect() throws ConnectionFailedException, ConnectionTimeoutException, ConnectionHandshakeException {
    synchronized (connectLock) {
      if(isConnected) {
        return;
      }
      try {
        doConnect();
      } catch (SocketTimeoutException e) {
        throw new ConnectionTimeoutException();
      } catch (IOException e) {
        throw new ConnectionFailedException(e.getClass().getName() + " : "+ e.getMessage());
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new ConnectionHandshakeException("Handshake interrupted");
      } catch (ConnectionHandshakeException e) {
        throw e;
      } catch (ConnectionIOException e) {
        throw new ConnectionFailedException(e.getMessage());
      }
      isConnected = true;
    }
  }

  private void doConnect() throws IOException, InterruptedException, ConnectionIOException {
    connectSocket();
    final ConnectionHandshake handshake = ConnectionHandshake.createHandshake(config, this, socket);
    input = socket.getInputStream();
    output = socket.getOutputStream();
    readCellsThread.start();
    handshake.runHandshake();
    updateLastActivity();
  }
 
  private void connectSocket() throws IOException {
    if(initializationTracker != null) {
      if(isDirectoryConnection) {
        initializationTracker.notifyEvent(Tor.BOOTSTRAP_STATUS_CONN_DIR);
      } else {
        initializationTracker.notifyEvent(Tor.BOOTSTRAP_STATUS_CONN_OR);
      }
    }

    socket.connect(routerToSocketAddress(router), DEFAULT_CONNECT_TIMEOUT);
   
    if(initializationTracker != null) {
      if(isDirectoryConnection) {
        initializationTracker.notifyEvent(Tor.BOOTSTRAP_STATUS_HANDSHAKE_DIR);
      } else {
        initializationTracker.notifyEvent(Tor.BOOTSTRAP_STATUS_HANDSHAKE_OR);
      }
    }
  }

  private SocketAddress routerToSocketAddress(Router router) {
    final InetAddress address = router.getAddress().toInetAddress();
    return new InetSocketAddress(address, router.getOnionPort());
  }

  public void sendCell(Cell cell) throws ConnectionIOException  {
    if(!socket.isConnected()) {
      throw new ConnectionIOException("Cannot send cell because connection is not connected");
    }
    updateLastActivity();
    synchronized(output) {
      try {
        output.write(cell.getCellBytes());
      } catch (IOException e) {
        logger.fine("IOException writing cell to connection "+ e.getMessage());
        closeSocket();
        throw new ConnectionIOException(e.getClass().getName() + " : "+ e.getMessage());
      }
    }
  }

  private Cell recvCell() throws ConnectionIOException {
    try {
      return CellImpl.readFromInputStream(input);
    } catch(EOFException e) {
      closeSocket();
      throw new ConnectionIOException();
    } catch (IOException e) {
      if(!isClosed) {
        logger.fine("IOException reading cell from connection "+ this + " : "+ e.getMessage());
        closeSocket();
      }
      throw new ConnectionIOException(e.getClass().getName() + " " + e.getMessage());
    }
  }

  void closeSocket() {
    try {
      logger.fine("Closing connection to "+ this);
      isClosed = true;
      socket.close();
      isConnected = false;
    } catch (IOException e) {
      logger.warning("Error closing socket: "+ e.getMessage());
    }
  }

  private Runnable createReadCellsRunnable() {
    return new Runnable() {
      public void run() {
        try {
          readCellsLoop();
        } catch(Exception e) {
          logger.log(Level.WARNING, "Unhandled exception processing incoming cells on connection "+ e, e);
        }
      }
    };
  }

  private void readCellsLoop() {
    while(!Thread.interrupted()) {
      try {
        processCell( recvCell() );
      } catch(ConnectionIOException e) {
        connectionControlCells.add(connectionClosedSentinel);
        notifyCircuitsLinkClosed();
        return;
      } catch(TorException e) {
        logger.log(Level.WARNING, "Unhandled Tor exception reading and processing cells: "+ e.getMessage(), e);
      }
    }
  }

  private void notifyCircuitsLinkClosed() {
   
  }

  Cell readConnectionControlCell() throws ConnectionIOException {
    try {
      return connectionControlCells.take();
    } catch (InterruptedException e) {
      closeSocket();
      throw new ConnectionIOException();
    }
  }

  private void processCell(Cell cell) {
    updateLastActivity();
    final int command = cell.getCommand();

    if(command == Cell.RELAY) {
      processRelayCell(cell);
      return;
    }

    switch(command) {
    case Cell.NETINFO:
    case Cell.VERSIONS:
    case Cell.CERTS:
    case Cell.AUTH_CHALLENGE:
      connectionControlCells.add(cell);
      break;

    case Cell.CREATED:
    case Cell.CREATED_FAST:
    case Cell.DESTROY:
      processControlCell(cell);
      break;
    default:
      // Ignore everything else
      break;
    }
  }

  private void processRelayCell(Cell cell) {
    synchronized(circuitMap) {
      final Circuit 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 Circuit circuit = circuitMap.get(cell.getCircuitId());
      if(circuit != null) {
        circuit.deliverControlCell(cell);
      }
    }
  }

  void idleCloseCheck() {
    synchronized (circuitMap) {
      final boolean needClose =  (!isClosed && circuitMap.isEmpty() && getIdleMilliseconds() > CONNECTION_IDLE_TIMEOUT);
      if(needClose) {
        logger.fine("Closing connection to "+ this +" on idle timeout");
        closeSocket();
      }
    }
  }

  private void updateLastActivity() {
    lastActivity.set(System.currentTimeMillis());
  }

  private long getIdleMilliseconds() {
    if(lastActivity.get() == 0) {
      return 0;
    }
    return System.currentTimeMillis() - lastActivity.get();
  }

  public void removeCircuit(Circuit circuit) {
    synchronized(circuitMap) {
      circuitMap.remove(circuit.getCircuitId());
    }
  }

  public String toString() {
    return "!" + router.getNickname() + "!";
  }

  public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
    final int circuitCount;
    synchronized (circuitMap) {
      circuitCount = circuitMap.size();
    }
    if(circuitCount == 0 && (flags & DASHBOARD_CONNECTIONS_VERBOSE) == 0) {
      return;
    }
    writer.print("  [Connection router="+ router.getNickname());
    writer.print(" circuits="+ circuitCount);
    writer.print(" idle="+ (getIdleMilliseconds()/1000) + "s");
    writer.println("]");
  }
}
TOP

Related Classes of com.subgraph.orchid.connections.ConnectionImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.