Exemplo n.º 1
0
class TcpTunnel(ImmortalThread):
    def __init__(self, vcp):
        #super(TcpTunnel, self).__init__(self)
        ImmortalThread.__init__(self)
        self._needs_reconfig = 0
        # link to the port object
        self._vcp = vcp
        self._lock = Lock()
        self._op_lock = Lock()
        # list of operations to apply to an {out|in}bound tcp 
        # segment. In the future this might include operations 
        # such as encryption or compression - for now, only the
        # header info. that is applied by devices such as 
        # Lantronix's UDS-10 is supported.  Up refers to what is
        # being applied to outbound tcp data, down to received.
        # Methods should be added to these lists in the order they
        # are to be applied.
        self._up_segment_ops = []
        self._down_segment_ops = []
        # transaction identifier - used only in vcp mode.
        self.__tid = 0
        self._pending_tid = 0
        # one and only poll object.  socket, serial fd and
        # command pipe are all polled.
        self._poll_obj = None
        # command pipe allows other threads to insert control
        # messages.
        self._cmd_pipe = None
        # both sides (serial & socket) of the tunnel
        self._sock_listen_fd = None
        self._sock_fd = None
        self._serial_port = None
        # tcp state management
        self._is_connected = 0
        self.is_active = 0
        # tunnel statistics
        self.tcp_bytes_rcvd = 0
        self.tcp_bytes_sent = 0
        self.serial_bytes_rcvd = 0
        self.serial_bytes_sent = 0
        self.connection_attempts = 0
            
    def configure(self, config):
        self.tty = '/dev/tty' + config['dev'][-2:]
        self.tcp_port = int(config['tcp_port'])
        self.mode = config['mode']
        self.timeout_msec = config['p_timeout_msec']

        if self.mode == 'vcp':
            if self._up_segment_ops.count(self._add_vcp_header) == 0:
                self._up_segment_ops.append(self._add_vcp_header)
            if self._down_segment_ops.count(self._remove_vcp_header) == 0:
                self._down_segment_ops.append(self._remove_vcp_header)
        self.is_server = int(config['is_server'])
        if self.is_server == 0:
            self.host = config['host']  # who we're connecting to.
        if self.is_active:
            # tunnel is being reconfigured "in flight".
            self._needs_reconfig = 1
            self._send_cmd('reconfig')
            if self._is_in_accept():
                self._clear_accept()
                    
    def run(self):
        self._needs_reconfig = 0
        if self.is_active:
            # we've restarted due to a configuration change
            self.start_tunnel()
        else:
            if not self._cmd_pipe:
                # set up the command pipe and begin to build the poll obj.
                self._cmd_pipe = os.pipe()
                self._poll_obj = select.poll()
                self._poll_obj.register(self._cmd_pipe[READ],
                    select.POLLIN | select.POLLERR | select.POLLHUP)
        while 1:
            # no poll timeout, wait here until kicked to start.
            evt = self._poll_obj.poll(-1)
            if evt[0][0] == self._cmd_pipe[READ]:
                if evt[0][1] == select.POLLIN:
                    cmd = os.read(self._cmd_pipe[READ], 32)
                    if cmd.find('start') >= 0:
                        self.start_tunnel()
            # cmd pipe poll err, critical, hard restart
            self._cmd_pipe = None
            self._poll_obj = None
            raise ERestart()
                        
    def start_tunnel(self):
        if self is not currentThread():
            self._send_cmd('start')
            return
        self._op_lock.acquire()
        try:
            self.is_active = 1
            # set up Port object for the tunnel that reads\writes to the 
            # slave device file of the pseudo-terminal pair. 
            if not self._serial_port:
                self._serial_port = Port()
            cfg = self._vcp.configuration()
            cfg['dev'] = self.tty
            cfg['name'] = '_slave'
            cfg['parent'] = self._vcp
            self._serial_port.configure(cfg)
            self._op_lock.release()
        except:
            self._op_lock.release()
        while self.is_active:
            if not self._serial_port.is_open():
                self._serial_port.open()
                self._serial_port.drain()
            try:
                if self.is_server:
                    self._do_listen()
                else:
                    self._do_connect()
            except:
                msglog.exception()
                if self._serial_port and not self._serial_port.is_open():
                    self._serial_port.close()

    def stop_tunnel(self):
        self.is_active = 0
        if self is not currentThread():
            self._send_cmd('stop')
            if self._is_in_accept():
                self._clear_accept()
        else:
            self._op_lock.acquire()
            try:
                self._tear_down_fds()
            finally:
                self._op_lock.release()
            #raise ERestart()
        
    def is_connected(self):
        self._op_lock.acquire()
        result = self._is_connected
        self._op_lock.release()
        return result
                                
    def _create_socket(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        return s
            
    def _close_socket(self):
        self._is_connected = 0
        self._sock_fd.close()
            
    def _send_cmd(self, cmd):
        self._lock.acquire()
        try:
            os.write(self._cmd_pipe[WRITE], cmd)
        finally:
            self._lock.release()
            
    def _do_connect(self):
        while self.is_active:
            self._sock_fd = self._create_socket()
            while not self._is_connected:
                self.connection_attempts += 1
                try:
                    self._sock_fd.connect((self.host, self.tcp_port))
                    self._is_connected = 1
                except socket.gaierror, e:
                    # host related error, ie. hostname not resolving - possibly transient
                    self.connection_attempts += 1
                    msglog.log('VCP', WARN, 'Error resolving hostname %s.' % self.host)
                    time.sleep(60)
                    raise EConnectionError
                except socket.error, e:
                    # connection error, possibly transient - sleep for a bit and retry
                    self.connection_attempts += 1
                    time.sleep(30) 
            if self._needs_reconfig:
                self._is_connected = 0
                self._tear_down_fds()
                raise ERestart()
            # loop in _do_tunnel until the tcp connection or the framework 
            # based consumer (ie. protocol) "goes away".
            self._do_tunnel()
            self._is_connected = 0