def handshake_socks4(self): """ SOCKS4 handshake The danted SOCKS server doesn't seem to understand SOCKS4a, so this does not do the SOCKS4a trick of requesting DNS resolution on the proxy. The request has five fields: - The protocol number (4) - The request type (1 == TCP connection - The destination port (2 bytes, network order) - The destination ipaddr (4 bytes, network order) - The nul-terminated user-ID. (1 byte, always 0) """ # print 'SocksShim.handshake_socks4' request = struct.pack('!BBH', 4, 1, self.socks_dest_port) try: addr = socket.gethostbyname(self.socks_dest_host) except BaseException, exc: QE2LOG.error('SocksShim.handshake_socks4 ERROR [%s]', str(exc)) self.transport.loseConnection() return
def connectionLost(self, reason=twisted.internet.protocol.connectionDone): """ Connection to the SOCKS server was lost during handshake """ QE2LOG.error('SocksShim.connectionLost [%s] %s', str(self), str(reason))
def connectionMade(self): """ After the connection is established, send the SOCKS4/SOCKS4a request to the server. """ # print '__SocksShim.connectionMade' self.socks_dest_host = self.factory.socks_dest_host self.socks_dest_port = self.factory.socks_dest_port self.socks_dest_class = self.factory.socks_dest_class # Decide whether to use SOCKS4 or SOCKS5 by testing # whether we have a dotted quad or something else. # If we already have a dotted quad address, then use # SOCKS4; otherwise use SOCKS5 with DNS resolution # try: _addr = socket.inet_aton(self.socks_dest_host) except socket.error: self.socks_dest_ver = 5 self.handshake_socks5() except BaseException, exc: QE2LOG.error('SocksSum.connectionMade() [%s]', str(exc)) self.transport.loseConnection()
def del_bottom(self, bottom): """ Remove a *Bottom instance from this endpoint If the instance isn't part of this endpoint, print a diagnostic and swallow the error """ try: self.bottoms.remove(bottom) except KeyError, exc: QE2LOG.error('Qe2Endpoint.del_bottom: missing bottom')
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)
def segment_covers(segment, hole_start, hole_last): """ Return a segment representing the subset of the hole that is covered by the segment, or None of if the segment does not overlap the hole """ hole_len = 1 + hole_last - hole_start seg_start = segment[FIRST_OFF_IND] seg_last = segment[LAST_OFF_IND] seg_data = segment[DATA_IND] # Need to be very careful with the extents here # # First check whether the hole overlaps the segment # at all, and if not, return None. # # Then check whether the segment first within the hole. # # Finally check each of the edge cases # if (seg_last < hole_start) or (seg_start > hole_last): # print 'case X' return None elif (seg_start <= hole_start) and (seg_last > hole_last): start = hole_start - seg_start last = start + hole_len new_seg = [hole_start, hole_last, seg_data[start:last]] # print 'case 0 hole [%d, %d] %s' % ( # hole_start, hole_last, str(new_seg)) return new_seg elif (seg_start >= hole_start) and (seg_last <= hole_last): return segment elif seg_start >= hole_start: return [seg_start, hole_last, seg_data[:1 + hole_last - seg_start]] elif seg_last >= hole_start: return [hole_start, seg_last, seg_data[hole_start - seg_start:]] else: QE2LOG.error('unhandled case') assert (False)
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()
def dataReceived(self, data): """ Receive data in response to a handshake message, and dispatch to the correct handler (depending on which handshake we're doing If the handshake is complete, then monkey-patch this instance to have the desired class and initialize the new instance (with connectionMade() and dataReceived, if appropriate) """ # print '__SocksShim.dataReceived' self.shim_recv_buf += data if self.socks_dest_ver == 4: self.data_recv_4() elif self.socks_dest_ver == 5: self.data_recv_5() else: QE2LOG.error('unhandled SOCKS version [%s]', str(self.socks_dest_ver)) assert (0) if self.handshake_state == self.HANDSHAKE_COMPLETE: # If we've reached this point, then everything is OK, # at least as far as we can tell at this point. # # Monkey-patch this instance, connect to the new class, # and then pretend to receive any pending data received = self.shim_recv_buf self.__class__ = self.socks_dest_class self.__init__() self.connectionMade() if received: self.dataReceived(received)
def process_msgs(self, msgs): """ Process messages received by a channel """ max_offset = -1 delivered = False old_remote_ack_recv = self.remote_ack_recv for msg in msgs: QE2LOG.debug('RECEIVE MSG %s', str(msg)) # update max_offset and remote_ack_recv, no # matter what the message type is # if max_offset < msg.ack_send: max_offset = msg.ack_send if self.remote_ack_recv < msg.ack_recv: self.remote_ack_recv = msg.ack_recv if self.remote_ack_send < msg.ack_send: self.remote_ack_send = msg.ack_send if msg.opcode == Qe2Msg.OP_DATA: if len(msg.data) > 0: self.deliver(msg.send_offset, msg.data) delivered = True elif msg.opcode == Qe2Msg.OP_PING: pass elif msg.opcode == Qe2Msg.OP_HOLE: if self.remote_ack_recv > msg.hole[0]: QE2LOG.info('UNEXPECTED hole start before remote_ack_recv') self.add_remote_hole(msg.hole[0], msg.hole[0] + msg.hole[1] - 1) elif msg.opcode == Qe2Msg.OP_CHAN: QE2LOG.info('got OP_CHAN message') pass elif msg.opcode == Qe2Msg.OP_HALT: # TODO: this should close down the quilt (not just # one particular channel) # QE2LOG.info('got OP_HALT message') elif msg.opcode == Qe2Msg.OP_INIT: self.handle_init(msg) else: QE2LOG.error('UNHANDLED msg %d', msg.opcode) # If this sequence of msgs included delivered data, then # see if there's anything to push to the app # if delivered: self.pending_to_app() if max_offset > self.ack_recv: QE2LOG.info('max_offset %d > self.ack_rev %d: something missing', max_offset, self.ack_recv) self.add_local_hole(self.ack_recv + 1, max_offset) QE2LOG.debug('LOCAL holes %s', str(sorted(self.local_holes))) # We've gotten acknowledgment from the remote endpoint # for additional data. Throw away our old copy. # # TODO: it is much more efficient to delete larger chunks # periodically rather than small chunks constantly. # if old_remote_ack_recv < self.remote_ack_recv: self.pending_out.discard(self.remote_ack_recv - old_remote_ack_recv)
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) else: QE2LOG.error('SocksShim.handshake_socks5 unhandled state') 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)
def handle_init(self, msg): QE2LOG.error('server received an OP_INIT msg; unexpected')