def _OH_session_answer(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: session = data['session'] sdp = data['sdp'] except KeyError: raise APIError('Invalid parameters: "session" and "sdp" must be specified') try: session_info = self.sessions_map[session] except KeyError: raise APIError('Unknown session specified: %s' % session) if session_info.direction != 'incoming': raise APIError('Cannot answer outgoing session') if session_info.state != 'connecting': raise APIError('Invalid state for session answer') data = {'request': 'accept'} jsep = {'type': 'answer', 'sdp': sdp} block_on(self.backend.janus_message(self.janus_session_id, session_info.janus_handle_id, data, jsep)) data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('%s answered session %s' % (session_info.account_id, session)) except APIError, e: log.error('session-answer: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def _OH_session_terminate(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: session = data['session'] except KeyError: raise APIError('Invalid parameters: "session" must be specified') try: session_info = self.sessions_map[session] except KeyError: raise APIError('Unknown session specified: %s' % session) if session_info.state not in ('connecting', 'progress', 'accepted', 'established'): raise APIError('Invalid state for session terminate: \"%s\"' % session_info.state) if session_info.direction == 'incoming' and session_info.state == 'connecting': data = {'request': 'decline', 'code': 486} else: data = {'request': 'hangup'} block_on(self.backend.janus_message(self.janus_session_id, session_info.janus_handle_id, data)) data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('%s terminated session %s' % (session_info.account_id, session)) except APIError, e: log.error('session-terminate: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def _OH_account_unregister(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: account = data['account'] except KeyError: raise APIError('Invalid parameters: "account" must be specified') try: account_info = self.accounts_map[account] except KeyError: raise APIError('Unknown account specified: %s' % account) handle_id = account_info.janus_handle_id if handle_id is not None: block_on(self.backend.janus_detach(self.janus_session_id, handle_id)) self.backend.janus_set_event_handler(handle_id, None) account_info.janus_handle_id = None self.account_handles_map.pop(handle_id) data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('Account %s will unregister' % account) except APIError, e: log.error('account-unregister: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def _OH_session_trickle(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: session = data['session'] candidates = data['candidates'] except KeyError: raise APIError('Invalid parameters: "session" and "candidates" must be specified') try: session_info = self.sessions_map[session] except KeyError: raise APIError('Unknown session specified: %s' % session) if session_info.state == 'terminated': raise APIError('Session is terminated') block_on(self.backend.janus_trickle(self.janus_session_id, session_info.janus_handle_id, candidates)) data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('Trickled ICE candidate(s) for session %s' % session) except APIError, e: log.error('session-trickle: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def onClose(self, clean, code, reason): if self.connection_handler is None: # Very early connection closed, onOpen wasn't even called return log.msg('Connection from %s closed' % self.peer) self.factory.connections.discard(self) self.connection_handler.stop() self.connection_handler = None
def _send_push_notification(payload): if GeneralConfig.firebase_server_key: try: r = yield agent.request('POST', FIREBASE_API_URL, headers, StringProducer(payload)) except Exception, e: log.msg('Error sending Firebase message: %s', e) else: if r.code != 200: log.warn('Error sending Firebase message: %s' % r.phrase)
def onConnect(self, request): self.peer = request.peer if SYLK_WS_PROTOCOL not in request.protocols: log.msg('Rejecting connection from %s, remote does not support our sub-protocol' % self.peer) raise HttpException(406, 'No compatible protocol specified') if not self.backend.ready: log.msg('Rejecting connection from %s, backend is not connected' % self.peer) raise HttpException(503, 'Backend is not connected') return SYLK_WS_PROTOCOL
def _OH_session_create(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: account = data['account'] session = data['session'] uri = data['uri'] sdp = data['sdp'] except KeyError: raise APIError('Invalid parameters: "account", "session", "uri" and "sdp" must be specified') try: account_info = self.accounts_map[account] except KeyError: raise APIError('Unknown account specified: %s' % account) if session in self.sessions_map: raise APIError('Session ID (%s) already in use' % session) # Create a new plugin handle and 'register' it, without actually doing so handle_id = block_on(self.backend.janus_attach(self.janus_session_id, 'janus.plugin.sip')) self.backend.janus_set_event_handler(handle_id, self._handle_janus_event) try: proxy = self._lookup_sip_proxy(account_info.id) except DNSLookupError: block_on(self.backend.janus_detach(self.janus_session_id, handle_id)) self.backend.janus_set_event_handler(handle_id, None) raise APIError('DNS lookup error') account_uri = 'sip:%s' % account_info.id data = {'request': 'register', 'username': account_uri, 'ha1_secret': account_info.password, 'proxy': proxy, 'send_register': False} block_on(self.backend.janus_message(self.janus_session_id, handle_id, data)) session_info = JanusSessionInfo(session) session_info.janus_handle_id = handle_id session_info.init_outgoing(account, uri) self.sessions_map[session_info.id] = session_info self.session_handles_map[handle_id] = session_info data = {'request': 'call', 'uri': 'sip:%s' % SIP_PREFIX_RE.sub('', uri)} jsep = {'type': 'offer', 'sdp': sdp} block_on(self.backend.janus_message(self.janus_session_id, handle_id, data, jsep)) data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('Outgoing session %s from %s to %s created' % (session, account, uri)) except APIError, e: log.error('session-create: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def start(self): ws_url = "ws" + server.url[4:] + "/webrtcgateway/ws" self.factory = SylkWebSocketServerFactory( ws_url, protocols=[SYLK_WS_PROTOCOL], server="SylkServer/%s" % sylk_version, debug=False ) self.factory.setProtocolOptions( allowedOrigins=GeneralConfig.web_origins, autoPingInterval=GeneralConfig.websocket_ping_interval, autoPingTimeout=GeneralConfig.websocket_ping_interval / 2, ) self.factory.ws_logger = self.ws_logger self.web = WebRTCGatewayWeb(self.factory) server.register_resource("webrtcgateway", self.web.resource()) log.msg("WebSocket handler started at %s" % ws_url) log.msg("Allowed web origins: %s" % ", ".join(GeneralConfig.web_origins)) log.msg("Allowed SIP domains: %s" % ", ".join(GeneralConfig.sip_domains)) log.msg("Using Janus API: %s" % JanusConfig.api_url) self.ws_logger.start() self.backend = JanusBackend() self.backend.start() self.factory.backend = self.backend
def _OH_account_register(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: account = data['account'] except KeyError: raise APIError('Invalid parameters: "account" must be specified') try: account_info = self.accounts_map[account] except KeyError: raise APIError('Unknown account specified: %s' % account) proxy = self._lookup_sip_proxy(account) handle_id = account_info.janus_handle_id if handle_id is not None: # Destroy the existing plugin handle block_on(self.backend.janus_detach(self.janus_session_id, handle_id)) self.backend.janus_set_event_handler(handle_id, None) self.account_handles_map.pop(handle_id) account_info.janus_handle_id = None # Create a plugin handle handle_id = block_on(self.backend.janus_attach(self.janus_session_id, 'janus.plugin.sip')) self.backend.janus_set_event_handler(handle_id, self._handle_janus_event) account_info.janus_handle_id = handle_id self.account_handles_map[handle_id] = account_info data = {'request': 'register', 'username': account_info.uri, 'ha1_secret': account_info.password, 'proxy': proxy} block_on(self.backend.janus_message(self.janus_session_id, handle_id, data)) data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('Account %s will register' % account) except APIError, e: log.error('account-register: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def onClose(self, wasClean, code, reason): if self.ready_event is None: # Very early connection closed, onOpen wasn't even called return log.msg('Connection from %s closed' % self.transport.getPeer()) self.factory.connections.discard(self) if self.ready_event.is_set(): assert self.janus_session_id is not None self.backend.janus_stop_keepalive(self.janus_session_id) self.backend.janus_destroy_session(self.janus_session_id) if self.proc is not None: self.proc.kill() self.proc = None # cleanup self.ready_event.clear() self.accounts_map.clear() self.account_handles_map.clear() self.sessions_map.clear() self.session_handles_map.clear() self.janus_session_id = None
def _OH_account_add(self, data): transaction = data.get('transaction', None) if transaction is None: log.warn('Transaction not specified!') return try: try: account = data['account'] password = data['password'] except KeyError: raise APIError('Invalid parameters: "account" and "password" must be specified') if account in self.accounts_map: log.warn('Account %s already added' % account) data = dict(sylkrtc='error', transaction=transaction, error='Account already added') self._send_data(json.dumps(data)) return # Validate URI uri = 'sip:%s' % account try: sip_uri = SIPURI.parse(uri) except SIPCoreError: raise APIError('Invalid account specified: %s' % account) if not {'*', sip_uri.host}.intersection(GeneralConfig.sip_domains): raise APIError('SIP domain not allowed: %s' % sip_uri.host) # Create and store our mapping account_info = AccountInfo(account, password) self.accounts_map[account_info.id] = account_info data = dict(sylkrtc='ack', transaction=transaction) self._send_data(json.dumps(data)) log.msg('Account %s added' % account) except APIError, e: log.error('account_add: %s' % e) data = dict(sylkrtc='error', transaction=transaction, error=str(e)) self._send_data(json.dumps(data))
def onConnect(self, request): log.msg('Incoming connection from %s (origin %s)' % (request.peer, request.origin)) if SYLK_WS_PROTOCOL not in request.protocols: log.msg('Rejecting connection, remote does not support our sub-protocol') raise http.HttpException(http.NOT_ACCEPTABLE[0], 'No compatible protocol specified') if not self.backend.ready: log.msg('Rejecting connection, backend is not connected') raise http.HttpException(http.SERVICE_UNAVAILABLE[0], 'Backend is not connected') return SYLK_WS_PROTOCOL
def start(self): ws_url = 'ws' + server.url[4:] + '/webrtcgateway/ws' self.factory = SylkWebSocketServerFactory(ws_url, protocols=[SYLK_WS_PROTOCOL], server='SylkServer/%s' % sylk_version, debug=False) self.factory.setProtocolOptions(allowedOrigins=GeneralConfig.web_origins) self.factory.ws_logger = self.ws_logger self.web = WebRTCGatewayWeb(self.factory) server.register_resource('webrtcgateway', self.web.resource()) log.msg('WebSocket handler started at %s' % ws_url) log.msg('Allowed web origins: %s' % ', '.join(GeneralConfig.web_origins)) log.msg('Allowed SIP domains: %s' % ', '.join(GeneralConfig.sip_domains)) log.msg('Using Janus API: %s' % JanusConfig.api_url) self.ws_logger.start() self.backend = JanusBackend() self.backend.start() self.factory.backend = self.backend
def start(self): ws_url = 'ws' + server.url[4:] + '/webrtcgateway/ws' self.factory = SylkWebSocketServerFactory(ws_url, protocols=[SYLK_WS_PROTOCOL], server='SylkServer/%s' % sylk_version, debug=False) self.factory.setProtocolOptions(allowedOrigins=GeneralConfig.web_origins, autoPingInterval=GeneralConfig.websocket_ping_interval, autoPingTimeout=GeneralConfig.websocket_ping_interval/2) self.factory.ws_logger = self.ws_logger self.web = WebRTCGatewayWeb(self.factory) server.register_resource('webrtcgateway', self.web.resource()) log.msg('WebSocket handler started at %s' % ws_url) log.msg('Allowed web origins: %s' % ', '.join(GeneralConfig.web_origins)) log.msg('Allowed SIP domains: %s' % ', '.join(GeneralConfig.sip_domains)) log.msg('Using Janus API: %s' % JanusConfig.api_url) self.ws_logger.start() self.backend = JanusBackend() self.backend.start() self.factory.backend = self.backend
def _NH_JanusBackendDisconnected(self, notification): log.msg('Janus backend connection down: %s' % notification.data.reason) self.connection = Null
def _NH_JanusBackendConnected(self, notification): assert self.connection is Null self.connection = notification.sender log.msg('Janus backend connection up') self.factory.resetDelay()
def incoming_session(self, session): log.msg(u'New incoming session %s from %s rejected' % (session.call_id, IdentityFormatter.format(session.remote_identity))) session.reject(403)
def incoming_session(self, session): log.msg(u'New incoming session %s from %s rejected' % (session.call_id, IdentityFormatter.format(session.remote_identity))) session.reject(403)
def _NH_JanusBackendDisconnected(self, notification): log.msg('Janus backend connection down: %s' % notification.data.reason) self.connection = Null
def onOpen(self): log.msg('Connection from %s open' % self.peer) self.factory.connections.add(self) self.connection_handler = ConnectionHandler(self) self.connection_handler.start()
def _OH_janus_event(self, data): handle_id = data['handle_id'] event_type = data['event_type'] event = data['event'] if event_type == 'event': event_data = event['plugindata']['data'] if 'result' in event_data: jsep = event.get('jsep', None) event_type = event_data['result']['event'] if event_type in ('registering', 'registered', 'registration_failed', 'incomingcall'): # skip 'registered' events from session handles if event_type == 'registered' and event_data['result']['register_sent'] in (False, 'false'): return # account event try: account_info = self.account_handles_map[handle_id] except KeyError: log.warn('Could not find account for handle ID %s' % handle_id) return if event_type == 'incomingcall': originator = SIP_PREFIX_RE.sub('', event_data['result']['username']) jsep = event.get('jsep', None) assert jsep is not None session_id = uuid.uuid4().hex session = JanusSessionInfo(session_id) session.janus_handle_id = handle_id session.init_incoming(account_info.id, originator) self.sessions_map[session_id] = session self.session_handles_map[handle_id] = session data = dict(sylkrtc='account_event', account=account_info.id, session=session_id, event='incoming_session', data=dict(originator=originator, sdp=jsep['sdp'])) log.msg('Incoming session %s %s <-> %s created' % (session.id, session.remote_identity, session.local_identity)) else: registration_state = event_type if registration_state == 'registration_failed': registration_state = 'failed' if account_info.registration_state == registration_state: return account_info.registration_state = registration_state registration_data = dict(state=registration_state) if registration_state == 'failed': code = event_data['result']['code'] reason = event_data['result']['reason'] registration_data['reason'] = '%d %s' % (code, reason) data = dict(sylkrtc='account_event', account=account_info.id, event='registration_state', data=registration_data) log.msg('Account %s registration state changed to %s' % (account_info.id, registration_state)) self._send_data(json.dumps(data)) elif event_type in ('calling', 'accepted', 'hangup'): # session event try: session_info = self.session_handles_map[handle_id] except KeyError: log.warn('Could not find session for handle ID %s' % handle_id) return if event_type == 'hangup' and session_info.state == 'terminated': return if event_type == 'calling': session_info.state = 'progress' elif event_type == 'accepted': session_info.state = 'accepted' elif event_type == 'hangup': session_info.state = 'terminated' data = dict(sylkrtc='session_event', session=session_info.id, event='state', data=dict(state=session_info.state)) log.msg('%s session %s state: %s' % (session_info.direction.title(), session_info.id, session_info.state)) if session_info.state == 'accepted' and session_info.direction == 'outgoing': assert jsep is not None data['data']['sdp'] = jsep['sdp'] elif session_info.state == 'terminated': code = event_data['result'].get('code', 0) reason = event_data['result'].get('reason', 'Unknown') reason = '%d %s' % (code, reason) data['data']['reason'] = reason self._send_data(json.dumps(data)) if session_info.state == 'terminated': self._cleanup_session(session_info) log.msg('%s session %s %s <-> %s terminated (%s)' % (session_info.direction.title(), session_info.id, session_info.local_identity, session_info.remote_identity, reason)) elif event_type in ('ack', 'hangingup'): # ignore pass else: log.warn('Unexpected event type: %s' % event_type) else: log.warn('Unexpected event: %s' % event) elif event_type == 'webrtcup': try: session_info = self.session_handles_map[handle_id] except KeyError: log.msg('Could not find session for handle ID %s' % handle_id) return session_info.state = 'established' data = dict(sylkrtc='session_event', session=session_info.id, event='state', data=dict(state=session_info.state)) log.msg('%s session %s state: %s' % (session_info.direction.title(), session_info.id, session_info.state)) self._send_data(json.dumps(data)) elif event_type == 'hangup': try: session_info = self.session_handles_map[handle_id] except KeyError: log.msg('Could not find session for handle ID %s' % handle_id) return if session_info.state != 'terminated': session_info.state = 'terminated' code = event.get('code', 0) reason = event.get('reason', 'Unknown') reason = '%d %s' % (code, reason) data = dict(sylkrtc='session_event', session=session_info.id, event='state', data=dict(state=session_info.state, reason=reason)) log.msg('%s session %s state: %s' % (session_info.direction.title(), session_info.id, session_info.state)) self._send_data(json.dumps(data)) self._cleanup_session(session_info) log.msg('%s session %s %s <-> %s terminated (%s)' % (session_info.direction.title(), session_info.id, session_info.local_identity, session_info.remote_identity, reason)) elif event_type in ('media', 'declining'): # ignore pass else: log.warn('Received unexpected event type: %s' % event_type)
def _NH_JanusBackendConnected(self, notification): assert self.connection is Null self.connection = notification.sender log.msg('Janus backend connection up') self.factory.resetDelay()
def start(self): host, port = GeneralConfig.http_management_interface self.listener = reactor.listenTCP(port, Site(self.app.resource()), interface=host) log.msg('Admin web handler started at http://%s:%d' % (host, port))