def add_hb_peer(_peer_conf, _ctable_loc, _peer): _ctable_loc[int_id(_peer)] = {} _ctable_peer = _ctable_loc[int_id(_peer)] # if the Frequency is 000.xxx assume it's not an RF peer, otherwise format the text fields # (9 char, but we are just software) see https://wiki.brandmeister.network/index.php/Homebrew/example/php2 if _peer_conf['TX_FREQ'].isalnum() and _peer_conf['RX_FREQ'].isalnum(): if _peer_conf['TX_FREQ'][:3] == b'000' or _peer_conf[ 'RX_FREQ'][:3] == b'000': _ctable_peer['TX_FREQ'] = 'N/A' _ctable_peer['RX_FREQ'] = 'N/A' else: _ctable_peer['TX_FREQ'] = _peer_conf['TX_FREQ'][:3].decode( 'utf-8') + '.' + _peer_conf['TX_FREQ'][3:7].decode( 'utf-8') + ' MHz' _ctable_peer['RX_FREQ'] = _peer_conf['RX_FREQ'][:3].decode( 'utf-8') + '.' + _peer_conf['RX_FREQ'][3:7].decode( 'utf-8') + ' MHz' else: _ctable_peer['TX_FREQ'] = 'N/A' _ctable_peer['RX_FREQ'] = 'N/A' # timeslots are kinda complicated too. 0 = none, 1 or 2 mean that one slot, 3 is both, and anything else it considered DMO # Slots (0, 1=1, 2=2, 1&2=3 Duplex, 4=Simplex) see https://wiki.brandmeister.network/index.php/Homebrew/example/php2 if (_peer_conf['SLOTS'] == b'0'): _ctable_peer['SLOTS'] = 'NONE' elif (_peer_conf['SLOTS'] == b'1' or _peer_conf['SLOTS'] == b'2'): _ctable_peer['SLOTS'] = _peer_conf['SLOTS'].decode('utf-8') elif (_peer_conf['SLOTS'] == b'3'): _ctable_peer['SLOTS'] = 'Duplex' else: _ctable_peer['SLOTS'] = 'Simplex' # Simple translation items _ctable_peer['SOFTWARE_ID'] = _peer_conf['SOFTWARE_ID'].decode( 'utf-8').strip() _ctable_peer['PACKAGE_ID'] = _peer_conf['PACKAGE_ID'].decode( 'utf-8').strip() _ctable_peer['COLORCODE'] = _peer_conf['COLORCODE'].decode('utf-8') _ctable_peer['CALLSIGN'] = _peer_conf['CALLSIGN'].decode('utf-8') _ctable_peer['LOCATION'] = _peer_conf['LOCATION'].decode('utf-8') _ctable_peer['CONNECTION'] = _peer_conf['CONNECTION'] _ctable_peer['CONNECTED'] = since(_peer_conf['CONNECTED']) _ctable_peer['IP'] = _peer_conf['IP'] _ctable_peer['PORT'] = _peer_conf['PORT'] #_ctable_peer['LAST_PING'] = _peer_conf['LAST_PING'] # SLOT 1&2 - for real-time montior: make the structure for later use for ts in range(1, 3): _ctable_peer[ts] = {} _ctable_peer[ts]['COLOR'] = '' _ctable_peer[ts]['BGCOLOR'] = '' _ctable_peer[ts]['TS'] = '' _ctable_peer[ts]['TYPE'] = '' _ctable_peer[ts]['SUB'] = '' _ctable_peer[ts]['SRC'] = '' _ctable_peer[ts]['DEST'] = ''
def build_bridge_table(_bridges): _stats_table = {} _now = time() _cnow = strftime('%Y-%m-%d %H:%M:%S', localtime(_now)) for _bridge, _bridge_data in list(_bridges.items()): _stats_table[_bridge] = {} for system in _bridges[_bridge]: _stats_table[_bridge][system['SYSTEM']] = {} _stats_table[_bridge][system['SYSTEM']]['TS'] = system['TS'] _stats_table[_bridge][system['SYSTEM']]['TGID'] = int_id( system['TGID']) if system['TO_TYPE'] == 'ON' or system['TO_TYPE'] == 'OFF': if system['TIMER'] - _now > 0: _stats_table[_bridge][system['SYSTEM']]['EXP_TIME'] = int( system['TIMER'] - _now) else: _stats_table[_bridge][ system['SYSTEM']]['EXP_TIME'] = 'Expired' if system['TO_TYPE'] == 'ON': _stats_table[_bridge][ system['SYSTEM']]['TO_ACTION'] = 'Disconnect' else: _stats_table[_bridge][ system['SYSTEM']]['TO_ACTION'] = 'Connect' else: _stats_table[_bridge][system['SYSTEM']]['EXP_TIME'] = 'N/A' _stats_table[_bridge][system['SYSTEM']]['TO_ACTION'] = 'None' if system['ACTIVE'] == True: _stats_table[_bridge][system['SYSTEM']]['ACTIVE'] = 'Connected' _stats_table[_bridge][system['SYSTEM']]['COLOR'] = BLACK _stats_table[_bridge][system['SYSTEM']]['BGCOLOR'] = GREEN elif system['ACTIVE'] == False: _stats_table[_bridge][ system['SYSTEM']]['ACTIVE'] = 'Disconnected' _stats_table[_bridge][system['SYSTEM']]['COLOR'] = WHITE _stats_table[_bridge][system['SYSTEM']]['BGCOLOR'] = RED for i in range(len(system['ON'])): system['ON'][i] = str(int_id(system['ON'][i])) _stats_table[_bridge][system['SYSTEM']]['TRIG_ON'] = ', '.join( system['ON']) for i in range(len(system['OFF'])): system['OFF'][i] = str(int_id(system['OFF'][i])) _stats_table[_bridge][system['SYSTEM']]['TRIG_OFF'] = ', '.join( system['OFF']) return _stats_table
def update_hblink_table(_config, _stats_table): # Is there a system in HBlink's config monitor doesn't know about? for _hbp in _config: if _config[_hbp]['MODE'] == 'MASTER': for _peer in _config[_hbp]['PEERS']: if int_id(_peer) not in _stats_table['MASTERS'][_hbp]['PEERS'] and _config[_hbp]['PEERS'][_peer]['CONNECTION'] == 'YES': logger.info('Adding peer to CTABLE that has registerred: %s', int_id(_peer)) add_hb_peer(_config[_hbp]['PEERS'][_peer], _stats_table['MASTERS'][_hbp]['PEERS'], _peer) # Is there a system in monitor that's been removed from HBlink's config? for _hbp in _stats_table['MASTERS']: remove_list = [] if _config[_hbp]['MODE'] == 'MASTER': for _peer in _stats_table['MASTERS'][_hbp]['PEERS']: if bytes_4(_peer) not in _config[_hbp]['PEERS']: remove_list.append(_peer) for _peer in remove_list: logger.info('Deleting stats peer not in hblink config: %s', _peer) del (_stats_table['MASTERS'][_hbp]['PEERS'][_peer]) # Update connection time for _hbp in _stats_table['MASTERS']: for _peer in _stats_table['MASTERS'][_hbp]['PEERS']: if bytes_4(_peer) in _config[_hbp]['PEERS']: _stats_table['MASTERS'][_hbp]['PEERS'][_peer]['CONNECTED'] = since(_config[_hbp]['PEERS'][bytes_4(_peer)]['CONNECTED']) for _hbp in _stats_table['PEERS']: if _stats_table['PEERS'][_hbp]['MODE'] == 'XLXPEER': if _config[_hbp]['XLXSTATS']['CONNECTION'] == "YES": _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_config[_hbp]['XLXSTATS']['CONNECTED']) _stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['XLXSTATS']['CONNECTION'] _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _config[_hbp]['XLXSTATS']['PINGS_SENT'] _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _config[_hbp]['XLXSTATS']['PINGS_ACKD'] else: _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --" _stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['XLXSTATS']['CONNECTION'] _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 else: if _config[_hbp]['STATS']['CONNECTION'] == "YES": _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_config[_hbp]['STATS']['CONNECTED']) _stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['STATS']['CONNECTION'] _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _config[_hbp]['STATS']['PINGS_SENT'] _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _config[_hbp]['STATS']['PINGS_ACKD'] else: _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --" _stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['STATS']['CONNECTION'] _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 cleanTE() build_stats()
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] if _call_type == 'group': # Is this is a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): self.STATUS['RX_START'] = pkt_time logger.info('(%s) *START RECORDING* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) self.CALL_DATA.append(_data) self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id return # Final actions - Is this a voice terminator? if (_frame_type == const.HBPF_DATA_SYNC) and ( _dtype_vseq == const.HBPF_SLT_VTERM) and ( self.STATUS[_slot]['RX_TYPE'] != const.HBPF_SLT_VTERM) and (self.CALL_DATA): call_duration = pkt_time - self.STATUS['RX_START'] #Change the stream ID self.CALL_DATA.append(_data) logger.info('(%s) *END RECORDING* STREAM ID: %s', self._system, int_id(_stream_id)) sleep(2) _new_stream_id = bytes_4(randint(0x00, 0xFFFFFFFF)) logger.info('(%s) *START PLAYBACK* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ self._system, int_id(_new_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) for i in self.CALL_DATA: i = i[:16] + _new_stream_id + i[20:] self.send_system(i) sleep(0.06) self.CALL_DATA = [] logger.info('(%s) *END PLAYBACK* STREAM ID: %s', self._system, int_id(_new_stream_id)) else: if self.CALL_DATA: #Change the stream ID self.CALL_DATA.append(_data) # Mark status variables for use later self.STATUS[_slot]['RX_RFS'] = _rf_src self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq self.STATUS[_slot]['RX_TGID'] = _dst_id self.STATUS[_slot]['RX_TIME'] = pkt_time self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] if _call_type == 'group': # Is this is a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): self.STATUS['RX_START'] = pkt_time logger.info('(%s) *START RECORDING* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) self.CALL_DATA.append(_data) self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id return # Final actions - Is this a voice terminator? if (_frame_type == const.HBPF_DATA_SYNC) and ( _dtype_vseq == const.HBPF_SLT_VTERM) and ( self.STATUS[_slot]['RX_TYPE'] != const.HBPF_SLT_VTERM) and (self.CALL_DATA): call_duration = pkt_time - self.STATUS['RX_START'] self.CALL_DATA.append(_data) logger.info('(%s) *END RECORDING* STREAM ID: %s', self._system, int_id(_stream_id)) sleep(2) logger.info('(%s) *START PLAYBACK* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if (int_id(_dst_id) == 9996): fout = open( '/home/mmdvm/' + str(int_id(_stream_id)) + '.ambe', 'w+b') for i in self.CALL_DATA: if (int_id(_dst_id) == 9996): fout.write(i) # self.send_system(i) self.send_system(i[:5] + bytes_3(int_id(_peer_id)) + i[8:]) #print(i) sleep(0.06) if (int_id(_dst_id) == 9996): fout.close() self.CALL_DATA = [] logger.info('(%s) *END PLAYBACK* STREAM ID: %s', self._system, int_id(_stream_id)) else: if self.CALL_DATA: self.CALL_DATA.append(_data) # Mark status variables for use later self.STATUS[_slot]['RX_RFS'] = _rf_src self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq self.STATUS[_slot]['RX_TGID'] = _dst_id self.STATUS[_slot]['RX_TIME'] = pkt_time self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
def add_hb_peer(_peer_conf, _ctable_loc, _peer): _ctable_loc[int_id(_peer)] = {} _ctable_peer = _ctable_loc[int_id(_peer)] # if the Frequency is 000.xxx assume it's not an RF peer, otherwise format the text fields if _peer_conf['TX_FREQ'][:3] == b'000' or _peer_conf[ 'RX_FREQ'][:3] == b'000': _ctable_peer['TX_FREQ'] = 'N/A' _ctable_peer['RX_FREQ'] = '' else: _ctable_peer['TX_FREQ'] = 'TX: ' + _peer_conf['TX_FREQ'][:3].decode( 'utf-8') + '.' + _peer_conf['TX_FREQ'][3:7].decode('utf-8') _ctable_peer['RX_FREQ'] = 'RX: ' + _peer_conf['RX_FREQ'][:3].decode( 'utf-8') + '.' + _peer_conf['RX_FREQ'][3:7].decode('utf-8') # timeslots are kinda complicated too. 0 = none, 1 or 2 mean that one slot, 3 is both, and anything else it considered DMO if (_peer_conf['SLOTS'] == b'0'): _ctable_peer['SLOTS'] = 'NONE' elif (_peer_conf['SLOTS'] == b'1' or _peer_conf['SLOTS'] == b'2'): _ctable_peer['SLOTS'] = _peer_conf['SLOTS'] elif (_peer_conf['SLOTS'] == b'3'): _ctable_peer['SLOTS'] = 'BOTH' else: _ctable_peer['SLOTS'] = 'DMO' # Simple translation items _ctable_peer['COLORCODE'] = _peer_conf['COLORCODE'].decode('utf-8') _ctable_peer['CALLSIGN'] = _peer_conf['CALLSIGN'].decode('utf-8') _ctable_peer['LOCATION'] = _peer_conf['LOCATION'].decode('utf-8') _ctable_peer['CONNECTION'] = _peer_conf['CONNECTION'] _ctable_peer['CONNECTED'] = since(_peer_conf['CONNECTED']) _ctable_peer['IP'] = _peer_conf['IP'] _ctable_peer['PORT'] = _peer_conf['PORT'] #_ctable_peer['LAST_PING'] = _peer_conf['LAST_PING'] # SLOT 1&2 - for real-time montior: make the structure for later use for ts in range(1, 3): _ctable_peer[ts] = {} _ctable_peer[ts]['COLOR'] = '' _ctable_peer[ts]['BGCOLOR'] = '' _ctable_peer[ts]['TS'] = '' _ctable_peer[ts]['TYPE'] = '' _ctable_peer[ts]['SUB'] = '' _ctable_peer[ts]['SRC'] = '' _ctable_peer[ts]['DEST'] = ''
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and ( _stream_id != self.last_stream): print(int_id(_stream_id), int_id(self.last_stream)) self.last_stream = _stream_id print('start speech') speech = pkt_gen(bytes_3(3120101), bytes_3(2), bytes_4(3120119), 0, [words['all_circuits'], words['all_circuits']]) sleep(1) while True: try: pkt = next(speech) except StopIteration: break sleep(.058) self.send_system(pkt) print(bhex(pkt)) print('end speech')
def get_alias(_id, _dict, *args): if type(_id) == bytes: _id = int_id(_id) if _id in _dict: if args: retValue = [] for _item in args: try: retValue.append(_dict[_id][_item]) except TypeError: return _dict[_id] return retValue else: return _dict[_id] return _id
def build_hblink_table(_config, _stats_table): for _hbp, _hbp_data in list(_config.items()): if _hbp_data['ENABLED'] == True: # Process Master Systems if _hbp_data['MODE'] == 'MASTER': _stats_table['MASTERS'][_hbp] = {} if _hbp_data['REPEAT']: _stats_table['MASTERS'][_hbp]['REPEAT'] = "repeat" else: _stats_table['MASTERS'][_hbp]['REPEAT'] = "isolate" _stats_table['MASTERS'][_hbp]['PEERS'] = {} for _peer in _hbp_data['PEERS']: add_hb_peer(_hbp_data['PEERS'][_peer], _stats_table['MASTERS'][_hbp]['PEERS'], _peer) # Proccess Peer Systems elif (_hbp_data['MODE'] == 'XLXPEER' or _hbp_data['MODE'] == 'PEER') and HOMEBREW_INC: _stats_table['PEERS'][_hbp] = {} _stats_table['PEERS'][_hbp]['MODE'] = _hbp_data['MODE'] if str(type(_hbp_data['LOCATION'])).find("bytes") != -1: _stats_table['PEERS'][_hbp]['LOCATION'] = _hbp_data[ 'LOCATION'].decode('utf-8').strip() else: _stats_table['PEERS'][_hbp]['LOCATION'] = _hbp_data[ 'LOCATION'] if str(type(_hbp_data['CALLSIGN'])).find("bytes") != -1: _stats_table['PEERS'][_hbp]['CALLSIGN'] = _hbp_data[ 'CALLSIGN'].decode('utf-8').strip() else: _stats_table['PEERS'][_hbp]['CALLSIGN'] = _hbp_data[ 'CALLSIGN'] _stats_table['PEERS'][_hbp]['RADIO_ID'] = int_id( _hbp_data['RADIO_ID']) _stats_table['PEERS'][_hbp]['MASTER_IP'] = _hbp_data[ 'MASTER_IP'] _stats_table['PEERS'][_hbp]['MASTER_PORT'] = _hbp_data[ 'MASTER_PORT'] _stats_table['PEERS'][_hbp]['STATS'] = {} if _stats_table['PEERS'][_hbp]['MODE'] == 'XLXPEER': _stats_table['PEERS'][_hbp]['STATS'][ 'CONNECTION'] = _hbp_data['XLXSTATS']['CONNECTION'] if _hbp_data['XLXSTATS']['CONNECTION'] == "YES": _stats_table['PEERS'][_hbp]['STATS'][ 'CONNECTED'] = since( _hbp_data['XLXSTATS']['CONNECTED']) _stats_table['PEERS'][_hbp]['STATS'][ 'PINGS_SENT'] = _hbp_data['XLXSTATS']['PINGS_SENT'] _stats_table['PEERS'][_hbp]['STATS'][ 'PINGS_ACKD'] = _hbp_data['XLXSTATS']['PINGS_ACKD'] else: _stats_table['PEERS'][_hbp]['STATS'][ 'CONNECTED'] = "-- --" _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 else: _stats_table['PEERS'][_hbp]['STATS'][ 'CONNECTION'] = _hbp_data['STATS']['CONNECTION'] if _hbp_data['STATS']['CONNECTION'] == "YES": _stats_table['PEERS'][_hbp]['STATS'][ 'CONNECTED'] = since( _hbp_data['STATS']['CONNECTED']) _stats_table['PEERS'][_hbp]['STATS'][ 'PINGS_SENT'] = _hbp_data['STATS']['PINGS_SENT'] _stats_table['PEERS'][_hbp]['STATS'][ 'PINGS_ACKD'] = _hbp_data['STATS']['PINGS_ACKD'] else: _stats_table['PEERS'][_hbp]['STATS'][ 'CONNECTED'] = "-- --" _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 if _hbp_data['SLOTS'] == b'0': _stats_table['PEERS'][_hbp]['SLOTS'] = 'NONE' elif _hbp_data['SLOTS'] == b'1' or _hbp_data['SLOTS'] == b'2': _stats_table['PEERS'][_hbp]['SLOTS'] = _hbp_data[ 'SLOTS'].decode('utf-8') elif _hbp_data['SLOTS'] == b'3': _stats_table['PEERS'][_hbp]['SLOTS'] = '1&2' else: _stats_table['PEERS'][_hbp]['SLOTS'] = 'DMO' # SLOT 1&2 - for real-time montior: make the structure for later use for ts in range(1, 3): _stats_table['PEERS'][_hbp][ts] = {} _stats_table['PEERS'][_hbp][ts]['COLOR'] = '' _stats_table['PEERS'][_hbp][ts]['BGCOLOR'] = '' _stats_table['PEERS'][_hbp][ts]['TS'] = '' _stats_table['PEERS'][_hbp][ts]['TYPE'] = '' _stats_table['PEERS'][_hbp][ts]['SUB'] = '' _stats_table['PEERS'][_hbp][ts]['SRC'] = '' _stats_table['PEERS'][_hbp][ts]['DEST'] = '' # Process OpenBridge systems elif _hbp_data['MODE'] == 'OPENBRIDGE': _stats_table['OPENBRIDGES'][_hbp] = {} _stats_table['OPENBRIDGES'][_hbp]['NETWORK_ID'] = int_id( _hbp_data['NETWORK_ID']) _stats_table['OPENBRIDGES'][_hbp]['TARGET_IP'] = _hbp_data[ 'TARGET_IP'] _stats_table['OPENBRIDGES'][_hbp]['TARGET_PORT'] = _hbp_data[ 'TARGET_PORT'] _stats_table['OPENBRIDGES'][_hbp]['STREAMS'] = {}
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] if _call_type == 'group': # Is this is a new call stream? new_stream = (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) if new_stream: self.STATUS[_slot]['RX_START'] = pkt_time self.STATUS[_slot]['RX_LOSS'] = 0 self.STATUS[_slot]['RX_SEQ'] = _seq logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) else: # This could be much better, it will have errors during roll-over if _seq > (self.STATUS[_slot]['RX_SEQ'] + 1): #print(_seq, self.STATUS[_slot]['RX_SEQ']) self.STATUS[_slot]['RX_LOSS'] += _seq - ( self.STATUS[_slot]['RX_SEQ'] + 1) # Final actions - Is this a voice terminator? if (_frame_type == const.HBPF_DATA_SYNC) and ( _dtype_vseq == const.HBPF_SLT_VTERM) and ( self.STATUS[_slot]['RX_TYPE'] != const.HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_slot]['RX_START'] logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Loss: %s, Duration: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self.STATUS[_slot]['RX_LOSS'], call_duration) for _target in self._CONFIG['SYSTEMS']: if _target != self._system: _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] # BEGIN STANDARD CONTENTION HANDLING # # The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is: # From a different group than last RX from this HBSystem, but it has been less than Group Hangtime # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD and self.STATUS[ _slot]['RX_STREAM_ID'] != _stream_id: logger.info( '(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD and self.STATUS[ _slot]['RX_STREAM_ID'] != _stream_id: logger.info( '(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue if (_dst_id == _target_status[_slot]['RX_TGID']) and ( (pkt_time - _target_status[_slot]['RX_TIME']) < const.STREAM_TO): if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD and self.STATUS[ _slot]['RX_STREAM_ID'] != _stream_id: logger.info( '(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if (_dst_id == _target_status[_slot]['TX_TGID']) and ( _rf_src != _target_status[_slot]['TX_RFS']) and ( (pkt_time - _target_status[_slot]['TX_TIME']) < const.STREAM_TO): if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD and self.STATUS[ _slot]['RX_STREAM_ID'] != _stream_id: logger.info( '(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if _stream_id != _target_status[_slot][ 'TX_STREAM_ID']: logger.info( '(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', _target, int_id(_stream_id), int_id(_rf_src)) _target_status[_slot][ 'TX_STREAM_ID'] = _stream_id continue if _slot == 1 and not acl_check( _dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if _stream_id != _target_status[_slot][ 'TX_STREAM_ID']: logger.info( '(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', _target, int_id(_stream_id), int_id(_dst_id)) _target_status[_slot][ 'TX_STREAM_ID'] = _stream_id continue if _slot == 2 and not acl_check( _dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): if _stream_id != _target_status[_slot][ 'TX_STREAM_ID']: logger.info( '(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL', _target, int_id(_stream_id), int_id(_dst_id)) _target_status[_slot][ 'TX_STREAM_ID'] = _stream_id continue if _target_system['USE_ACL']: if not acl_check(_rf_src, _target_system['SUB_ACL']): if _stream_id != _target_status[_slot][ 'TX_STREAM_ID']: logger.info( '(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', _target, int_id(_stream_id), int_id(_rf_src)) _target_status[_slot][ 'TX_STREAM_ID'] = _stream_id continue if _slot == 1 and not acl_check( _dst_id, _target_system['TG1_ACL']): if _stream_id != _target_status[_slot][ 'TX_STREAM_ID']: logger.info( '(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL', _target, int_id(_stream_id), int_id(_dst_id)) _target_status[_slot][ 'TX_STREAM_ID'] = _stream_id continue if _slot == 2 and not acl_check( _dst_id, _target_system['TG2_ACL']): if _stream_id != _target_status[_slot][ 'TX_STREAM_ID']: logger.info( '(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL', _target, int_id(_stream_id), int_id(_dst_id)) _target_status[_slot][ 'TX_STREAM_ID'] = _stream_id continue # Record this stuff for later # Is this a new call stream? if new_stream: # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id _target_status[_slot]['TX_RFS'] = _rf_src _target_status[_slot]['TX_PEER'] = _peer_id _target_status[_slot]['TX_STREAM_ID'] = _stream_id _target_status[_slot]['TX_TIME'] = pkt_time systems[_target].send_system(_data) #logger.debug('(%s) Packet routed to system: %s', self._system, _target) # Mark status variables for use later self.STATUS[_slot]['RX_RFS'] = _rf_src self.STATUS[_slot]['RX_SEQ'] = _seq self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq self.STATUS[_slot]['RX_TGID'] = _dst_id self.STATUS[_slot]['RX_TIME'] = pkt_time self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
def peer_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) # Validate that we receveived this packet from the master - security check! if self._config['MASTER_SOCKADDR'] == _sockaddr: # Extract the command, which is various length, but only 4 significant characters _command = _data[:4] if _command == DMRD: # DMRData -- encapsulated DMR data frame _peer_id = _data[11:15] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation _seq = _data[4:5] _rf_src = _data[5:8] _dst_id = _data[8:11] _bits = _data[15] _slot = 2 if (_bits & 0x80) else 1 #_call_type = 'unit' if (_bits & 0x40) else 'group' if _bits & 0x40: _call_type = 'unit' elif (_bits & 0x23) == 0x23: _call_type = 'vcsbk' else: _call_type = 'group' _frame_type = (_bits & 0x30) >> 4 _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #logger.debug('(%s) DMRD - Sequence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', self._system, int_id(_stream_id), int_id(_rf_src)) self._laststrid[_slot] = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_rf_src)) self._laststrid[_slot] = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return # Userland actions -- typically this is the function you subclass for an application self.dmrd_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) elif _command == MSTN: # Actually MSTNAK -- a NACK from the master _peer_id = _data[6:10] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation logger.warning('(%s) MSTNAK Received. Resetting connection to the Master.', self._system) self._stats['CONNECTION'] = 'NO' # Disconnect ourselves and re-register self._stats['CONNECTED'] = time() elif _command == RPTA: # Actually RPTACK -- an ACK from the master # Depending on the state, an RPTACK means different things, in each clause, we check and/or set the state if self._stats['CONNECTION'] == 'RPTL_SENT': # If we've sent a login request... _login_int32 = _data[6:10] logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32)) _pass_hash = sha256(b''.join([_login_int32, self._config['PASSPHRASE']])).hexdigest() _pass_hash = bhex(_pass_hash) self.send_master(b''.join([RPTK, self._config['RADIO_ID'], _pass_hash])) self._stats['CONNECTION'] = 'AUTHENTICATED' elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge... _peer_id = _data[6:10] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation logger.info('(%s) Repeater Authentication Accepted', self._system) _config_packet = b''.join([\ self._config['RADIO_ID'],\ self._config['CALLSIGN'],\ self._config['RX_FREQ'],\ self._config['TX_FREQ'],\ self._config['TX_POWER'],\ self._config['COLORCODE'],\ self._config['LATITUDE'],\ self._config['LONGITUDE'],\ self._config['HEIGHT'],\ self._config['LOCATION'],\ self._config['DESCRIPTION'],\ self._config['SLOTS'],\ self._config['URL'],\ self._config['SOFTWARE_ID'],\ self._config['PACKAGE_ID']\ ]) self.send_master(b''.join([RPTC, _config_packet])) self._stats['CONNECTION'] = 'CONFIG-SENT' logger.info('(%s) Repeater Configuration Sent', self._system) else: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) elif self._stats['CONNECTION'] == 'CONFIG-SENT': # If we've sent out configuration to the master _peer_id = _data[6:10] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation logger.info('(%s) Repeater Configuration Accepted', self._system) if self._config['OPTIONS']: self.send_master(b''.join([RPTO, self._config['RADIO_ID'], self._config['OPTIONS']])) self._stats['CONNECTION'] = 'OPTIONS-SENT' logger.info('(%s) Sent options: (%s)', self._system, self._config['OPTIONS']) else: self._stats['CONNECTION'] = 'YES' self._stats['CONNECTED'] = time() logger.info('(%s) Connection to Master Completed', self._system) # If we are an XLX, send the XLX module request here. if self._config['MODE'] == 'XLXPEER': self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR']) self.send_xlxmaster(self._config['RADIO_ID'], self._config['XLXMODULE'], self._config['MASTER_SOCKADDR']) logger.info('(%s) Sending XLX Module request', self._system) else: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) elif self._stats['CONNECTION'] == 'OPTIONS-SENT': # If we've sent out options to the master _peer_id = _data[6:10] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation logger.info('(%s) Repeater Options Accepted', self._system) self._stats['CONNECTION'] = 'YES' self._stats['CONNECTED'] = time() logger.info('(%s) Connection to Master Completed with options', self._system) else: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) elif _command == MSTP: # Actually MSTPONG -- a reply to RPTPING (send by peer) _peer_id = _data[7:11] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation self._stats['PING_OUTSTANDING'] = False self._stats['NUM_OUTSTANDING'] = 0 self._stats['PINGS_ACKD'] += 1 logger.debug('(%s) MSTPONG Received. Pongs Since Connected: %s', self._system, self._stats['PINGS_ACKD']) elif _command == MSTC: # Actually MSTCL -- notify us the master is closing down _peer_id = _data[5:9] if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation self._stats['CONNECTION'] = 'NO' logger.info('(%s) MSTCL Recieved', self._system) elif _command == RPTS: if _data[:7] == RPTSBKN: logger.info('(%s) Received Site Beacon with Repeater ID: %s', self._system, int_id(_data[7:])) else: logger.error('(%s) Received an invalid command in packet: %s', self._system, ahex(_data))
def process_message(_bmessage): global CTABLE, CONFIG, BRIDGES, CONFIG_RX, BRIDGES_RX _message = _bmessage.decode('utf-8', 'ignore') opcode = _message[:1] _now = strftime('%Y-%m-%d %H:%M:%S %Z', localtime(time())) if opcode == OPCODE['CONFIG_SND']: logging.debug('got CONFIG_SND opcode') CONFIG = load_dictionary(_bmessage) CONFIG_RX = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) if CTABLE['MASTERS']: update_hblink_table(CONFIG, CTABLE) else: build_hblink_table(CONFIG, CTABLE) elif opcode == OPCODE['BRIDGE_SND']: logging.debug('got BRIDGE_SND opcode') BRIDGES = load_dictionary(_bmessage) BRIDGES_RX = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) if BRIDGES_INC: BTABLE['BRIDGES'] = build_bridge_table(BRIDGES) elif opcode == OPCODE['LINK_EVENT']: logging.info('LINK_EVENT Received: {}'.format(repr(_message[1:]))) elif opcode == OPCODE['BRDG_EVENT']: logging.info('BRIDGE EVENT: {}'.format(repr(_message[1:]))) p = _message[1:].split(",") rts_update(p) opbfilter = get_opbf() if p[0] == 'GROUP VOICE' and p[2] != 'TX' and p[5] not in opbfilter: if p[1] == 'END': _matchsystem = False if (int(p[8]) == 9) and (int(p[7]) == 2) and BRIDGES: for _bridge in BRIDGES: for _bridgesystem in BRIDGES[_bridge]: if p[3] == _bridgesystem[ 'SYSTEM'] and _bridgesystem[ 'ACTIVE'] == True and int_id( _bridgesystem['TGID']) == 9: _refdest = str(_bridge[1:]) print(_refdest) log_message = '{} {} {} SYS: {:8.8s} SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s} Time: {}s '.format( _now[10:19], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], _refdest, alias_tgid(int(_refdest), talkgroup_ids) + " (9)", p[6], alias_short(int(p[6]), subscriber_ids), int(float(p[9]))) _matchsystem = True #If we didn't get a match, use default if _matchsystem == False: log_message = '{} {} {} SYS: {:8.8s} SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s} Time: {}s '.format( _now[10:19], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], p[8], alias_tgid(int(p[8]), talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids), int(float(p[9]))) # log only to file if system is NOT OpenBridge event (not logging open bridge system, name depends on your OB definitions) AND transmit time is LONGER as 2sec (make sense for very short transmits) if LASTHEARD_INC: if int(float(p[9])) > 2: _matchsystem = False if (int(p[8]) == 9) and (int(p[7]) == 2) and BRIDGES: for _bridge in BRIDGES: for _bridgesystem in BRIDGES[_bridge]: if p[3] == _bridgesystem[ 'SYSTEM'] and _bridgesystem[ 'ACTIVE'] == True and int_id( _bridgesystem['TGID'] ) == 9: _refdest = str(_bridge[1:]) log_lh_message = '{},{},{},{},{},{},{},TS{},TG{},{},{},{}'.format( _now, p[9], p[0], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], _refdest, alias_tgid(int(_refdest), talkgroup_ids) + " (9)", p[6], alias_short( int(p[6]), subscriber_ids)) _matchsystem = True if _matchsystem == False: log_lh_message = '{},{},{},{},{},{},{},TS{},TG{},{},{},{}'.format( _now, p[9], p[0], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], p[8], alias_tgid(int(p[8]), talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids)) lh_logfile = open(LOG_PATH + "lastheard.log", "a") lh_logfile.write(log_lh_message + '\n') lh_logfile.close() # Lastheard in Dashboard by SP2ONG my_list = [] i = 0 f = open(PATH + "templates/lastheard.html", "w") f.write( "<br><fieldset style=\"border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;\">\n" ) #f.write("<legend><b><font color=\"#000\"> .: Lastheard :. </font></b></legend>\n") f.write( "<table style=\"width:100%; font: 10pt arial, sans-serif\">\n" ) f.write( "<TR style=\" height: 32px;font: 10pt arial, sans-serif; background-color:#9dc209; color:black\"><TH>Date</TH><TH>Time</TH><TH>Callsign (DMR-Id)</TH><TH>Name</TH><TH>TG#</TH><TH>TG Name</TH><TH>TX (s)</TH></TR>\n" ) with open(LOG_PATH + "lastheard.log", "r") as textfile: for row in islice( reversed(list(csv.reader(textfile))), 100): duration = row[1] dur = str(int(float(duration.strip()))) if row[10] not in my_list: if len(row) < 13: hline = "<TR style=\"background-color:#f9f9f9f9;\"><TD>" + row[ 0][:10] + "</TD><TD>" + row[0][ 11: 16] + "</TD><TD><font color=#0066ff><b><a target=\"_blank\" href=https://qrz.com/db/" + row[ 11] + ">" + row[11] + "</a></b></font><span style=\"font: 7pt arial,sans-serif\"> (" + row[ 10] + ")</span></TD><TD><font color=#002d62><b></b></font></TD><TD><font color=#b5651d><b>" + row[ 8][2:] + "</b></font></TD><TD><font color=green><b>" + row[ 9] + "</b></font></TD><TD>" + dur + "</TD><TR>" my_list.append(row[10]) i += 1 else: hline = "<TR style=\"background-color:#f9f9f9f9;\"><TD>" + row[ 0][:10] + "</TD><TD>" + row[0][ 11: 16] + "</TD><TD><font color=#0066ff><b><a target=\"_blank\" href=https://qrz.com/db/" + row[ 11] + ">" + row[11] + "</a></b></font><span style=\"font: 7pt arial,sans-serif\"> (" + row[ 10] + ")</span></TD><TD><font color=#002d62><b>" + row[ 12] + "</b></font></TD><TD><font color=#b5651d><b>" + row[ 8][2:] + "</b></font></TD><TD><font color=green><b>" + row[ 9] + "</b></font></TD><TD>" + dur + "</TD><TR>" my_list.append(row[10]) i += 1 f.write(hline + "\n") if i == 20: break f.write("</table></fieldset><br>") f.close() # End of Lastheard elif p[1] == 'START': log_message = '{} {} {} SYS: {:8.8s} SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s}'.format( _now[10:19], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], p[8], alias_tgid(int(p[8]), talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids)) elif p[1] == 'END WITHOUT MATCHING START': log_message = '{} {} {} on SYSTEM {:8.8s}: SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s}'.format( _now[10:19], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], p[8], alias_tgid(int(p[8]), talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids)) else: log_message = '{} UNKNOWN GROUP VOICE LOG MESSAGE'.format(_now) dashboard_server.broadcast('l' + log_message) LOGBUF.append(log_message) else: logging.debug('{}: UNKNOWN LOG MESSAGE'.format(_now)) else: logging.debug('got unknown opcode: {}, message: {}'.format( repr(opcode), repr(_message[1:])))
def rts_update(p): callType = p[0] action = p[1] trx = p[2] system = p[3] streamId = p[4] sourcePeer = int(p[5]) sourceSub = int(p[6]) timeSlot = int(p[7]) destination = int(p[8]) timeout = datetime.datetime.now().timestamp() if system in CTABLE['MASTERS']: for peer in CTABLE['MASTERS'][system]['PEERS']: if sourcePeer == peer: bgcolor = RED color = WHITE else: bgcolor = GREEN color = BLACK if action == 'START': CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'TIMEOUT'] = timeout CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TS'] = True CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'COLOR'] = color CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'BGCOLOR'] = bgcolor CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'TYPE'] = callType CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'SUB'] = '{} ({})'.format( alias_short(sourceSub, subscriber_ids), sourceSub) CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'SRC'] = peer _matchsystem = False if destination == 9 and timeSlot == 2: for _bridge in BRIDGES: for _bridgesystem in BRIDGES[_bridge]: if system == _bridgesystem[ 'SYSTEM'] and _bridgesystem[ 'ACTIVE'] == True and int_id( _bridgesystem['TGID']) == 9: _refdest = int(_bridge[1:]) CTABLE['MASTERS'][system]['PEERS'][peer][ timeSlot]['DEST'] = '{} ({})'.format( alias_tgid(_refdest, talkgroup_ids), "" + str(destination)) _matchsystem = True if _matchsystem == False: CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'DEST'] = '{} ({})'.format( alias_tgid(destination, talkgroup_ids), destination) if action == 'END': CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'TS'] = False CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'COLOR'] = BLACK CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot][ 'BGCOLOR'] = WHITE2 CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TYPE'] = '' CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SUB'] = '' CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SRC'] = '' CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['DEST'] = '' if system in CTABLE['OPENBRIDGES']: if action == 'START': CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId] = ( trx, alias_call(sourceSub, subscriber_ids), 'TG{}'.format(destination), timeout) if action == 'END': if streamId in CTABLE['OPENBRIDGES'][system]['STREAMS']: del CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId] if system in CTABLE['PEERS']: bgcolor = GREEN if trx == 'RX': bgcolor = RED color = WHITE else: bgcolor = GREEN color = BLACK if action == 'START': CTABLE['PEERS'][system][timeSlot]['TIMEOUT'] = timeout CTABLE['PEERS'][system][timeSlot]['TS'] = True CTABLE['PEERS'][system][timeSlot]['COLOR'] = color CTABLE['PEERS'][system][timeSlot]['BGCOLOR'] = bgcolor CTABLE['PEERS'][system][timeSlot]['SUB'] = '{} ({})'.format( alias_short(sourceSub, subscriber_ids), sourceSub) CTABLE['PEERS'][system][timeSlot]['SRC'] = sourcePeer CTABLE['PEERS'][system][timeSlot]['DEST'] = '{} ({})'.format( alias_tgid(destination, talkgroup_ids), destination) if action == 'END': CTABLE['PEERS'][system][timeSlot]['TS'] = False CTABLE['PEERS'][system][timeSlot]['COLOR'] = BLACK CTABLE['PEERS'][system][timeSlot]['BGCOLOR'] = WHITE2 CTABLE['PEERS'][system][timeSlot]['TYPE'] = '' CTABLE['PEERS'][system][timeSlot]['SUB'] = '' CTABLE['PEERS'][system][timeSlot]['SRC'] = '' CTABLE['PEERS'][system][timeSlot]['DEST'] = '' build_stats()
def process(pkt): # we need to save ambe payload from MMDVM as global var global ambe_payload_mmdvm # we need to save the last packet Seq from Hytera IPSC UDP packet for later processing as global var global last_seq_HYT # get payload from packet is landed in netqueue - the payload is including(!) the IP header - the payload we use starts at p[28:] data = IP(pkt.get_payload()) # process only UDP packets longer than 80, shorter packets will be pass-thru without any modification if len(data) > 80: # hexdump(data) # print("Length:", len(data),"\n\r") # extract payload from UDP packet mod_data = raw(data) # convert to bytearray p = bytearray(mod_data) # print(p[28:]) # is the packet a MMDVM DMRD packet ? if p[28:32] == b"DMRD": # if p[28] == 68 and p[29] == 77 and p[30] == 82 and p[31] == 68 : print("------ packet processing MMDVM ------") p1 = bytearray(p[48:82]) p1 = ahex(p1) print(p1, ":from DMRGateway(payload) Seq.Nr:", hex(p[32]), "Byte15-Flags:", format(p[43], '08b'), check_FrameType_MMDVM(p[43]), "SrcId:", int_id(p[33:36]), "T:", int_id(p[36:39])) # swap the ambe mmdvm payload HiByte<>LowByte needed for use in Hytera ambe payload p2 = byte_swap(p1) # save swapped ambe payload in ambe_paylaod_mmdvm for later insert in Hytera ambe payload ambe_payload_mmdvm = p2 print(p2, ":modify MMDVM(Byte_swapping) Seq.Nr:", hex(p[32]), "Byte15-Flags:", format(p[43], '08b')) # print(ahex(p[48:82])) # print(ahex(p[48:82]),":MMDVM Seq.Nr: ",hex(p[32]),"Status: ",format(p[43],'08b')) # is it a Hytera packet ? elif p[28:32] == b"ZZZZ" or p[28:32] == bytearray.fromhex( 'ee ee 11 11'): print("------ packet processing IPSC HYTERA ------") # print(ambe_payload_mmdvm,":saved") # get the SrcId from Hytera payload SrcId = p[96:99] # get the destination Id from Hytera paylaod _DestId = bytearray(p[92:95]) _DestId = ahex(_DestId) # change byteorder for correct calculating destination Id DestId = swap_DestId(_DestId) # print(ahex(p[44:46])) # get slot number from Hytera payload _slot = bytearray(p[44:46]) _slot = ahex(_slot) Slot = check_Slot_HYT(_slot) print(ahex(p[28:32]), ":first 4 Bytes from HytGW Seq.Nr:", hex(p[32]), "FrameType:", hex(p[36]), "Frametype:", check_FrameType_HYT(p[36])) print(ahex(p[54:88]), ":from HytGW unpatched SrcId:", int.from_bytes(SrcId, byteorder='little'), " T:", int.from_bytes(bhex(DestId), byteorder='big'), "(", check_CallType_HYT(p[90]), ") TS:", Slot) # delete the UDP checksum and fill with 00 00 as No_CheckSum p[26:28] = bytearray.fromhex('00 00') if p[28:32] == b"ZZZZ": # if p[28] == 90 and p[29] == 90 and p[30] == 90 and p[31] == 90: # save the last HYT SeqNr (0x00 to 0xFF) of UDP payload for later use (format uint8) last_seq_HYT = p[32] # print(hex(last_seq_HYT)) # replace the Offset_0-3 ZZZZ with 00 00 00 00 (not sure - stamped packet as packet from base station/master) p[28:32] = bytearray.fromhex('00 00 00 00') # check if the Hytera packet is START_OF_TRANSMISSION SeqNr.0x0/Offset_4 and 0x2/Offset_8 if p[36] == 2 and p[32] == 0: print( "CALL_START_PAYLOAD possible not correct => need MODIFY...processing packet..." ) # insert the MMDVM payload VOICE_START p[54:88] = bhex(ambe_payload_mmdvm) print(ahex(p[54:88]), ":to RD985 replace with MMDVM(swapped) SrcId:", int.from_bytes(SrcId, byteorder='little'), " T:", int.from_bytes(bhex(DestId), byteorder='big'), "(", check_CallType_HYT(p[90]), ") TS:", Slot) print("CALL_START => now OK") # check if the Hytera packet is END_OF_TRANSMISSION 0x2222/Offset_18-19 and 0x3/Offset_8 if p[46:48] == bytearray.fromhex('22 22') and p[36] == 3: # if p[46] == 34 and p[47] == 34 and p[36]) == 3: print( "CALL_END_WITHOUT_PAYLOAD => need MODIFY...processing packet..." ) # p[28:32] = bytearray.fromhex('00 00 00 00') # correct some bytes in Hytera payload p[48:50] = bytearray.fromhex('11 11') p[51:53] = bytearray.fromhex('00 10') # insert the MMDVM payload VOICE_TERMINATOR_WITH_LC because the Hytera_GW do it NOT and fill all with 00 ! try: p[54:88] = bhex(ambe_payload_mmdvm) except NameError: print("No codec yet") print(ahex(p[54:88]), ":to RD985 replace with MMDVM(swapped) SrcId:", int.from_bytes(SrcId, byteorder='little'), " T:", int.from_bytes(bhex(DestId), byteorder='big'), "(", check_CallType_HYT(p[90]), ") TS:", Slot) print("CALL_END_WITH_Voiceterminator_LC => now OK") # write all changes to packet in netqueue p = modify_packet(p) pkt.set_payload(bytes(p)) # we accept now the packet in netqueue with all changes and transmit it pkt.accept()
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): # Capture data headers global n_packet_assembly #logger.info(_dtype_vseq) if int_id(_dst_id) == data_id: #logger.info(type(_seq)) if type(_seq) is bytes: pckt_seq = int.from_bytes(_seq, 'big') else: pckt_seq = _seq if _call_type == call_type or ( _call_type == 'vcsbk' and pckt_seq > 3): #int.from_bytes(_seq, 'big') > 3 ): if _dtype_vseq == 6 or _dtype_vseq == 'group': global btf logger.info('Header from ' + str(int_id(_rf_src))) logger.info(ahex(bptc_decode(_data))) logger.info('Blocks to follow: ' + str(ba2num(bptc_decode(_data)[65:72]))) btf = ba2num(bptc_decode(_data)[65:72]) # Data blocks at 1/2 rate, see https://github.com/g4klx/MMDVM/blob/master/DMRDefines.h for data types. _dtype_seq defined here also if _dtype_vseq == 7: btf = btf - 1 logger.info('Block #: ' + str(btf)) #logger.info(_seq) global packet_assembly logger.info('Data block from ' + str(int_id(_rf_src))) logger.info(ahex(bptc_decode(_data))) if _seq == 0: n_packet_assembly = 0 packet_assembly = '' if btf < btf + 1: n_packet_assembly = n_packet_assembly + 1 packet_assembly = packet_assembly + str( bptc_decode(_data) ) #str((decode_full_lc(b_packet)).strip('bitarray(')) # Use block 0 as trigger. $GPRMC must also be in string to indicate NMEA. # This triggers the APRS upload if btf == 0: #_seq == 12: final_packet = bitarray( re.sub("\)|\(|bitarray|'", '', packet_assembly)).tobytes().decode( 'utf-8', 'ignore') if '$GPRMC' in final_packet: logger.info(final_packet + '\n') logger.info('Latitude: ' + re.sub(',', '', final_packet[29:40]) + ' Longitude: ' + re.sub(',', '', final_packet[41:53]) + ' Direction: ' + re.sub(',', '', final_packet[58:62]) + ' Speed: ' + re.sub(',', '', final_packet[53:58])) # Begin APRS format and upload aprs_loc_packet = str( get_alias(int_id(_rf_src), subscriber_ids) ) + '-' + str(user_ssid) + '>APRS,TCPIP*:/' + str( datetime.datetime.utcnow().strftime("%H%M%Sh") ) + str(final_packet[29:36]) + str( final_packet[39]) + '/' + str( re.sub(',', '', final_packet[41:49]) ) + str( final_packet[52] ) + '[/' + aprs_comment + ' DMR ID: ' + str( int_id(_rf_src)) logger.info(aprs_loc_packet) try: # Try parse of APRS packet. If it fails, it will not upload to APRS-IS aprslib.parse(aprs_loc_packet) AIS = aprslib.IS(aprs_callsign, passwd=aprs_passcode, host=aprs_server, port=aprs_port) AIS.connect() AIS.sendall(aprs_loc_packet) AIS.close() except: logger.info( 'Failed to parse packet. Packet may be deformed. Not uploaded.' ) # Get callsign based on DMR ID #logger.info(get_alias(int_id(_rf_src), subscriber_ids)) # End APRS-IS upload else: logger.info(final_packet) packet_assembly = '' logger.info(_seq) #logger.info(_dtype_vseq) logger.info(ahex(bptc_decode(_data)).decode('utf-8', 'ignore')) #logger.info(bitarray(re.sub("\)|\(|bitarray|'", '', str(bptc_decode(_data)).tobytes().decode('utf-8', 'ignore')))) else: pass
def datagramReceived(self, _packet, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! #logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_packet)) if _packet[:4] == DMRD: # DMRData -- encapsulated DMR data frame _data = _packet[:53] _hash = _packet[53:] _ckhs = hmac_new(self._config['PASSPHRASE'],_data,sha1).digest() if compare_digest(_hash, _ckhs) and _sockaddr == self._config['TARGET_SOCK']: _peer_id = _data[11:15] _seq = _data[4] _rf_src = _data[5:8] _dst_id = _data[8:11] _bits = _data[15] _slot = 2 if (_bits & 0x80) else 1 #_call_type = 'unit' if (_bits & 0x40) else 'group' if _bits & 0x40: _call_type = 'unit' elif (_bits & 0x23) == 0x23: _call_type = 'vcsbk' else: _call_type = 'group' _frame_type = (_bits & 0x30) >> 4 _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) # Sanity check for OpenBridge -- all calls must be on Slot 1 for Brandmeister or DMR+. Other HBlinks can process timeslot on OPB if the flag is set if _slot != 1 and not self._config['BOTH_SLOTS'] and not _call_type == 'unit': logger.error('(%s) OpenBridge packet discarded because it was not received on slot 1. SID: %s, TGID %s', self._system, int_id(_rf_src), int_id(_dst_id)) return # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if _stream_id not in self._laststrid: logger.info('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', self._system, int_id(_stream_id), int_id(_rf_src)) self._laststrid.append(_stream_id) return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if _stream_id not in self._laststrid: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid.append(_stream_id) return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if _stream_id not in self._laststrid: logger.info('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_rf_src)) self._laststrid.append(_stream_id) return if not acl_check(_dst_id, self._config['TG1_ACL']): if _stream_id not in self._laststrid: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid.append(_stream_id) return # Userland actions -- typically this is the function you subclass for an application self.dmrd_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) else: logger.info('(%s) OpenBridge HMAC failed, packet discarded - OPCODE: %s DATA: %s HMAC LENGTH: %s HMAC: %s', self._system, _packet[:4], repr(_packet[:53]), len(_packet[53:]), repr(_packet[53:]))
def master_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) # Extract the command, which is various length, all but one 4 significant characters -- RPTCL _command = _data[:4] if _command == DMRD: # DMRData -- encapsulated DMR data frame _peer_id = _data[11:15] if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == 'YES' \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: _seq = _data[4] _rf_src = _data[5:8] _dst_id = _data[8:11] _bits = _data[15] _slot = 2 if (_bits & 0x80) else 1 #_call_type = 'unit' if (_bits & 0x40) else 'group' if _bits & 0x40: _call_type = 'unit' elif (_bits & 0x23) == 0x23: _call_type = 'vcsbk' else: _call_type = 'group' _frame_type = (_bits & 0x30) >> 4 _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, _seq, int_id(_rf_src), int_id(_dst_id)) # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', self._system, int_id(_stream_id), int_id(_rf_src)) self._laststrid[_slot] = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_rf_src)) self._laststrid[_slot] = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): if self._laststrid[_slot] != _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): if self._laststrid[_slot]!= _stream_id: logger.info('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) self._laststrid[_slot] = _stream_id return # The basic purpose of a master is to repeat to the peers if self._config['REPEAT'] == True: pkt = [_data[:11], '', _data[15:]] for _peer in self._peers: if _peer != _peer_id: pkt[1] = _peer self.transport.write(b''.join(pkt), self._peers[_peer]['SOCKADDR']) #logger.debug('(%s) Packet on TS%s from %s (%s) for destination ID %s repeated to peer: %s (%s) [Stream ID: %s]', self._system, _slot, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), int_id(_dst_id), self._peers[_peer]['CALLSIGN'], int_id(_peer), int_id(_stream_id)) # Userland actions -- typically this is the function you subclass for an application self.dmrd_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) elif _command == RPTL: # RPTLogin -- a repeater wants to login _peer_id = _data[4:8] # Check to see if we've reached the maximum number of allowed peers if len(self._peers) < self._config['MAX_PEERS']: # Check for valid Radio ID if acl_check(_peer_id, self._CONFIG['GLOBAL']['REG_ACL']) and acl_check(_peer_id, self._config['REG_ACL']): # Build the configuration data strcuture for the peer self._peers.update({_peer_id: { 'CONNECTION': 'RPTL-RECEIVED', 'CONNECTED': time(), 'PINGS_RECEIVED': 0, 'LAST_PING': time(), 'SOCKADDR': _sockaddr, 'IP': _sockaddr[0], 'PORT': _sockaddr[1], 'SALT': randint(0,0xFFFFFFFF), 'RADIO_ID': str(int(ahex(_peer_id), 16)), 'CALLSIGN': '', 'RX_FREQ': '', 'TX_FREQ': '', 'TX_POWER': '', 'COLORCODE': '', 'LATITUDE': '', 'LONGITUDE': '', 'HEIGHT': '', 'LOCATION': '', 'DESCRIPTION': '', 'SLOTS': '', 'URL': '', 'SOFTWARE_ID': '', 'PACKAGE_ID': '', }}) logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._system, int_id(_peer_id), _sockaddr[0], _sockaddr[1]) _salt_str = bytes_4(self._peers[_peer_id]['SALT']) self.send_peer(_peer_id, b''.join([RPTACK, _salt_str])) self._peers[_peer_id]['CONNECTION'] = 'CHALLENGE_SENT' logger.info('(%s) Sent Challenge Response to %s for login: %s', self._system, int_id(_peer_id), self._peers[_peer_id]['SALT']) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Invalid Login from %s Radio ID: %s Denied by Registation ACL', self._system, _sockaddr[0], int_id(_peer_id)) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Registration denied from Radio ID: %s Maximum number of peers exceeded', self._system, int_id(_peer_id)) elif _command == RPTK: # Repeater has answered our login challenge _peer_id = _data[4:8] if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == 'CHALLENGE_SENT' \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: _this_peer = self._peers[_peer_id] _this_peer['LAST_PING'] = time() _sent_hash = _data[8:] _salt_str = bytes_4(_this_peer['SALT']) _calc_hash = bhex(sha256(_salt_str+self._config['PASSPHRASE']).hexdigest()) if _sent_hash == _calc_hash: _this_peer['CONNECTION'] = 'WAITING_CONFIG' self.send_peer(_peer_id, b''.join([RPTACK, _peer_id])) logger.info('(%s) Peer %s has completed the login exchange successfully', self._system, _this_peer['RADIO_ID']) else: logger.info('(%s) Peer %s has FAILED the login exchange successfully', self._system, _this_peer['RADIO_ID']) self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) del self._peers[_peer_id] else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Login challenge from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) elif _command == RPTC: # Repeater is sending it's configuraiton OR disconnecting if _data[:5] == RPTCL: # Disconnect command _peer_id = _data[5:9] if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == 'YES' \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: logger.info('(%s) Peer is closing down: %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id)) self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) del self._peers[_peer_id] else: _peer_id = _data[4:8] # Configure Command if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == 'WAITING_CONFIG' \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: _this_peer = self._peers[_peer_id] _this_peer['CONNECTION'] = 'YES' _this_peer['CONNECTED'] = time() _this_peer['LAST_PING'] = time() _this_peer['CALLSIGN'] = _data[8:16] _this_peer['RX_FREQ'] = _data[16:25] _this_peer['TX_FREQ'] = _data[25:34] _this_peer['TX_POWER'] = _data[34:36] _this_peer['COLORCODE'] = _data[36:38] _this_peer['LATITUDE'] = _data[38:46] _this_peer['LONGITUDE'] = _data[46:55] _this_peer['HEIGHT'] = _data[55:58] _this_peer['LOCATION'] = _data[58:78] _this_peer['DESCRIPTION'] = _data[78:97] _this_peer['SLOTS'] = _data[97:98] _this_peer['URL'] = _data[98:222] _this_peer['SOFTWARE_ID'] = _data[222:262] _this_peer['PACKAGE_ID'] = _data[262:302] self.send_peer(_peer_id, b''.join([RPTACK, _peer_id])) logger.info('(%s) Peer %s (%s) has sent repeater configuration', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) elif _command == RPTP: # RPTPing -- peer is pinging us _peer_id = _data[7:11] if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == "YES" \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: self._peers[_peer_id]['PINGS_RECEIVED'] += 1 self._peers[_peer_id]['LAST_PING'] = time() self.send_peer(_peer_id, b''.join([MSTPONG, _peer_id])) logger.debug('(%s) Received and answered RPTPING from peer %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id)) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id)) elif _command == RPTO: _peer_id = _data[4:8] if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == 'YES' \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: logger.info('(%s) Peer %s (%s) has send options: %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), _data[8:]) self.transport.write(b''.join([RPTACK, _peer_id]), _sockaddr) elif _command == DMRA: _peer_id = _data[4:8] logger.info('(%s) Recieved DMR Talker Alias from peer %s, subscriber %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_rf_src)) else: logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data))
def datagramReceived(self, data, addr): # HomeBrew Protocol Commands DMRD = b'DMRD' DMRA = b'DMRA' MSTCL = b'MSTCL' MSTNAK = b'MSTNAK' MSTPONG = b'MSTPONG' MSTN = b'MSTN' MSTP = b'MSTP' MSTC = b'MSTC' RPTL = b'RPTL' RPTPING = b'RPTPING' RPTCL = b'RPTCL' RPTL = b'RPTL' RPTACK = b'RPTACK' RPTK = b'RPTK' RPTC = b'RPTC' RPTP = b'RPTP' RPTA = b'RPTA' RPTO = b'RPTO' host, port = addr nowtime = time() Debug = self.debug #If the packet comes from the master if host == self.master: _command = data[:4] if _command == DMRD: _peer_id = data[11:15] elif _command == RPTA: if data[6:10] in self.peerTrack: _peer_id = data[6:10] else: _peer_id = self.connTrack[port] elif _command == MSTN: _peer_id = data[6:10] self.peerTrack[_peer_id]['timer'].cancel() self.reaper(_peer_id) return elif _command == MSTP: _peer_id = data[7:11] elif _command == MSTC: _peer_id = data[5:9] self.peerTrack[_peer_id]['timer'].cancel() self.reaper(_peer_id) return # _peer_id = self.connTrack[port] if self.debug: print(data) if _peer_id and _peer_id in self.peerTrack: self.transport.write(data, (self.peerTrack[_peer_id]['shost'], self.peerTrack[_peer_id]['sport'])) #self.peerTrack[_peer_id]['timer'].reset() return else: _command = data[:4] if _command == DMRD: # DMRData -- encapsulated DMR data frame _peer_id = data[11:15] elif _command == DMRA: # DMRAlias -- Talker Alias information _peer_id = _data[4:8] elif _command == RPTL: # RPTLogin -- a repeater wants to login _peer_id = data[4:8] elif _command == RPTK: # Repeater has answered our login challenge _peer_id = data[4:8] elif _command == RPTC: # Repeater is sending it's configuraiton OR disconnecting if data[:5] == RPTCL: # Disconnect command _peer_id = data[5:9] else: _peer_id = data[4:8] # Configure Command elif _command == RPTO: # options _peer_id = data[4:8] elif _command == RPTP: # RPTPing -- peer is pinging us _peer_id = data[7:11] else: return if _peer_id in self.peerTrack: _dport = self.peerTrack[_peer_id]['dport'] self.peerTrack[_peer_id]['sport'] = port self.peerTrack[_peer_id]['shost'] = host self.transport.write(data, ('127.0.0.1', _dport)) self.peerTrack[_peer_id]['timer'].reset() if self.debug: print(data) return else: if int_id(_peer_id) in self.blackList: return #for _dport in self.connTrack: while True: _dport = random.randint(1, (self.numPorts - 1)) _dport = _dport + self.destPortStart if not self.connTrack[_dport]: break self.connTrack[_dport] = _peer_id self.peerTrack[_peer_id] = {} self.peerTrack[_peer_id]['dport'] = _dport self.peerTrack[_peer_id]['sport'] = port self.peerTrack[_peer_id]['shost'] = host self.peerTrack[_peer_id]['timer'] = ResettableTimer( self.timeout, self.reaper, [_peer_id]) self.peerTrack[_peer_id]['timer'].start() self.transport.write(data, (self.master, _dport)) if self.debug: print(data) return
def acl_check(_id, _acl): id = int_id(_id) for entry in _acl[1]: if entry[0] <= id <= entry[1]: return _acl[0] return not _acl[0]
def datagramReceived(self, data, addr): # HomeBrew Protocol Commands DMRD = b'DMRD' DMRA = b'DMRA' MSTCL = b'MSTCL' MSTNAK = b'MSTNAK' MSTPONG = b'MSTPONG' MSTN = b'MSTN' MSTP = b'MSTP' MSTC = b'MSTC' RPTL = b'RPTL' RPTPING = b'RPTPING' RPTCL = b'RPTCL' RPTL = b'RPTL' RPTACK = b'RPTACK' RPTK = b'RPTK' RPTC = b'RPTC' RPTP = b'RPTP' RPTA = b'RPTA' RPTO = b'RPTO' _peer_id = False host, port = addr nowtime = time() Debug = self.debug #If the packet comes from the master if host == self.master: _command = data[:4] if _command == DMRD: _peer_id = data[11:15] elif _command == RPTA: if data[6:10] in self.peerTrack: _peer_id = data[6:10] else: _peer_id = self.connTrack[port] elif _command == MSTN: _peer_id = data[6:10] elif _command == MSTP: _peer_id = data[7:11] elif _command == MSTC: _peer_id = data[5:9] if self.debug: print(data) if _peer_id in self.peerTrack: self.transport.write(data, (self.peerTrack[_peer_id]['shost'], self.peerTrack[_peer_id]['sport'])) # Remove the client after send a MSTN or MSTC packet if _command in (MSTN, MSTC): # Give time to the client for a reply to prevent port reassignment self.peerTrack[_peer_id]['timer'].reset(15) return else: _command = data[:4] if _command == DMRD: # DMRData -- encapsulated DMR data frame _peer_id = data[11:15] elif _command == DMRA: # DMRAlias -- Talker Alias information _peer_id = data[4:8] elif _command == RPTL: # RPTLogin -- a repeater wants to login _peer_id = data[4:8] elif _command == RPTK: # Repeater has answered our login challenge _peer_id = data[4:8] elif _command == RPTC: # Repeater is sending it's configuraiton OR disconnecting if data[:5] == RPTCL: # Disconnect command _peer_id = data[5:9] else: _peer_id = data[4:8] # Configure Command elif _command == RPTO: # options _peer_id = data[4:8] elif _command == RPTP: # RPTPing -- peer is pinging us _peer_id = data[7:11] else: return if _peer_id in self.peerTrack: _dport = self.peerTrack[_peer_id]['dport'] self.peerTrack[_peer_id]['sport'] = port self.peerTrack[_peer_id]['shost'] = host self.transport.write(data, (self.master, _dport)) self.peerTrack[_peer_id]['timer'].reset(self.timeout) if self.debug: print(data) return else: if int_id(_peer_id) in self.blackList: return # Make a list with the available ports _ports_avail = [ port for port in self.connTrack if not self.connTrack[port] ] if len(_ports_avail) > 0: _dport = random.choice(_ports_avail) else: return self.connTrack[_dport] = _peer_id self.peerTrack[_peer_id] = {} self.peerTrack[_peer_id]['dport'] = _dport self.peerTrack[_peer_id]['sport'] = port self.peerTrack[_peer_id]['shost'] = host self.peerTrack[_peer_id]['timer'] = reactor.callLater( self.timeout, self.reaper, _peer_id) self.transport.write(data, (self.master, _dport)) if self.clientinfo and _peer_id != b'\x00m@\xd7': print( f'New client: ID:{str(int_id(_peer_id)).rjust(9)} IP:{host.rjust(15)} Port:{port}, assigned to port:{_dport}.' ) if self.debug: print(data) return