예제 #1
0
class SessionManager(Logger):
    implements(IReadDescriptor)

    def __init__(self, relay, start_port, end_port):
        self.relay = relay
        self.ports = deque((i, i + 1) for i in xrange(start_port, end_port, 2))
        self.bad_ports = deque()
        self.sessions = {}
        self.watcher = _conntrack.ExpireWatcher()
        self.active_byte_counter = 0  # relayed byte counter for sessions active during last speed measurement
        self.closed_byte_counter = 0  # relayed byte counter for sessions closed after last speed measurement
        self.bps_relayed = 0
        if RelayConfig.traffic_sampling_period > 0:
            self.speed_calculator = RecurrentCall(
                RelayConfig.traffic_sampling_period, self._measure_speed)
        else:
            self.speed_calculator = None
        reactor.addReader(self)

    def _measure_speed(self):
        start_time = time()
        current_byte_counter = sum(session.relayed_bytes
                                   for session in self.sessions.itervalues())
        self.bps_relayed = 8 * (
            current_byte_counter + self.closed_byte_counter -
            self.active_byte_counter) / RelayConfig.traffic_sampling_period
        self.active_byte_counter = current_byte_counter
        self.closed_byte_counter = 0
        us_taken = int((time() - start_time) * 1000000)
        if us_taken > 10000:
            log.warning(
                'Aggregate speed calculation time exceeded 10ms: %d us for %d sessions'
                % (us_taken, len(self.sessions)))
        return KeepRunning

    # implemented for IReadDescriptor
    def fileno(self):
        return self.watcher.fd

    def doRead(self):
        stream = self.watcher.read()
        if stream:
            stream.expired_func()

    def connectionLost(self, reason):
        reactor.removeReader(self)

    # port management
    def get_ports(self):
        if len(self.bad_ports) > len(self.ports):
            log.debug('Excessive amount of bad ports, doing cleanup')
            self.ports.extend(self.bad_ports)
            self.bad_ports = deque()
        try:
            return self.ports.popleft()
        except IndexError:
            raise RelayPortsExhaustedError()

    def set_bad_ports(self, ports):
        self.bad_ports.append(ports)

    def free_ports(self, ports):
        self.ports.append(ports)

    # called by higher level
    def _find_session_key(self, call_id, from_tag, to_tag):
        key_from = (call_id, from_tag)
        if key_from in self.sessions:
            return key_from
        if to_tag:
            key_to = (call_id, to_tag)
            if key_to in self.sessions:
                return key_to
        return None

    def has_session(self, call_id, from_tag, to_tag=None, **kw):
        return any((call_id, tag) in self.sessions
                   for tag in (from_tag, to_tag) if tag is not None)

    def update_session(self,
                       dispatcher,
                       call_id,
                       from_tag,
                       from_uri,
                       to_uri,
                       cseq,
                       user_agent,
                       type,
                       media=[],
                       to_tag=None,
                       **kw):
        key = self._find_session_key(call_id, from_tag, to_tag)
        if key:
            session = self.sessions[key]
            is_downstream = (session.from_tag != from_tag) ^ (type
                                                              == 'request')
            is_caller_cseq = (session.from_tag == from_tag)
            session.update_media(cseq, to_tag, user_agent, media,
                                 is_downstream, is_caller_cseq)
        elif type == 'reply' and not media:
            return None
        else:
            is_downstream = type == 'request'
            is_caller_cseq = True
            session = Session(self, dispatcher, call_id, from_tag, from_uri,
                              to_tag, to_uri, cseq, user_agent, media,
                              is_downstream, is_caller_cseq)
            self.sessions[(call_id, from_tag)] = session
            self.relay.add_session(dispatcher)
        return session.get_local_media(is_downstream, cseq, is_caller_cseq)

    def remove_session(self, call_id, from_tag, to_tag=None, **kw):
        key = self._find_session_key(call_id, from_tag, to_tag)
        try:
            session = self.sessions[key]
        except KeyError:
            log.warning(
                'The dispatcher tried to remove a session which is no longer present on the relay'
            )
            return None
        session.logger.info('removed')
        session.cleanup()
        self.closed_byte_counter += session.relayed_bytes
        del self.sessions[key]
        reactor.callLater(0, self.relay.remove_session, session.dispatcher)
        return session

    def session_expired(self, call_id, from_tag):
        key = (call_id, from_tag)
        try:
            session = self.sessions[key]
        except KeyError:
            log.warning(
                'A session expired but is no longer present on the relay')
            return
        session.logger.info('expired')
        session.cleanup()
        self.closed_byte_counter += session.relayed_bytes
        del self.sessions[key]
        self.relay.session_expired(session)
        self.relay.remove_session(session.dispatcher)

    def cleanup(self):
        if self.speed_calculator is not None:
            self.speed_calculator.cancel()
        for key in self.sessions.keys():
            self.session_expired(*key)

    @property
    def statistics(self):
        return [session.statistics for session in self.sessions.itervalues()]

    @property
    def stream_count(self):
        stream_count = {}
        for session in self.sessions.itervalues():
            for stream in set(chain(*session.streams.itervalues())):
                if stream.is_alive:
                    stream_count[stream.media_type] = stream_count.get(
                        stream.media_type, 0) + 1
        return stream_count
예제 #2
0
class SessionManager(Logger):
    implements(IReadDescriptor)

    def __init__(self, relay, start_port, end_port):
        self.relay = relay
        self.ports = deque((i, i+1) for i in xrange(start_port, end_port, 2))
        self.bad_ports = deque()
        self.sessions = {}
        self.watcher = _conntrack.ExpireWatcher()
        self.active_byte_counter = 0 # relayed byte counter for sessions active during last speed measurement
        self.closed_byte_counter = 0 # relayed byte counter for sessions closed after last speed measurement
        self.bps_relayed = 0
        if Config.traffic_sampling_period > 0:
            self.speed_calculator = RecurrentCall(Config.traffic_sampling_period, self._measure_speed)
        else:
            self.speed_calculator = None
        reactor.addReader(self)

    def _measure_speed(self):
        start_time = time()
        current_byte_counter = sum(session.relayed_bytes for session in self.sessions.itervalues())
        self.bps_relayed = 8 * (current_byte_counter + self.closed_byte_counter - self.active_byte_counter) / Config.traffic_sampling_period
        self.active_byte_counter = current_byte_counter
        self.closed_byte_counter = 0
        us_taken = int((time() - start_time) * 1000000)
        if us_taken > 10000:
            log.warn("Aggregate speed calculation time exceeded 10ms: %d us for %d sessions" % (us_taken, len(self.sessions)))
        return KeepRunning

    # implemented for IReadDescriptor
    def fileno(self):
        return self.watcher.fd

    def doRead(self):
        stream = self.watcher.read()
        if stream:
            stream.expired_func()

    def connectionLost(self, reason):
        reactor.removeReader(self)

    # port management
    def get_ports(self):
        if len(self.bad_ports) > len(self.ports):
            log.debug("Excessive amount of bad ports, doing cleanup")
            self.ports.extend(self.bad_ports)
            self.bad_ports = deque()
        try:
            return self.ports.popleft()
        except IndexError:
            raise RelayPortsExhaustedError()

    def set_bad_ports(self, ports):
        self.bad_ports.append(ports)

    def free_ports(self, ports):
        self.ports.append(ports)

    # called by higher level
    def _find_session_key(self, call_id, from_tag, to_tag):
        key_from = (call_id, from_tag)
        if key_from in self.sessions:
            return key_from
        if to_tag:
            key_to = (call_id, to_tag)
            if key_to in self.sessions:
                return key_to
        return None

    def has_session(self, call_id, from_tag, to_tag=None, **kw):
        return any((call_id, tag) in self.sessions for tag in (from_tag, to_tag) if tag is not None)

    def update_session(self, dispatcher, call_id, from_tag, from_uri, to_uri, cseq, user_agent, type, media=[], to_tag=None, **kw):
        key = self._find_session_key(call_id, from_tag, to_tag)
        if key:
            session = self.sessions[key]
            log.debug("updating existing session %s" % session)
            is_downstream = (session.from_tag != from_tag) ^ (type == "request")
            is_caller_cseq = (session.from_tag == from_tag)
            session.update_media(cseq, to_tag, user_agent, media, is_downstream, is_caller_cseq)
        elif type == "reply" and not media:
            return None
        else:
            is_downstream = type == "request"
            is_caller_cseq = True
            session = Session(self, dispatcher, call_id, from_tag, from_uri, to_tag, to_uri, cseq, user_agent, media, is_downstream, is_caller_cseq)
            self.sessions[(call_id, from_tag)] = session
            self.relay.add_session(dispatcher)
            log.debug("created new session %s" % session)
        return session.get_local_media(is_downstream, cseq, is_caller_cseq)

    def remove_session(self, call_id, from_tag, to_tag=None, **kw):
        key = self._find_session_key(call_id, from_tag, to_tag)
        try:
            session = self.sessions[key]
        except KeyError:
            log.warn("The dispatcher tried to remove a session which is no longer present on the relay")
            return None
        log.debug("removing session %s" % session)
        session.cleanup()
        self.closed_byte_counter += session.relayed_bytes
        del self.sessions[key]
        reactor.callLater(0, self.relay.remove_session, session.dispatcher)
        return session

    def session_expired(self, call_id, from_tag):
        key = (call_id, from_tag)
        try:
            session = self.sessions[key]
        except KeyError:
            log.warn("A session expired that was no longer present on the relay")
            return
        log.debug("expired session %s" % session)
        session.cleanup()
        self.closed_byte_counter += session.relayed_bytes
        del self.sessions[key]
        self.relay.session_expired(session)
        self.relay.remove_session(session.dispatcher)

    def cleanup(self):
        if self.speed_calculator is not None:
            self.speed_calculator.cancel()
        for key in self.sessions.keys():
            self.session_expired(*key)

    @property
    def statistics(self):
        return [session.statistics for session in self.sessions.itervalues()]

    @property
    def stream_count(self):
        stream_count = {}
        for session in self.sessions.itervalues():
            for stream in set(chain(*session.streams.itervalues())):
                if stream.is_alive:
                    stream_count[stream.media_type] = stream_count.get(stream.media_type, 0) + 1
        return stream_count
예제 #3
0
파일: relay.py 프로젝트: tmhenry/mediaproxy
class RelayClientProtocol(LineOnlyReceiver):
    noisy = False
    required_headers = {'update': set(['call_id', 'from_tag', 'from_uri', 'to_uri', 'cseq', 'user_agent', 'type']),
                        'remove': set(['call_id', 'from_tag']),
                        'summary': set(),
                        'sessions': set()}

    def __init__(self):
        self.command = None
        self.seq = None
        self._connection_watcher = None
        self._queued_keepalives = 0

    def _send_keepalive(self):
        if self._queued_keepalives >= 3:
            # 3 keepalives in a row didn't get an answer. assume connection is down.
            log.error("missed 3 keepalive answers in a row. assuming the connection is down.")
            # do not use loseConnection() as it waits to flush the output buffers.
            reactor.callLater(0, self.transport.connectionLost, failure.Failure(TCPTimedOutError()))
            return None
        self.transport.write("ping\r\n")
        self._queued_keepalives += 1
        return KeepRunning

    def connectionMade(self):
        peer = self.transport.getPeer()
        log.debug("Connected to dispatcher at %s:%d" % (peer.host, peer.port))
        if RelayConfig.passport is not None:
            peer_cert = self.transport.getPeerCertificate()
            if not RelayConfig.passport.accept(peer_cert):
                self.transport.loseConnection(CertificateSecurityError('peer certificate not accepted'))
        self._connection_watcher = RecurrentCall(RelayConfig.keepalive_interval, self._send_keepalive)

    def connectionLost(self, reason):
        if self._connection_watcher is not None:
            self._connection_watcher.cancel()
            self._connection_watcher = None
        self._queued_keepalives = 0

    def lineReceived(self, line):
        if line == 'pong':
            self._queued_keepalives -= 1
            return
        if self.command is None:
            try:
                command, seq = line.split()
            except ValueError:
                log.error("Could not decode command/sequence number pair from dispatcher: %s" % line)
                return
            if command in self.required_headers:
                self.command = command
                self.seq = seq
                self.headers = DecodingDict()
            else:
                log.error("Unknown command: %s" % command)
                self.transport.write("%s error\r\n" % seq)
        elif line == "":
            try:
                missing_headers = self.required_headers[self.command].difference(self.headers)
                if missing_headers:
                    for header in missing_headers:
                        log.error("Missing mandatory header '%s' from '%s' command" % (header, self.command))
                    response = "error"
                else:
                    try:
                        response = self.factory.parent.got_command(self.factory.host, self.command, self.headers)
                    except:
                        log.err()
                        response = "error"
            finally:
                self.transport.write("%s %s\r\n" % (self.seq, response))
                self.command = None
        else:
            try:
                name, value = line.split(": ", 1)
            except ValueError:
                log.error("Unable to parse header: %s" % line)
            else:
                try:
                    self.headers[name] = value
                except DecodingError, e:
                    log.error("Could not decode header: %s" % e)
예제 #4
0
class RelayClientProtocol(LineOnlyReceiver):
    noisy = False
    required_headers = {
        'update': {
            'call_id', 'from_tag', 'from_uri', 'to_uri', 'cseq', 'user_agent',
            'type'
        },
        'remove': {'call_id', 'from_tag'},
        'summary': set(),
        'sessions': set()
    }

    def __init__(self):
        self.command = None
        self.seq = None
        self.headers = DecodingDict()
        self._connection_watcher = None
        self._queued_keepalives = 0

    def _send_keepalive(self):
        if self._queued_keepalives >= 3:
            log.error(
                'missed 3 keepalive answers in a row. assuming the connection is down.'
            )
            # do not use loseConnection() as it waits to flush the output buffers.
            reactor.callLater(0, self.transport.connectionLost,
                              failure.Failure(TCPTimedOutError()))
            return None
        self.transport.write('ping' + self.delimiter)
        self._queued_keepalives += 1
        return KeepRunning

    def reply(self, reply):
        self.transport.write(reply + self.delimiter)

    def connectionMade(self):
        peer = self.transport.getPeer()
        log.info('Connected to dispatcher at %s:%d' % (peer.host, peer.port))
        if RelayConfig.passport is not None:
            peer_cert = self.transport.getPeerCertificate()
            if not RelayConfig.passport.accept(peer_cert):
                self.transport.loseConnection(
                    CertificateSecurityError('peer certificate not accepted'))
        self._connection_watcher = RecurrentCall(
            RelayConfig.keepalive_interval, self._send_keepalive)

    def connectionLost(self, reason=connectionDone):
        if self._connection_watcher is not None:
            self._connection_watcher.cancel()
            self._connection_watcher = None
        self._queued_keepalives = 0

    def lineReceived(self, line):
        if line == 'pong':
            self._queued_keepalives -= 1
            return
        if self.command is None:
            try:
                command, seq = line.split()
            except ValueError:
                log.error(
                    'Could not decode command/sequence number pair from dispatcher: %s'
                    % line)
                return
            if command in self.required_headers:
                self.command = command
                self.seq = seq
                self.headers = DecodingDict()
            else:
                log.error('Unknown command: %s' % command)
                self.reply('{} error'.format(seq))
        elif line == '':
            missing_headers = self.required_headers[self.command].difference(
                self.headers)
            if missing_headers:
                for header in missing_headers:
                    log.error('Missing mandatory header %r from %r command' %
                              (header, self.command))
                response = 'error'
            else:
                # noinspection PyBroadException
                try:
                    response = self.factory.parent.got_command(
                        self.factory.host, self.command, self.headers)
                except Exception:
                    log.exception()
                    response = 'error'
            self.reply('{} {}'.format(self.seq, response))
            self.command = None
        else:
            try:
                name, value = line.split(": ", 1)
            except ValueError:
                log.error('Unable to parse header: %s' % line)
            else:
                try:
                    self.headers[name] = value
                except DecodingError, e:
                    log.error('Could not decode header: %s' % e)