コード例 #1
0
ファイル: endpoint.py プロジェクト: lapd-c/curveball
    def create_hole_msg_list(self, hole_start, hole_end, max_msg_size):
        """
        Create a list of messages that can be used to fill
        the "oldest" hole that the remote needs us to fill
        """

        content = self.pending_out.peek(hole_start, hole_end + 1)

        QE2LOG.warn('creating descriptor for hole [%d %d]', hole_start,
                    hole_end)
        QE2LOG.warn('hole length %d content length %d',
                    1 + hole_end - hole_start, len(content))

        # This is not very efficient in terms of memory or CPU,
        # but it's not the bottleneck.
        #
        msgs = list()
        while content:
            content_prefix = content[:max_msg_size]
            content = content[max_msg_size:]

            msg = Qe2DataMsg(self.uuid,
                             content_prefix,
                             0,
                             send_offset=hole_start)
            hole_start += len(content_prefix)

            msgs.append(msg)

        # reverse the list to make it easier to pop off the msgs.
        #
        msgs.reverse()

        return msgs
コード例 #2
0
    def connectionFailed(self, reason):
        """
        Could not connect to the SOCKS server
        """

        QE2LOG.warn('SocksShim.connectionFailed [%s] %s', str(self),
                    str(reason))
コード例 #3
0
    def start_one(self):
        """
        Take a descriptor from the latent list, and attempt
        to start it
        """

        if not self.latent:
            QE2LOG.warn('Qe2ChannelManager.start_one: no latent channels')
            return None

        latent = self.latent[0]
        self.latent = self.latent[1:]

        QE2LOG.info('Qe2ChannelManager.start_one: starting %s', str(latent))

        latent.start_time = time.time()
        self.starting.append(latent)
        channel = latent.create()

        channel.connect()

        looper = LoopingCall(channel.pusher)
        channel.set_looper(looper)
        looper.start(latent.loop_interval, now=True)

        latent.channel = channel

        return channel
コード例 #4
0
ファイル: channel.py プロジェクト: lapd-c/curveball
    def connection_failed(self, reason):
        """
        Couldn't connect, or lost the connection

        This will usually be subclassed, because how to react to failure
        depends on details of the connection.
        """

        QE2LOG.warn('Qe2Channel.connection_failed(): %s', str(reason))
コード例 #5
0
    def connectionFailed(self, reason=None):
        """
        The app failed to connect, or something else happened.

        Check: can this even happen to an endpoint?
        """

        QE2LOG.warn('Qe2ClientTop.connectionFailed')
        self.app_connected = False
コード例 #6
0
        def nudger():

            man.update()

            QE2LOG.info('running %d starting %d latent %d', len(man.running),
                        len(man.starting), len(man.latent))
            QE2LOG.debug(str(man))

            if len(man.running) == 0:
                QE2LOG.warn('no channels running: starting latent channel')
                man.start_one()
コード例 #7
0
    def data_recv_5(self):
        """
        Handle the SOCKS responses
        """

        # QE2LOG.debug('incoming len %d', len(self.shim_recv_buf))

        if self.handshake_state == self.HANDSHAKE_STATE_1:
            if len(self.shim_recv_buf) < 2:
                return

            if self.shim_recv_buf[:2] != '\x05\x00':
                QE2LOG.warn('failed SOCKS5 first response')
                self.transport.loseConnection()
            else:
                self.shim_recv_buf = self.shim_recv_buf[2:]
                self.handshake_state = self.HANDSHAKE_STATE_2

                self.handshake_socks5()

        elif self.handshake_state == self.HANDSHAKE_STATE_2:

            # NOTE: we only handle the case where the server returns
            # us an ordinary IPv4 address (which is expected for a
            # TCP connect request).  If a server returns a DNS-type
            # address, which has a variable length, this function
            # can't parse it yet.

            resp_len = 10
            if len(self.shim_recv_buf) < resp_len:
                return

            response = self.shim_recv_buf[:resp_len]
            self.shim_recv_buf = self.shim_recv_buf[resp_len:]

            expected_prefix = '\x05\x00\x00\x01'

            if response[:len(expected_prefix)] != expected_prefix:
                QE2LOG.warn('failed SOCKS5 second response [prefix]')
                self.transport.loseConnection()
                return

            # NOTE: we do not attempt to validate the returned IPv4
            # address or ephemeral port.  We could check them for
            # basic sanity (make sure they're valid, at least) but
            # we can't check that they're *correct*.

            self.handshake_state = self.HANDSHAKE_COMPLETE

        else:
            QE2LOG.error('SocksShim.data_recv_5 unhandled state')
            assert (0)
コード例 #8
0
    def do_connect_failed(self, reason):
        """
        If we haven't already tried to reconnect too many times,
        wait a brief moment and try again
        """

        if self._connection_attempts < self._max_connection_attempts:
            self._connection_attempts += 1
            QE2LOG.debug('TCP4SocksClientEndpoint.do_connect_failed: ' +
                         'trying again')
            self._reactor.callLater(0.5, self.do_connect)
        else:
            QE2LOG.warn('TCP4SocksClientEndpoint.do_connect_failed: giving up')
コード例 #9
0
    def handshake_socks5(self):
        """
        SOCKS5 handshake

        We only support a subset of SOCKS5 -- TCP, no authentication,
        IPv4 addresses (if an address is given)

        The SOCKS5 handshake has two states: requesting a connection
        to the SOCKS server itself, and then requesting a connection
        via that server.  If handshake_state == 1, then this sends the
        first request; otherwise it sends the second.
        """

        # print 'SocksShim.handshake_socks5 %s' % str(self.handshake_state)

        # TODO: check that the socks_dest_host is a string, and short
        # enough to be sane.

        if self.handshake_state == self.HANDSHAKE_STATE_1:
            request = '\x05\x01\x00'
            self.transport.write(request)
        elif self.handshake_state == self.HANDSHAKE_STATE_2:
            # Even though this looks like the same request that
            # we send in state 1, the bytes have a different semantic
            # so I don't want to combine them.
            #
            request = '\x05\x01\x00'

            # Figure out whether the address is a dotted quad already,
            # or whether we need to ask for resolution.  If we try to
            # convert it directly to binary, and we get a socket.error,
            # then it's a DNS name.

            try:
                addr = socket.inet_aton(self.socks_dest_host)
            except socket.error:
                request += '\x03'
                request += chr(len(self.socks_dest_host))
                request += self.socks_dest_host
            except BaseException, exc:
                QE2LOG.warn('SocksShim.handshake_socks5 (2) ERR [%s]',
                            str(exc))
                self.transport.loseConnection()
                return
            else:
                request += '\x01'
                request += addr

            request += struct.pack('!H', self.socks_dest_port)

            self.transport.write(request)
コード例 #10
0
    def is_connected(self):
        """
        If the Curveball client fails, or is disconnected, then
        the channel is not connected
        """

        if not self.curveball_client:
            return False
        else:
            retcode = self.curveball_client.poll()
            if retcode != None:
                QE2LOG.warn('curveball-client %d exited [%d]',
                            self.curveball_client.pid, retcode)
                self.curveball_client = None
                return False
            return Qe2Channel.is_connected(self)
コード例 #11
0
    def create(self):
        """
        Instantiate the channel object
        """

        try:
            # The endpoint might not have been created
            # when the descriptor was initialized; make sure
            # that it's initialized now
            #
            self.kwargs['endpoint'] = self.endpoint

            self.channel = self.objclass(**self.kwargs)

        except BaseException, exc:
            QE2LOG.warn('Qe2ChannelDescriptor.create: failed [%s]', str(exc))
            return None
コード例 #12
0
    def connectionMade(self):
        """
        The app has connected.

        We only permit a single connection; if we have a connection,
        reject any others
        """

        if self.factory.endpoint.top:
            QE2LOG.warn('Qe2ClientTop.connectionMade: ' +
                        'rejecting second connection')
            self.transport.loseConnection()
        else:
            QE2LOG.info('Qe2ClientTop.connectionMade: ' +
                        'accepting first connection')
            self.factory.endpoint.top = self
            self.app_connected = True
コード例 #13
0
    def connectionLost(self, reason=None):
        """
        The app has closed its connection with us.

        We need to shut down, but we need to do it in a reasonable way.
        It might make sense to leave the channels open for a few moments,
        and pass some chaff through, or it might make sense to shut them
        down all together (i.e., if the browser exited, or the user
        closed a page in the browser)
        """

        QE2LOG.debug('Qe2ClientTop.connectionLost')
        QE2LOG.warn('SHUTTING DOWN QUILT')

        self.app_connected = False

        # Try to kill any lingering channels and their subprocesses
        #
        if self.factory.endpoint.chanman:
            self.factory.endpoint.chanman.stop_all()
コード例 #14
0
    def disconnect(self):
        """
        Disconnect with the other end of this channel
        """

        QE2LOG.info('DROPPING CURVEBALL CONNECTION')

        # cleanup Curveball-specific stuff
        #
        if self.curveball_client:
            try:
                QE2LOG.info('terminating curveball-client %d',
                            self.curveball_client.pid)
                self.curveball_client.terminate()
                QE2LOG.info('waiting for curveball-client')
                exit_code = self.curveball_client.wait()
                QE2LOG.warn('curveball-client is dead [%d]', exit_code)
            except BaseException, exc:
                QE2LOG.info('curveball-client did not die [%s]', str(exc))

            self.curveball_client = None
コード例 #15
0
ファイル: endpoint.py プロジェクト: lapd-c/curveball
    def fill_remote_hole(self):
        """
        Select the "oldest" hole from remote_holes,
        and create a Qe2DataMsg to fill it.

        Return None if there is no hole to fill.
        """

        self.prune_remote_holes()

        if not self.remote_holes:
            return None

        (hole_start, hole_end) = sorted(self.remote_holes)[0]

        # If the hole is too large to stuff into one message,
        # put as much as we can
        #
        if (1 + hole_end - hole_start) > Qe2Msg.MAX_PAYLOAD_LEN:
            hole_end = hole_start + Qe2Msg.MAX_PAYLOAD_LEN - 1

        QE2LOG.debug('fill_remote_hole: filling [%d %d]', hole_start, hole_end)

        try:
            filler = self.pending_out.peek(hole_start, hole_end + 1)
            if filler:
                # print 'fill_remote_hole: filler is [%s]' % filler

                hole_msg = Qe2DataMsg(self.uuid,
                                      filler,
                                      0,
                                      send_offset=hole_start)
                return hole_msg
            else:
                QE2LOG.warn('fill_remote_hole: filler [%d, %d] not found',
                            hole_start, hole_end)
                return None
        except BaseException, exc:
            QE2LOG.warn('fill_remote_hole: exception [%s]', str(exc))
            return None
コード例 #16
0
    def update(self):
        """
        Update the starting, running, and latent lists to reflect
        the current state of each descriptor.

        The assumption is that instantiated descriptors go from
        starting to running to latent as they get older.  Sometimes,
        however, they get stuck in starting and we have to forcibly
        stop them.
        """

        new_latent = self.latent[:]
        new_starting = list()
        new_running = list()

        now = time.time()

        for desc in self.starting:
            if desc.channel.is_connected():
                desc.start_time = None
                self.running.append(desc)
            elif (now - desc.start_time) > self.max_starting_wait:
                QE2LOG.warn('killing channel stuck in start')
                desc.channel.disconnect()
                new_latent.append(desc)
            else:
                QE2LOG.warn('channel lingering in start state')
                new_starting.append(desc)

        for desc in self.running:
            if desc.channel.is_connected():
                new_running.append(desc)
            else:
                QE2LOG.info('a channel has been lost')
                new_latent.append(desc)

        self.latent = new_latent
        self.starting = new_starting
        self.running = new_running
コード例 #17
0
ファイル: endpoint.py プロジェクト: lapd-c/curveball
    def handle_init(self, msg):
        """
        Handle receipt of a OP_INIT msg.

        Note that it is an error for this to be received
        by a server, so QuiltServers should subclass this method

        The server_quilt_uuid is the uuid assigned to this
        quilt by the server.  If this is the first OP_INIT
        we've gotten from the server, then remember make note
        of it.  If it's not the first OP_INIT, then check that
        the server_quilt_uuid matches previous server_quilt_uuids,
        and drop the connection if it does not.
        """

        server_quilt_uuid = msg.data[:16]

        if self.server_quilt_uuid == None:
            QE2LOG.info('got server_quilt_uuid %s',
                        server_quilt_uuid.encode('hex'))
            self.server_quilt_uuid = server_quilt_uuid
        elif self.server_quilt_uuid != server_quilt_uuid:
            QE2LOG.warn('server_quilt_uuid mismatch: expected %s got %s',
                        self.server_quilt_uuid.encode('hex'),
                        server_quilt_uuid.encode('hex'))
            QE2LOG.info('dropping quilt')

            # If there is a channel manager, ask it to stop
            # all channels
            #
            if self.chanman:
                QE2LOG.warn('stopping all connections')
                self.chanman.stop_all()
            else:
                QE2LOG.error('no chanman?')

            # reactor.callLater(2, reactor.stop)
            reactor.stop()
コード例 #18
0
    def data_recv_4(self):
        """
        Handle the SOCKS4 response
        """

        needed = 8

        if len(self.shim_recv_buf) < needed:
            return

        response = self.shim_recv_buf[:needed]
        self.shim_recv_buf = self.shim_recv_buf[needed:]

        (_null, status, _port, _ipaddr) = struct.unpack('!BBHL', response)

        if status != 0x5a:
            QE2LOG.warn('SOCKS4 connection failed %x %x %x', status, _ipaddr,
                        _port)
            self.transport.loseConnection()
        else:
            self.handshake_state = self.HANDSHAKE_COMPLETE

        return
コード例 #19
0
    def normalize_segments(self):
        """
        Coalesce adjacent segments
        """

        # print 'input ' + str(self.segments)

        # Make sure the segments are in offset order
        #
        new_segs = sorted(self.segments, key=lambda seg: seg[FIRST_OFF_IND])

        # Make sure that the segments don't overlap
        # (this is just a sanity check)
        for ind in xrange(len(new_segs) - 1):
            if new_segs[ind][LAST_OFF_IND] > new_segs[ind + 1][FIRST_OFF_IND]:
                QE2LOG.warn('WHOOPS: overlapping segments at %d [%s] and [%s]',
                            ind, str(new_segs[ind]), str(new_segs[ind + 1]))

        # Coalesce: scan through the list of segments from start to end.
        # If two segments are adjacent, set the second segment to be the
        # concatenation of the two segments, and set the first to be None.
        #
        for ind in xrange(len(new_segs) - 1):
            if (new_segs[ind][LAST_OFF_IND] == (
                    new_segs[ind + 1][FIRST_OFF_IND] - 1)):
                new_segs[ind + 1] = [
                    new_segs[ind][FIRST_OFF_IND],
                    new_segs[ind + 1][LAST_OFF_IND],
                    new_segs[ind][DATA_IND] + new_segs[ind + 1][DATA_IND]
                ]
                new_segs[ind] = None

        # Remove any segments that have been absorbed.
        #
        new_segs = [new_seg for new_seg in new_segs if new_seg]

        self.segments = new_segs
コード例 #20
0
ファイル: server.py プロジェクト: lapd-c/curveball
    def dataReceived(self, data):

        QE2LOG.debug('dataReceived marker %d', self.marker)

        self.recv_buf += data

        (msgs, self.recv_buf) = Qe2Msg.recv(self.recv_buf)

        if not msgs:
            return

        # The first message on a channel MUST be an OP_CHAN message
        #
        # NOTE: in a full-featured channel, this message will describe the
        # channel and include parameters that the server should use,
        # but this is not implemented.  We always use the same channel
        # parameters.

        if not self.uuid:
            first_msg = msgs[0]

            if first_msg.opcode != Qe2Msg.OP_CHAN:
                QE2LOG.warn('channel started/resumed without OP_CHAN msg')
                self.loseConnection()

            msgs = msgs[1:]

            self.uuid = first_msg.uuid

            listener = self.factory.server_listener

            if not (self.uuid in listener.uuid2server):
                QE2LOG.info('Creating Qe2Server for uuid %s',
                            str(self.uuid).encode('hex'))
                listener.uuid2server[self.uuid] = Qe2Server(self.uuid)

            self.server = listener.uuid2server[self.uuid]

            # Register ourselves with our server endpoint
            #
            self.server.add_bottom(self)

            # We should get the parameters in the first OP_CHAN message
            #
            # TODO: this doesn't pay attention to the first message, but
            # instead makes assumptions about the parameters
            #
            if not self.channel:
                QE2LOG.debug('CREATING CHANNEL ON SERVER')
                self.channel = Qe2SocketServerChannel(self.transport,
                                                      self.server)

            # Immediately tell the client the UUID we selected for our
            # local UUID, so it can tell if we crash or the connections
            # are redirected.
            #
            # TODO: it would be better if this message didn't need to
            # happen immediately (because this gives the channel a
            # fingerprint) but instead was based on the channel parameters.
            #
            # Since we're ignoring the channel paramters in this version,
            # we don't have much choice, but a smarter channel would wait.
            #
            resp_msg = Qe2Msg(Qe2Msg.OP_INIT, self.uuid,
                              self.server.local_uuid, 0)
            self.transport.write(resp_msg.pack())

        endpoint = self.server

        endpoint.process_msgs(msgs)
コード例 #21
0
ファイル: channel.py プロジェクト: lapd-c/curveball
    def connectionFailed(self):
        """
        twisted connectionFailed
        """

        QE2LOG.warn('Qe2ChannelWorker: connectionFailed')
コード例 #22
0
ファイル: server.py プロジェクト: lapd-c/curveball
 def connectionFailed(self):
     QE2LOG.warn('Qe2ServerBottom.connectionFailed()')