Source code for connection.connectionpool

# Copyright 2014, 2015, Nik Kinkel
# See LICENSE for licensing information

'''
.. topic:: Details

    The ConnectionPool manages a pool of TLS connections to entry nodes. The
    main job of the ConnectionPool is to hand out TLS connections to
    requesting circuits and keep track of all the open connections. TLS
    connections to the same entry nodes are shared among circuits.

'''
import logging

from twisted.internet import defer, endpoints
from twisted.internet.ssl import ClientContextFactory

from OpenSSL import SSL

from oppy.connection.connection import Connection
from oppy.connection.definitions import V3_CIPHER_STRING


[docs]class TLSClientContextFactory(ClientContextFactory): isClient = 1 method = SSL.TLSv1_METHOD _contextFactory = SSL.Context
[docs] def getContext(self): context = self._contextFactory(self.method) context.set_cipher_list(V3_CIPHER_STRING) return context
[docs]class ConnectionPool(object): '''A pool of TLS connections to entry nodes.''' def __init__(self): logging.debug('Connection pool created.') self._connection_map = {} self._pending_map = {}
[docs] def getConnection(self, relay): '''Return a deferred which will fire (if connection attempt is successful) with a Connection Protocol made to *relay*. There are three general cases to handle for incoming connection requests: 1. We already have an open TLS connection to the requested relay. In this case, immediately callback the deferred with the open connection. 2. We're currently trying to connect to this relay. In this case, add the request to a pending request list for this relay. When the connection is made successfully, callback all pending request deferreds with the Connection, or errback all pending request deferreds on failure. 3. We have no open or pending connections to this relay (i.e. this is the first connection request to this relay). In this case, create a new list of pending requests for this connection and add the current request. Create an SSL endpoint and add an appropriate callback and errback. If the request is successful, callback all pending requests with the open connection when it opens; errback all pending requests on failure. :param stem.descriptor.server_descriptor.RelayDescriptor relay: relay to make a TLS connection to :returns: **twisted.internet.defer.Deferred** which, on success, will callback with an oppy.connection.connection.Connection Protocol object ''' from twisted.internet import reactor d = defer.Deferred() # case 1 if relay.fingerprint in self._connection_map: d.callback(self._connection_map[relay.fingerprint]) # case 2 elif relay.fingerprint in self._pending_map: self._pending_map[relay.fingerprint].append(d) # case 3 else: connection_defer = endpoints.connectProtocol( endpoints.SSL4ClientEndpoint(reactor, relay.address, relay.or_port, TLSClientContextFactory()), Connection(relay) ) connection_defer.addCallback(self._connectionSucceeded, relay.fingerprint) connection_defer.addErrback(self._connectionFailed, relay.fingerprint) self._pending_map[relay.fingerprint] = [] self._pending_map[relay.fingerprint].append(d) return d
def _connectionSucceeded(self, result, fingerprint): '''For every pending request for this connection, callback the request deferred with this open connection, then remove this connection from the pending map and add to the connection map. Called when the TLS connection to the IP of relay with *fingerprint* opens successfully. :param oppy.connection.connection.Connection result: the successfully opened connection :param str fingerprint: fingerprint of relay we have connected to ''' for request in self._pending_map[fingerprint]: request.callback(result) del self._pending_map[fingerprint] self._connection_map[fingerprint] = result def _connectionFailed(self, reason, fingerprint): '''For every pending request for this connection, errback the request deferred. Remove this connection from the pending map. Called when the TLS connection to the IP of relay with *fingerprint* fails. :param reason reason: reason this connection failed :param str fingerprint: fingerprint of the relay this connection failed to ''' msg = "Connection to {} failed: {}.".format(fingerprint, reason) logging.debug(msg) for request in self._pending_map[fingerprint]: request.errback(reason) del self._pending_map[fingerprint]
[docs] def removeConnection(self, fingerprint): '''Remove the connection to relay with *fingerprint* from the connection pool. :param str fingerprint: fingerprint of connection to remove ''' if fingerprint in self._connection_map: del self._connection_map[fingerprint]
[docs] def shouldDestroyConnection(self, fingerprint): '''Return **True** if ConnectionPool thinks we should destroy the TLS connection to relay with *fingerprint*. Called when the number of circuits on a connection drops to zero. .. note:: For now, we always return True. Eventually, we may want to maintain a connection to any guards, even if there are no currently open circuits. :param str fingerprint: fingerprint of connection to check :returns: **bool** **True** if we think this connection should be destroyed ''' return True