From 5511a3b25aa51d944c4b0a34d530877ea4e629f7 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Tue, 25 Sep 2018 20:17:55 -0500 Subject: [PATCH 01/56] OpenBridge initial commit The beginning of OpenBridge support. It does not yet do anything, so downloading this is worthless for all but selected alpha testers. --- hb_config.py | 13 +++++++++ hb_const.py | 2 +- hblink-SAMPLE.cfg | 22 +++++++++++++++ hblink.py | 72 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 4 deletions(-) diff --git a/hb_config.py b/hb_config.py index 628f6ac..b892205 100755 --- a/hb_config.py +++ b/hb_config.py @@ -151,6 +151,19 @@ def build_config(_config_file): 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME') }}) CONFIG['SYSTEMS'][section].update({'PEERS': {}}) + + elif config.get(section, 'MODE') == 'OPENBRIDGE': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'NETWORK_ID': config.getint(section, 'NETWORK_ID'), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'PASSPHRASE': config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], + 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), + 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), + }}) + except ConfigParser.Error, err: print "Cannot parse configuration file. %s" %err diff --git a/hb_const.py b/hb_const.py index 815f5e5..7732b73 100755 --- a/hb_const.py +++ b/hb_const.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # ############################################################################### -# Copyright (C) 2016 Cortney T. Buffington, N0MJS +# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 8b69fc9..e9644c8 100644 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -75,6 +75,28 @@ STALE_DAYS: 7 EXPORT_IP: 127.0.0.1 EXPORT_PORT: 1234 +# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS +# OpenBridge is a protocol originall created by DMR+ for connection between an +# IPSC2 server and Brandmeister. It has been implemented here at the suggestion +# of the Brandmeister team as a way to legitimately connect HBlink to the +# Brandemiester network. +# It is recommended to name the system the ID of the Brandmeister server that +# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the +# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password +# that must be agreed upon between you and the operator of the server you are +# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that +# will be sent to the other server to identify this connection. +# other parameters follow the other system types. +[3102] +MODE: OPENBRIDGE +ENABLED: True +IP: +PORT: 62035 +NETWORK_ID: 3120101 +PASSPHRASE: c0edbabe +TARGET_IP: 74.91.114.19 +TARGET_PORT: 62035 + # MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS # HomeBrew Protocol Master instances go here. # IP may be left blank if there's one interface on your system. diff --git a/hblink.py b/hblink.py index c003c3f..e022af8 100755 --- a/hblink.py +++ b/hblink.py @@ -33,7 +33,8 @@ from __future__ import print_function from binascii import b2a_hex as ahex from binascii import a2b_hex as bhex from random import randint -from hashlib import sha256 +from hashlib import sha256, sha1 +from hmac import new as hmac_new, compare_digest from time import time from bitstring import BitArray from importlib import import_module @@ -181,6 +182,68 @@ class AMBE: self._sock.sendto(ambeBytes[18:27], (self._exp_ip, self._exp_port)) +#************************************************ +# OPENBRIDGE CLASS +#************************************************ + +class OPENBRIDGE(DatagramProtocol): + def __init__(self, _name, _config, _logger, _report): + # Define a few shortcuts to make the rest of the class more readable + self._CONFIG = _config + self._system = _name + self._logger = _logger + self._report = _report + self._config = self._CONFIG['SYSTEMS'][self._system] + self._localsock = (self._config['IP'], self._config['PORT']) + self._targetsock = (self._config['TARGET_IP'], self._config['TARGET_PORT']) + print(self._config['NETWORK_ID']) + + def dereg(self): + self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) + + def send_system(self, _packet): + if _packet[:4] == 'DMRD': + self.transport.write(_packet, (self._config['TARGET_IP'], self._config['TARGET_PORT'])) + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + pass + #print(int_id(_peer_id), int_id(_rf_src), int_id(_dst_id), int_id(_seq), _slot, _call_type, _frame_type, repr(_dtype_vseq), int_id(_stream_id)) + + _dmrd = _data[:53] + _hash = _data[53:] + + _ckhs = hmac_new(self._config['PASSPHRASE'],_dmrd,sha1).digest() + if compare_digest(_hash, _ckhs): + print('PEER:', int_id(_peer_id), 'RF SOURCE:', int_id(_rf_src), 'DESTINATION:', int_id(_dst_id), 'SLOT', _slot, 'SEQ:', int_id(_seq), 'STREAM:', int_id(_stream_id)) + else: + self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) + + # Aliased in __init__ to datagramReceived if system is a master + def datagramReceived(self, _data, _sockaddr): + # Keep This Line Commented Unless HEAVILY Debugging! + # self._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 _sockaddr == self._targetsock: + _seq = _data[4] + _rf_src = _data[5:8] + _dst_id = _data[8:11] + _bits = int_id(_data[15]) + _slot = 2 if (_bits & 0x80) else 1 + _call_type = 'unit' if (_bits & 0x40) else '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] + #self._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)) + + # 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) + + #************************************************ # HB MASTER CLASS #************************************************ @@ -649,10 +712,13 @@ if __name__ == '__main__': report_server = config_reports(CONFIG, logger, reportFactory) # HBlink instance creation - logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'HBlink.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: - systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + systems[system] = OPENBRIDGE(system, CONFIG, logger, report_server) + else: + systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) From 0904cade831eed975511c6e1f46194a683e88bfa Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Tue, 25 Sep 2018 20:19:25 -0500 Subject: [PATCH 02/56] Initial upload of OpenBridge Support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is only meaningful for the few selected pre-alpha testers of OpenBridge support. It doesn’t actually do anything useful for those building their own networks at this point. --- hblink-SAMPLE.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index e9644c8..b6995b7 100644 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -92,8 +92,8 @@ MODE: OPENBRIDGE ENABLED: True IP: PORT: 62035 -NETWORK_ID: 3120101 -PASSPHRASE: c0edbabe +NETWORK_ID: 3129100 +PASSPHRASE: password TARGET_IP: 74.91.114.19 TARGET_PORT: 62035 From 41fd8e6964ca06986cacb66f3cf14d6468aa18be Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 26 Sep 2018 09:00:26 -0500 Subject: [PATCH 03/56] Update hblink-SAMPLE.cfg Remove live IP and server ID --- hblink-SAMPLE.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index b6995b7..af1e833 100644 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -87,14 +87,14 @@ EXPORT_PORT: 1234 # connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that # will be sent to the other server to identify this connection. # other parameters follow the other system types. -[3102] +[3199] MODE: OPENBRIDGE ENABLED: True IP: PORT: 62035 NETWORK_ID: 3129100 PASSPHRASE: password -TARGET_IP: 74.91.114.19 +TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 # MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS From b20309c7767d6120561bf3acf88e8fd86e5e603b Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 26 Sep 2018 09:02:10 -0500 Subject: [PATCH 04/56] Reorganization 1 class per system type Separating HBSYSTEM into HBMASTER and HBPEER to reduce memory footprint and allow easier updating. Also cleaning up and normalizing the OPENBRIDGE class to match the standard HB classes better. --- hb_config.py | 1 + hblink.py | 173 +++++++++++++++++++++++++++------------------------ 2 files changed, 91 insertions(+), 83 deletions(-) diff --git a/hb_config.py b/hb_config.py index b892205..8633f4c 100755 --- a/hb_config.py +++ b/hb_config.py @@ -160,6 +160,7 @@ def build_config(_config_file): 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], + 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), }}) diff --git a/hblink.py b/hblink.py index e022af8..68fe8d7 100755 --- a/hblink.py +++ b/hblink.py @@ -194,41 +194,33 @@ class OPENBRIDGE(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - self._localsock = (self._config['IP'], self._config['PORT']) - self._targetsock = (self._config['TARGET_IP'], self._config['TARGET_PORT']) - print(self._config['NETWORK_ID']) def dereg(self): self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) def send_system(self, _packet): if _packet[:4] == 'DMRD': + _packet = _packet[:11] + self._config['NETWORK_ID'] + _packet[15:] self.transport.write(_packet, (self._config['TARGET_IP'], self._config['TARGET_PORT'])) + # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! + # self._logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) + else: + self._logger.error('(%s) OpenBridge system was asked to send non DMRD packet') def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass #print(int_id(_peer_id), int_id(_rf_src), int_id(_dst_id), int_id(_seq), _slot, _call_type, _frame_type, repr(_dtype_vseq), int_id(_stream_id)) - - _dmrd = _data[:53] - _hash = _data[53:] - _ckhs = hmac_new(self._config['PASSPHRASE'],_dmrd,sha1).digest() - if compare_digest(_hash, _ckhs): - print('PEER:', int_id(_peer_id), 'RF SOURCE:', int_id(_rf_src), 'DESTINATION:', int_id(_dst_id), 'SLOT', _slot, 'SEQ:', int_id(_seq), 'STREAM:', int_id(_stream_id)) - else: - self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) - - # Aliased in __init__ to datagramReceived if system is a master - def datagramReceived(self, _data, _sockaddr): + def datagramReceived(self, _packet, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._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 _sockaddr == self._targetsock: + if _packet[:4] == 'DMRD': # DMRData -- encapsulated DMR data frame + _data = _data[:53] + _ckhs = hmac_new(self._config['PASSPHRASE'],_data[53:],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] @@ -242,13 +234,15 @@ class OPENBRIDGE(DatagramProtocol): # 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: + self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) #************************************************ # HB MASTER CLASS #************************************************ -class HBSYSTEM(DatagramProtocol): +class HBMASTER(DatagramProtocol): def __init__(self, _name, _config, _logger, _report): # Define a few shortcuts to make the rest of the class more readable self._CONFIG = _config @@ -256,21 +250,7 @@ class HBSYSTEM(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - - # Define shortcuts and generic function names based on the type of system we are - if self._config['MODE'] == 'MASTER': - self._peers = self._CONFIG['SYSTEMS'][self._system]['PEERS'] - self.send_system = self.send_peers - self.maintenance_loop = self.master_maintenance_loop - self.datagramReceived = self.master_datagramReceived - self.dereg = self.master_dereg - - elif self._config['MODE'] == 'PEER': - self._stats = self._config['STATS'] - self.send_system = self.send_master - self.maintenance_loop = self.peer_maintenance_loop - self.datagramReceived = self.peer_datagramReceived - self.dereg = self.peer_dereg + self._peers = self._CONFIG['SYSTEMS'][self._system]['PEERS'] # Configure for AMBE audio export if enabled if self._config['EXPORT_AMBE']: @@ -281,8 +261,7 @@ class HBSYSTEM(DatagramProtocol): self._system_maintenance = task.LoopingCall(self.maintenance_loop) self._system_maintenance_loop = self._system_maintenance.start(self._CONFIG['GLOBAL']['PING_TIME']) - # Aliased in __init__ to maintenance_loop if system is a master - def master_maintenance_loop(self): + def maintenance_loop(self): self._logger.debug('(%s) Master maintenance loop started', self._system) for peer in self._peers: _this_peer = self._peers[peer] @@ -291,29 +270,8 @@ class HBSYSTEM(DatagramProtocol): self._logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) # Remove any timed out peers from the configuration del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] - - # Aliased in __init__ to maintenance_loop if system is a peer - def peer_maintenance_loop(self): - self._logger.debug('(%s) Peer maintenance loop started', self._system) - if self._stats['PING_OUTSTANDING']: - self._stats['NUM_OUTSTANDING'] += 1 - # If we're not connected, zero out the stats and send a login request RPTL - if self._stats['CONNECTION'] != 'YES' or self._stats['NUM_OUTSTANDING'] >= self._CONFIG['GLOBAL']['MAX_MISSED']: - self._stats['PINGS_SENT'] = 0 - self._stats['PINGS_ACKD'] = 0 - self._stats['NUM_OUTSTANDING'] = 0 - self._stats['PING_OUTSTANDING'] = False - self._stats['CONNECTION'] = 'RPTL_SENT' - self.send_master('RPTL'+self._config['RADIO_ID']) - self._logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) - # If we are connected, sent a ping to the master and increment the counter - if self._stats['CONNECTION'] == 'YES': - self.send_master('RPTPING'+self._config['RADIO_ID']) - self._logger.debug('(%s) RPTPING Sent to Master. Total Sent: %s, Total Missed: %s, Currently Outstanding: %s', self._system, self._stats['PINGS_SENT'], self._stats['PINGS_SENT'] - self._stats['PINGS_ACKD'], self._stats['NUM_OUTSTANDING']) - self._stats['PINGS_SENT'] += 1 - self._stats['PING_OUTSTANDING'] = True - def send_peers(self, _packet): + def send_system(self, _packet): for _peer in self._peers: self.send_peer(_peer, _packet) #self._logger.debug('(%s) Packet sent to peer %s', self._system, self._peers[_peer]['RADIO_ID']) @@ -323,29 +281,17 @@ class HBSYSTEM(DatagramProtocol): _packet = _packet[:11] + _peer + _packet[15:] self.transport.write(_packet, self._peers[_peer]['SOCKADDR']) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - #self._logger.debug('(%s) TX Packet to %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet)) - - def send_master(self, _packet): - if _packet[:4] == 'DMRD': - _packet = _packet[:11] + self._config['RADIO_ID'] + _packet[15:] - self.transport.write(_packet, self._config['MASTER_SOCKADDR']) - # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - # self._logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) - + #self._logger.debug('(%s) TX Packet to Peer %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet)) + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass - def master_dereg(self): + def dereg(self): for _peer in self._peers: self.send_peer(_peer, 'MSTCL'+_peer) self._logger.info('(%s) De-Registration sent to Peer: %s (%s)', self._system, self._peers[_peer]['CALLSIGN'], self._peers[_peer]['RADIO_ID']) - - def peer_dereg(self): - self.send_master('RPTCL'+self._config['RADIO_ID']) - self._logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) - - # Aliased in __init__ to datagramReceived if system is a master - def master_datagramReceived(self, _data, _sockaddr): + + def datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) @@ -497,8 +443,65 @@ class HBSYSTEM(DatagramProtocol): else: self._logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) - # Aliased in __init__ to datagramReceived if system is a peer - def peer_datagramReceived(self, _data, _sockaddr): + +#************************************************ +# HB PEER CLASS +#************************************************ + +class HBPEER(DatagramProtocol): + def __init__(self, _name, _config, _logger, _report): + # Define a few shortcuts to make the rest of the class more readable + self._CONFIG = _config + self._system = _name + self._logger = _logger + self._report = _report + self._config = self._CONFIG['SYSTEMS'][self._system] + self._stats = self._config['STATS'] + + # Configure for AMBE audio export if enabled + if self._config['EXPORT_AMBE']: + self._ambe = AMBE(_config, _logger) + + def startProtocol(self): + # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds + self._system_maintenance = task.LoopingCall(self.maintenance_loop) + self._system_maintenance_loop = self._system_maintenance.start(self._CONFIG['GLOBAL']['PING_TIME']) + + def maintenance_loop(self): + self._logger.debug('(%s) Peer maintenance loop started', self._system) + if self._stats['PING_OUTSTANDING']: + self._stats['NUM_OUTSTANDING'] += 1 + # If we're not connected, zero out the stats and send a login request RPTL + if self._stats['CONNECTION'] != 'YES' or self._stats['NUM_OUTSTANDING'] >= self._CONFIG['GLOBAL']['MAX_MISSED']: + self._stats['PINGS_SENT'] = 0 + self._stats['PINGS_ACKD'] = 0 + self._stats['NUM_OUTSTANDING'] = 0 + self._stats['PING_OUTSTANDING'] = False + self._stats['CONNECTION'] = 'RPTL_SENT' + self.send_system('RPTL'+self._config['RADIO_ID']) + self._logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) + # If we are connected, sent a ping to the master and increment the counter + if self._stats['CONNECTION'] == 'YES': + self.send_system('RPTPING'+self._config['RADIO_ID']) + self._logger.debug('(%s) RPTPING Sent to Master. Total Sent: %s, Total Missed: %s, Currently Outstanding: %s', self._system, self._stats['PINGS_SENT'], self._stats['PINGS_SENT'] - self._stats['PINGS_ACKD'], self._stats['NUM_OUTSTANDING']) + self._stats['PINGS_SENT'] += 1 + self._stats['PING_OUTSTANDING'] = True + + def send_system(self, _packet): + if _packet[:4] == 'DMRD': + _packet = _packet[:11] + self._config['RADIO_ID'] + _packet[15:] + self.transport.write(_packet, self._config['MASTER_SOCKADDR']) + # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! + # self._logger.debug('(%s) TX Packet to Master %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + pass + + def dereg(self): + self.send_system('RPTCL'+self._config['RADIO_ID']) + self._logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) + + def datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) @@ -540,7 +543,7 @@ class HBSYSTEM(DatagramProtocol): self._logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32)) _pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest() _pass_hash = bhex(_pass_hash) - self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash) + self.send_system('RPTK'+self._config['RADIO_ID']+_pass_hash) self._stats['CONNECTION'] = 'AUTHENTICATED' elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge... @@ -563,7 +566,7 @@ class HBSYSTEM(DatagramProtocol): self._config['SOFTWARE_ID']+\ self._config['PACKAGE_ID'] - self.send_master('RPTC'+_config_packet) + self.send_system('RPTC'+_config_packet) self._stats['CONNECTION'] = 'CONFIG-SENT' self._logger.info('(%s) Repeater Configuration Sent', self._system) else: @@ -575,7 +578,7 @@ class HBSYSTEM(DatagramProtocol): if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation self._logger.info('(%s) Repeater Configuration Accepted', self._system) if self._config['OPTIONS']: - self.send_master('RPTO'+self._config['RADIO_ID']+self._config['OPTIONS']) + self.send_system('RPTO'+self._config['RADIO_ID']+self._config['OPTIONS']) self._stats['CONNECTION'] = 'OPTIONS-SENT' self._logger.info('(%s) Sent options: (%s)', self._system, self._config['OPTIONS']) else: @@ -717,8 +720,12 @@ if __name__ == '__main__': if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': systems[system] = OPENBRIDGE(system, CONFIG, logger, report_server) + elif CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER': + systems[system] = HBMASTER(system, CONFIG, logger, report_server) + elif CONFIG['SYSTEMS'][system]['MODE'] == 'PEER': + systems[system] = HBPEER(system, CONFIG, logger, report_server) else: - systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) + logger.error('%s instance error: %s, %s. No such MODE: %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system], CONFIG['SYSTEMS'][system]['MODE']) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) From d1f827f1f2ff16e86fa377ce42cedb0942c9c5ec Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 26 Sep 2018 15:41:00 -0500 Subject: [PATCH 05/56] Revert "Reorganization 1 class per system type" This reverts commit b20309c7767d6120561bf3acf88e8fd86e5e603b. --- hb_config.py | 1 - hblink.py | 173 ++++++++++++++++++++++++--------------------------- 2 files changed, 83 insertions(+), 91 deletions(-) diff --git a/hb_config.py b/hb_config.py index 8633f4c..b892205 100755 --- a/hb_config.py +++ b/hb_config.py @@ -160,7 +160,6 @@ def build_config(_config_file): 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], - 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), }}) diff --git a/hblink.py b/hblink.py index 68fe8d7..e022af8 100755 --- a/hblink.py +++ b/hblink.py @@ -194,33 +194,41 @@ class OPENBRIDGE(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] + self._localsock = (self._config['IP'], self._config['PORT']) + self._targetsock = (self._config['TARGET_IP'], self._config['TARGET_PORT']) + print(self._config['NETWORK_ID']) def dereg(self): self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) def send_system(self, _packet): if _packet[:4] == 'DMRD': - _packet = _packet[:11] + self._config['NETWORK_ID'] + _packet[15:] self.transport.write(_packet, (self._config['TARGET_IP'], self._config['TARGET_PORT'])) - # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - # self._logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) - else: - self._logger.error('(%s) OpenBridge system was asked to send non DMRD packet') def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass #print(int_id(_peer_id), int_id(_rf_src), int_id(_dst_id), int_id(_seq), _slot, _call_type, _frame_type, repr(_dtype_vseq), int_id(_stream_id)) + + _dmrd = _data[:53] + _hash = _data[53:] - def datagramReceived(self, _packet, _sockaddr): + _ckhs = hmac_new(self._config['PASSPHRASE'],_dmrd,sha1).digest() + if compare_digest(_hash, _ckhs): + print('PEER:', int_id(_peer_id), 'RF SOURCE:', int_id(_rf_src), 'DESTINATION:', int_id(_dst_id), 'SLOT', _slot, 'SEQ:', int_id(_seq), 'STREAM:', int_id(_stream_id)) + else: + self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) + + # Aliased in __init__ to datagramReceived if system is a master + def datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._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 _packet[:4] == 'DMRD': # DMRData -- encapsulated DMR data frame - _data = _data[:53] - _ckhs = hmac_new(self._config['PASSPHRASE'],_data[53:],sha1).digest() - - if compare_digest(_hash, _ckhs) and _sockaddr == self._config['TARGET_SOCK']: - _peer_id = _data[11:15] + if _command == 'DMRD': # DMRData -- encapsulated DMR data frame + _peer_id = _data[11:15] + if _sockaddr == self._targetsock: _seq = _data[4] _rf_src = _data[5:8] _dst_id = _data[8:11] @@ -234,15 +242,13 @@ class OPENBRIDGE(DatagramProtocol): # 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: - self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) #************************************************ # HB MASTER CLASS #************************************************ -class HBMASTER(DatagramProtocol): +class HBSYSTEM(DatagramProtocol): def __init__(self, _name, _config, _logger, _report): # Define a few shortcuts to make the rest of the class more readable self._CONFIG = _config @@ -250,7 +256,21 @@ class HBMASTER(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - self._peers = self._CONFIG['SYSTEMS'][self._system]['PEERS'] + + # Define shortcuts and generic function names based on the type of system we are + if self._config['MODE'] == 'MASTER': + self._peers = self._CONFIG['SYSTEMS'][self._system]['PEERS'] + self.send_system = self.send_peers + self.maintenance_loop = self.master_maintenance_loop + self.datagramReceived = self.master_datagramReceived + self.dereg = self.master_dereg + + elif self._config['MODE'] == 'PEER': + self._stats = self._config['STATS'] + self.send_system = self.send_master + self.maintenance_loop = self.peer_maintenance_loop + self.datagramReceived = self.peer_datagramReceived + self.dereg = self.peer_dereg # Configure for AMBE audio export if enabled if self._config['EXPORT_AMBE']: @@ -261,7 +281,8 @@ class HBMASTER(DatagramProtocol): self._system_maintenance = task.LoopingCall(self.maintenance_loop) self._system_maintenance_loop = self._system_maintenance.start(self._CONFIG['GLOBAL']['PING_TIME']) - def maintenance_loop(self): + # Aliased in __init__ to maintenance_loop if system is a master + def master_maintenance_loop(self): self._logger.debug('(%s) Master maintenance loop started', self._system) for peer in self._peers: _this_peer = self._peers[peer] @@ -270,8 +291,29 @@ class HBMASTER(DatagramProtocol): self._logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) # Remove any timed out peers from the configuration del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] + + # Aliased in __init__ to maintenance_loop if system is a peer + def peer_maintenance_loop(self): + self._logger.debug('(%s) Peer maintenance loop started', self._system) + if self._stats['PING_OUTSTANDING']: + self._stats['NUM_OUTSTANDING'] += 1 + # If we're not connected, zero out the stats and send a login request RPTL + if self._stats['CONNECTION'] != 'YES' or self._stats['NUM_OUTSTANDING'] >= self._CONFIG['GLOBAL']['MAX_MISSED']: + self._stats['PINGS_SENT'] = 0 + self._stats['PINGS_ACKD'] = 0 + self._stats['NUM_OUTSTANDING'] = 0 + self._stats['PING_OUTSTANDING'] = False + self._stats['CONNECTION'] = 'RPTL_SENT' + self.send_master('RPTL'+self._config['RADIO_ID']) + self._logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) + # If we are connected, sent a ping to the master and increment the counter + if self._stats['CONNECTION'] == 'YES': + self.send_master('RPTPING'+self._config['RADIO_ID']) + self._logger.debug('(%s) RPTPING Sent to Master. Total Sent: %s, Total Missed: %s, Currently Outstanding: %s', self._system, self._stats['PINGS_SENT'], self._stats['PINGS_SENT'] - self._stats['PINGS_ACKD'], self._stats['NUM_OUTSTANDING']) + self._stats['PINGS_SENT'] += 1 + self._stats['PING_OUTSTANDING'] = True - def send_system(self, _packet): + def send_peers(self, _packet): for _peer in self._peers: self.send_peer(_peer, _packet) #self._logger.debug('(%s) Packet sent to peer %s', self._system, self._peers[_peer]['RADIO_ID']) @@ -281,17 +323,29 @@ class HBMASTER(DatagramProtocol): _packet = _packet[:11] + _peer + _packet[15:] self.transport.write(_packet, self._peers[_peer]['SOCKADDR']) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - #self._logger.debug('(%s) TX Packet to Peer %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet)) - + #self._logger.debug('(%s) TX Packet to %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet)) + + def send_master(self, _packet): + if _packet[:4] == 'DMRD': + _packet = _packet[:11] + self._config['RADIO_ID'] + _packet[15:] + self.transport.write(_packet, self._config['MASTER_SOCKADDR']) + # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! + # self._logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass - def dereg(self): + def master_dereg(self): for _peer in self._peers: self.send_peer(_peer, 'MSTCL'+_peer) self._logger.info('(%s) De-Registration sent to Peer: %s (%s)', self._system, self._peers[_peer]['CALLSIGN'], self._peers[_peer]['RADIO_ID']) - - def datagramReceived(self, _data, _sockaddr): + + def peer_dereg(self): + self.send_master('RPTCL'+self._config['RADIO_ID']) + self._logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) + + # Aliased in __init__ to datagramReceived if system is a master + def master_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) @@ -443,65 +497,8 @@ class HBMASTER(DatagramProtocol): else: self._logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) - -#************************************************ -# HB PEER CLASS -#************************************************ - -class HBPEER(DatagramProtocol): - def __init__(self, _name, _config, _logger, _report): - # Define a few shortcuts to make the rest of the class more readable - self._CONFIG = _config - self._system = _name - self._logger = _logger - self._report = _report - self._config = self._CONFIG['SYSTEMS'][self._system] - self._stats = self._config['STATS'] - - # Configure for AMBE audio export if enabled - if self._config['EXPORT_AMBE']: - self._ambe = AMBE(_config, _logger) - - def startProtocol(self): - # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds - self._system_maintenance = task.LoopingCall(self.maintenance_loop) - self._system_maintenance_loop = self._system_maintenance.start(self._CONFIG['GLOBAL']['PING_TIME']) - - def maintenance_loop(self): - self._logger.debug('(%s) Peer maintenance loop started', self._system) - if self._stats['PING_OUTSTANDING']: - self._stats['NUM_OUTSTANDING'] += 1 - # If we're not connected, zero out the stats and send a login request RPTL - if self._stats['CONNECTION'] != 'YES' or self._stats['NUM_OUTSTANDING'] >= self._CONFIG['GLOBAL']['MAX_MISSED']: - self._stats['PINGS_SENT'] = 0 - self._stats['PINGS_ACKD'] = 0 - self._stats['NUM_OUTSTANDING'] = 0 - self._stats['PING_OUTSTANDING'] = False - self._stats['CONNECTION'] = 'RPTL_SENT' - self.send_system('RPTL'+self._config['RADIO_ID']) - self._logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) - # If we are connected, sent a ping to the master and increment the counter - if self._stats['CONNECTION'] == 'YES': - self.send_system('RPTPING'+self._config['RADIO_ID']) - self._logger.debug('(%s) RPTPING Sent to Master. Total Sent: %s, Total Missed: %s, Currently Outstanding: %s', self._system, self._stats['PINGS_SENT'], self._stats['PINGS_SENT'] - self._stats['PINGS_ACKD'], self._stats['NUM_OUTSTANDING']) - self._stats['PINGS_SENT'] += 1 - self._stats['PING_OUTSTANDING'] = True - - def send_system(self, _packet): - if _packet[:4] == 'DMRD': - _packet = _packet[:11] + self._config['RADIO_ID'] + _packet[15:] - self.transport.write(_packet, self._config['MASTER_SOCKADDR']) - # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - # self._logger.debug('(%s) TX Packet to Master %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) - - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): - pass - - def dereg(self): - self.send_system('RPTCL'+self._config['RADIO_ID']) - self._logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) - - def datagramReceived(self, _data, _sockaddr): + # Aliased in __init__ to datagramReceived if system is a peer + def peer_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) @@ -543,7 +540,7 @@ class HBPEER(DatagramProtocol): self._logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32)) _pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest() _pass_hash = bhex(_pass_hash) - self.send_system('RPTK'+self._config['RADIO_ID']+_pass_hash) + self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash) self._stats['CONNECTION'] = 'AUTHENTICATED' elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge... @@ -566,7 +563,7 @@ class HBPEER(DatagramProtocol): self._config['SOFTWARE_ID']+\ self._config['PACKAGE_ID'] - self.send_system('RPTC'+_config_packet) + self.send_master('RPTC'+_config_packet) self._stats['CONNECTION'] = 'CONFIG-SENT' self._logger.info('(%s) Repeater Configuration Sent', self._system) else: @@ -578,7 +575,7 @@ class HBPEER(DatagramProtocol): if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation self._logger.info('(%s) Repeater Configuration Accepted', self._system) if self._config['OPTIONS']: - self.send_system('RPTO'+self._config['RADIO_ID']+self._config['OPTIONS']) + self.send_master('RPTO'+self._config['RADIO_ID']+self._config['OPTIONS']) self._stats['CONNECTION'] = 'OPTIONS-SENT' self._logger.info('(%s) Sent options: (%s)', self._system, self._config['OPTIONS']) else: @@ -720,12 +717,8 @@ if __name__ == '__main__': if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': systems[system] = OPENBRIDGE(system, CONFIG, logger, report_server) - elif CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER': - systems[system] = HBMASTER(system, CONFIG, logger, report_server) - elif CONFIG['SYSTEMS'][system]['MODE'] == 'PEER': - systems[system] = HBPEER(system, CONFIG, logger, report_server) else: - logger.error('%s instance error: %s, %s. No such MODE: %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system], CONFIG['SYSTEMS'][system]['MODE']) + systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) From 3bb39eb19a7ada94052e1d4395de7273a7c03d57 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 26 Sep 2018 15:48:48 -0500 Subject: [PATCH 06/56] Clean up OPENBRIDGE class --- hb_config.py | 1 + hblink.py | 34 ++++++++++++++-------------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/hb_config.py b/hb_config.py index b892205..8633f4c 100755 --- a/hb_config.py +++ b/hb_config.py @@ -160,6 +160,7 @@ def build_config(_config_file): 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], + 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), }}) diff --git a/hblink.py b/hblink.py index e022af8..86aaef7 100755 --- a/hblink.py +++ b/hblink.py @@ -194,41 +194,33 @@ class OPENBRIDGE(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - self._localsock = (self._config['IP'], self._config['PORT']) - self._targetsock = (self._config['TARGET_IP'], self._config['TARGET_PORT']) - print(self._config['NETWORK_ID']) def dereg(self): self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) def send_system(self, _packet): if _packet[:4] == 'DMRD': + _packet = _packet[:11] + self._config['NETWORK_ID'] + _packet[15:] self.transport.write(_packet, (self._config['TARGET_IP'], self._config['TARGET_PORT'])) + # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! + # self._logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) + else: + self._logger.error('(%s) OpenBridge system was asked to send non DMRD packet') def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass #print(int_id(_peer_id), int_id(_rf_src), int_id(_dst_id), int_id(_seq), _slot, _call_type, _frame_type, repr(_dtype_vseq), int_id(_stream_id)) - - _dmrd = _data[:53] - _hash = _data[53:] - _ckhs = hmac_new(self._config['PASSPHRASE'],_dmrd,sha1).digest() - if compare_digest(_hash, _ckhs): - print('PEER:', int_id(_peer_id), 'RF SOURCE:', int_id(_rf_src), 'DESTINATION:', int_id(_dst_id), 'SLOT', _slot, 'SEQ:', int_id(_seq), 'STREAM:', int_id(_stream_id)) - else: - self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) - - # Aliased in __init__ to datagramReceived if system is a master - def datagramReceived(self, _data, _sockaddr): + def datagramReceived(self, _packet, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! # self._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 _sockaddr == self._targetsock: + if _packet[:4] == 'DMRD': # DMRData -- encapsulated DMR data frame + _data = _data[:53] + _ckhs = hmac_new(self._config['PASSPHRASE'],_data[53:],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] @@ -242,6 +234,8 @@ class OPENBRIDGE(DatagramProtocol): # 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: + self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) #************************************************ From 9b8edd2e1cd701c7424056963ae6ac5991ba1471 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 8 Oct 2018 22:14:50 -0500 Subject: [PATCH 07/56] update --- .gitignore | 0 LICENSE.txt | 0 README.md | 0 acl.py | 0 hb_bridge_all_rules_SAMPLE.py | 0 hb_confbridge.py | 121 ++++++++++++++++++++++++++++- hb_confbridge_rules-SAMPLE.py | 0 hblink-SAMPLE.cfg | 0 reg_acl-SAMPLE.py | 0 reporting_const.py | 0 requirements.txt | 0 retired/hb_routing_rules-SAMPLE.py | 0 sub_acl-SAMPLE.py | 0 13 files changed, 118 insertions(+), 3 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 LICENSE.txt mode change 100644 => 100755 README.md mode change 100644 => 100755 acl.py mode change 100644 => 100755 hb_bridge_all_rules_SAMPLE.py mode change 100644 => 100755 hb_confbridge_rules-SAMPLE.py mode change 100644 => 100755 hblink-SAMPLE.cfg mode change 100644 => 100755 reg_acl-SAMPLE.py mode change 100644 => 100755 reporting_const.py mode change 100644 => 100755 requirements.txt mode change 100644 => 100755 retired/hb_routing_rules-SAMPLE.py mode change 100644 => 100755 sub_acl-SAMPLE.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE.txt b/LICENSE.txt old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/acl.py b/acl.py old mode 100644 new mode 100755 diff --git a/hb_bridge_all_rules_SAMPLE.py b/hb_bridge_all_rules_SAMPLE.py old mode 100644 new mode 100755 diff --git a/hb_confbridge.py b/hb_confbridge.py index cfa0c32..42e5e85 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -45,7 +45,7 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, build_reg_acl +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, build_reg_acl from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const import hb_config @@ -180,7 +180,119 @@ def rule_timer_loop(): if CONFIG['REPORTS']['REPORT']: report_server.send_clients('bridge updated') -class routerSYSTEM(HBSYSTEM): +class routerOBP(OPENBRIDGE): + + def __init__(self, _name, _config, _logger, _report): + OPENBRIDGE.__init__(self, _name, _config, _logger, _report) + self.STATUS = {} + + + 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 = int_id(_data[15]) + + if _call_type == 'group': + + # Check for ACL match, and return if the subscriber is not allowed + if allow_sub(_rf_src) == False: + self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id)) + return + + if _stream_id not in self.STATUS: + # This is a new call stream + self.STATUS[_stream_id] = pkt_time + self._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) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) + + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_slot]['RX_LC'] = decoded['LC'] + + # If we don't have a voice header then don't wait to decode it from the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_slot]['RX_LC'] = const.LC_OPT + _dst_id + _rf_src + + + for _bridge in BRIDGES: + for _system in BRIDGES[_bridge]: + + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + + for _target in BRIDGES[_bridge]: + if _target['SYSTEM'] != self._system: + if _target['ACTIVE']: + _target_status = systems[_target['SYSTEM']].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] + + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits + + # Assemble transmit HBP packet header + _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = _tmp_data + dmrpkt + _data[53:55] + + # Transmit the packet to the destination system + systems[_target['SYSTEM']].send_system(_tmp_data) + #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + + + # Final actions - Is this a voice terminator? + if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS['RX_START'] + self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %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 CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) + + + # Mark status variables for use later + self.STATUS[_slot]['RX_SEQ'] = _seq + 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 + + +class routerHBP(HBSYSTEM): def __init__(self, _name, _config, _logger, _report): HBSYSTEM.__init__(self, _name, _config, _logger, _report) @@ -529,7 +641,10 @@ if __name__ == '__main__': logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: - systems[system] = routerSYSTEM(system, CONFIG, logger, report_server) + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + systems[system] = routerOBP(system, CONFIG, logger, report_server) + else: + systems[system] = routerHBP(system, CONFIG, logger, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) diff --git a/hb_confbridge_rules-SAMPLE.py b/hb_confbridge_rules-SAMPLE.py old mode 100644 new mode 100755 diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg old mode 100644 new mode 100755 diff --git a/reg_acl-SAMPLE.py b/reg_acl-SAMPLE.py old mode 100644 new mode 100755 diff --git a/reporting_const.py b/reporting_const.py old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/retired/hb_routing_rules-SAMPLE.py b/retired/hb_routing_rules-SAMPLE.py old mode 100644 new mode 100755 diff --git a/sub_acl-SAMPLE.py b/sub_acl-SAMPLE.py old mode 100644 new mode 100755 From bccf8d93ddc365682a156f2deffc2ef810c1bff5 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 14:20:05 -0500 Subject: [PATCH 08/56] PROGRESS - DOES NOT WORK this commit is to get the test code moved to the testing server. It is not intended for people to try and download to use --- hb_confbridge.py | 162 +++++++++++++++++++++++++++++------------------ hblink.py | 1 + 2 files changed, 100 insertions(+), 63 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 42e5e85..0b9d2f0 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -179,6 +179,13 @@ def rule_timer_loop(): if CONFIG['REPORTS']['REPORT']: report_server.send_clients('bridge updated') + + for system in CONFIG['SYSTEMS']: + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + for stream_id in system['STATUS']: + if system['STATUS'][stream_id]['STREAM_START'] < _now + 1: + removed = system['STATUS'].pop(stream_id) + logger.warning('STALE OPENBRIDGE STREAM ID REMOVED FROM SYSTEM: %s, STREAM ID %s', system, int_id(stream_id)) class routerOBP(OPENBRIDGE): @@ -198,10 +205,14 @@ class routerOBP(OPENBRIDGE): if allow_sub(_rf_src) == False: self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id)) return + + # Is this a new call stream? + if (_stream_id not in self.STATUS): - if _stream_id not in self.STATUS: # This is a new call stream - self.STATUS[_stream_id] = pkt_time + self.STATUS[_stream_id]['STREAM_START'] = pkt_time + self.STATUS[_stream_id]['PKT_COUNT'] = 0 + self.STATUS[_stream_id]['CONTENTION'] = False self._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) if CONFIG['REPORTS']['REPORT']: @@ -210,12 +221,12 @@ class routerOBP(OPENBRIDGE): # If we can, use the LC from the voice header as to keep all options intact if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_slot]['RX_LC'] = decoded['LC'] + self.STATUS[_stream_id]['LC'] = decoded['LC'] # If we don't have a voice header then don't wait to decode it from the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: - self.STATUS[_slot]['RX_LC'] = const.LC_OPT + _dst_id + _rf_src + self.STATUS[_stream_id] = const.LC_OPT + _dst_id + _rf_src for _bridge in BRIDGES: @@ -224,73 +235,98 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - if _target['SYSTEM'] != self._system: - if _target['ACTIVE']: - _target_status = systems[_target['SYSTEM']].STATUS - _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - # Handle any necessary re-writes for the destination - if _system['TS'] != _target['TS']: - _tmp_bits = _bits ^ 1 << 7 - else: - _tmp_bits = _bits - - # Assemble transmit HBP packet header - _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: - dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: - dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = _tmp_data + dmrpkt + _data[53:55] - - # Transmit the packet to the destination system - systems[_target['SYSTEM']].send_system(_tmp_data) - #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (_target['MODE'] != 'OPENBRIDGE'): + _target_status = systems[_target['SYSTEM']].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] + + # BEGIN 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 ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + continue + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + continue + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + continue + if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + + # Set values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time + + if (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_stream_id]['LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits + + # Assemble transmit HBP packet header + _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = _tmp_data + dmrpkt + _data[53:55] + + # Transmit the packet to the destination system + systems[_target['SYSTEM']].send_system(_tmp_data) + #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Final actions - Is this a voice terminator? - if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS['RX_START'] + if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS['STREAM_START'] self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %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 CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) - - - # Mark status variables for use later - self.STATUS[_slot]['RX_SEQ'] = _seq - 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 - + removed = self.STATUS.pop(_stream_id) + if not removed: + self_logger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) class routerHBP(HBSYSTEM): diff --git a/hblink.py b/hblink.py index 86aaef7..e397c61 100755 --- a/hblink.py +++ b/hblink.py @@ -201,6 +201,7 @@ class OPENBRIDGE(DatagramProtocol): def send_system(self, _packet): if _packet[:4] == 'DMRD': _packet = _packet[:11] + self._config['NETWORK_ID'] + _packet[15:] + _packet += hmac_new(self._config['PASSPHRASE'],_packet,sha1).digest() self.transport.write(_packet, (self._config['TARGET_IP'], self._config['TARGET_PORT'])) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # self._logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) From cdbfcbb59c156f5071826fb851b2f440ce87c9b5 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 15:03:36 -0500 Subject: [PATCH 09/56] DO NOT USE THIS --- hblink.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hblink.py b/hblink.py index e397c61..20a4adc 100755 --- a/hblink.py +++ b/hblink.py @@ -217,7 +217,8 @@ class OPENBRIDGE(DatagramProtocol): # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) if _packet[:4] == 'DMRD': # DMRData -- encapsulated DMR data frame - _data = _data[:53] + _data = _packet[:53] + _hash = _packet[53:] _ckhs = hmac_new(self._config['PASSPHRASE'],_data[53:],sha1).digest() if compare_digest(_hash, _ckhs) and _sockaddr == self._config['TARGET_SOCK']: From f80331cc0facd5501559b616b12de1504eb94e0e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 15:52:03 -0500 Subject: [PATCH 10/56] Fixed hmac calculation typo --- hblink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink.py b/hblink.py index 20a4adc..1b203f1 100755 --- a/hblink.py +++ b/hblink.py @@ -219,7 +219,7 @@ class OPENBRIDGE(DatagramProtocol): if _packet[:4] == 'DMRD': # DMRData -- encapsulated DMR data frame _data = _packet[:53] _hash = _packet[53:] - _ckhs = hmac_new(self._config['PASSPHRASE'],_data[53:],sha1).digest() + _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] From b3273fcc7fd87350c37e302fa545bc9f1121e8a8 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:19:02 -0500 Subject: [PATCH 11/56] Update hb_confbridge.py --- hb_confbridge.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hb_confbridge.py b/hb_confbridge.py index 0b9d2f0..223304b 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -210,6 +210,7 @@ class routerOBP(OPENBRIDGE): if (_stream_id not in self.STATUS): # This is a new call stream + self.STATUS[_stream_id] = {} self.STATUS[_stream_id]['STREAM_START'] = pkt_time self.STATUS[_stream_id]['PKT_COUNT'] = 0 self.STATUS[_stream_id]['CONTENTION'] = False From 37b6f2786456b6a101193cf12e34744fd8c43ffb Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:21:31 -0500 Subject: [PATCH 12/56] Update hb_confbridge.py --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 223304b..40127c0 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -236,7 +236,7 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (_target['MODE'] != 'OPENBRIDGE'): + if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] From 4d9b2f0af4b804181748499917fb2dc9e64ec24d Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:23:16 -0500 Subject: [PATCH 13/56] Update hb_confbridge.py --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 40127c0..6c094c8 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -236,7 +236,7 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): + if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] From 3c7db08dfd486bd55d3fdf6fcb64614c78ab317f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:25:36 -0500 Subject: [PATCH 14/56] Update hb_confbridge.py --- hb_confbridge.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hb_confbridge.py b/hb_confbridge.py index 6c094c8..2bfa8bd 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -236,6 +236,10 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: + print('_target['SYSTEM']', _target['SYSTEM']) + print('self._system', self._system) + print('CONFIG['SYSTEMS'][_target]['ACTIVE']', CONFIG['SYSTEMS'][_target]['ACTIVE']) + print('CONFIG['SYSTEMS'][_target]['MODE']', CONFIG['SYSTEMS'][_target]['MODE']) if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] From 28007dcc87d49a4015551fe90fd697d5c6aa297b Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:26:42 -0500 Subject: [PATCH 15/56] Update hb_confbridge.py --- hb_confbridge.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 2bfa8bd..a67f075 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -236,10 +236,10 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - print('_target['SYSTEM']', _target['SYSTEM']) + print('_target[\'SYSTEM\']', _target['SYSTEM']) print('self._system', self._system) - print('CONFIG['SYSTEMS'][_target]['ACTIVE']', CONFIG['SYSTEMS'][_target]['ACTIVE']) - print('CONFIG['SYSTEMS'][_target]['MODE']', CONFIG['SYSTEMS'][_target]['MODE']) + print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG['SYSTEMS'][_target]['ACTIVE']) + print('CONFIG[\'SYSTEMS\'][_target][\'MODE\']', CONFIG['SYSTEMS'][_target]['MODE']) if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] From 491afb806cba5180d94895b134b3b0be8ac1d31f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:30:18 -0500 Subject: [PATCH 16/56] Update hb_confbridge.py --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index a67f075..2cca29d 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -238,7 +238,7 @@ class routerOBP(OPENBRIDGE): for _target in BRIDGES[_bridge]: print('_target[\'SYSTEM\']', _target['SYSTEM']) print('self._system', self._system) - print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG['SYSTEMS'][_target]['ACTIVE']) + print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG) #['SYSTEMS'][_target]['ACTIVE']) print('CONFIG[\'SYSTEMS\'][_target][\'MODE\']', CONFIG['SYSTEMS'][_target]['MODE']) if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS From 98298cfca199d4e00c29c58c55ba3d4013af74a7 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:33:21 -0500 Subject: [PATCH 17/56] Update hb_confbridge.py --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 2cca29d..5c9c9c5 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -238,7 +238,7 @@ class routerOBP(OPENBRIDGE): for _target in BRIDGES[_bridge]: print('_target[\'SYSTEM\']', _target['SYSTEM']) print('self._system', self._system) - print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG) #['SYSTEMS'][_target]['ACTIVE']) + print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG['SYSTEMS']) #['SYSTEMS'][_target]['ACTIVE']) print('CONFIG[\'SYSTEMS\'][_target][\'MODE\']', CONFIG['SYSTEMS'][_target]['MODE']) if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS From 5fd5ecb2efe3c936cf84e9619597adc37b6b2246 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:35:25 -0500 Subject: [PATCH 18/56] Update hb_confbridge.py --- hb_confbridge.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hb_confbridge.py b/hb_confbridge.py index 5c9c9c5..936f4fa 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -238,6 +238,7 @@ class routerOBP(OPENBRIDGE): for _target in BRIDGES[_bridge]: print('_target[\'SYSTEM\']', _target['SYSTEM']) print('self._system', self._system) + print('target', _target) print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG['SYSTEMS']) #['SYSTEMS'][_target]['ACTIVE']) print('CONFIG[\'SYSTEMS\'][_target][\'MODE\']', CONFIG['SYSTEMS'][_target]['MODE']) if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): From 85139a17e63a4789813e68840f47ce45955a359b Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:37:01 -0500 Subject: [PATCH 19/56] Update hb_confbridge.py --- hb_confbridge.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 936f4fa..693cc80 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -236,12 +236,7 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - print('_target[\'SYSTEM\']', _target['SYSTEM']) - print('self._system', self._system) - print('target', _target) - print('CONFIG[\'SYSTEMS\'][_target][\'ACTIVE\']', CONFIG['SYSTEMS']) #['SYSTEMS'][_target]['ACTIVE']) - print('CONFIG[\'SYSTEMS\'][_target][\'MODE\']', CONFIG['SYSTEMS'][_target]['MODE']) - if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target]['ACTIVE']) and (CONFIG['SYSTEMS'][_target]['MODE'] != 'OPENBRIDGE'): + if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] From 5fbf8f0a112a2d9d2d4af32fd6efe7d3e73ef177 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 12 Oct 2018 16:38:18 -0500 Subject: [PATCH 20/56] Update hb_confbridge.py --- hb_confbridge.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 693cc80..7210a73 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -236,7 +236,9 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): + print('got here') + if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): + print('then here') _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] From ed12a8effa8c2f9ab24a384654113b4d2e127cf4 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 27 Oct 2018 09:45:05 -0500 Subject: [PATCH 21/56] Dangerous, but it works Needs code optomization and cleanup, and as of now, has no method to purge abandoned stream_ids, and thus will have the effect of a memory leak for calls wothout voice terminators --- hb_confbridge.py | 246 ++++++++++++++++++++++++++++++----------------- hb_config.py | 2 +- 2 files changed, 160 insertions(+), 88 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 7210a73..480072a 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -179,14 +179,26 @@ def rule_timer_loop(): if CONFIG['REPORTS']['REPORT']: report_server.send_clients('bridge updated') - - for system in CONFIG['SYSTEMS']: - if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - for stream_id in system['STATUS']: - if system['STATUS'][stream_id]['STREAM_START'] < _now + 1: - removed = system['STATUS'].pop(stream_id) - logger.warning('STALE OPENBRIDGE STREAM ID REMOVED FROM SYSTEM: %s, STREAM ID %s', system, int_id(stream_id)) + +# run this every 10 seconds to trim orphaned stream ids +def stream_trimmer_loop(): + return + logger.info('(ALL OPENBRIDGE SYSTEMS) Trimming orphaned stream IDs from system lists') + _now = time() + + for system in systems: + remove_list = [] + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + for stream_id in systems[system].STATUS: + if systems[system].STATUS[stream_id]['LAST'] < _now + 1: + remove_list.append(stream_id) + + for stream in remove_list: + removed = systems[system].STATUS.pop(stream_id) + logger.warning('STALE OPENBRIDGE STREAM ID REMOVED FROM SYSTEM: %s, STREAM ID %s', system, int_id(stream)) + + class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _logger, _report): @@ -207,27 +219,33 @@ class routerOBP(OPENBRIDGE): return # Is this a new call stream? - if (_stream_id not in self.STATUS): - + if (_stream_id not in self.STATUS): # This is a new call stream - self.STATUS[_stream_id] = {} - self.STATUS[_stream_id]['STREAM_START'] = pkt_time - self.STATUS[_stream_id]['PKT_COUNT'] = 0 - self.STATUS[_stream_id]['CONTENTION'] = False - self._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) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) - + self.STATUS[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + # If we can, use the LC from the voice header as to keep all options intact if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) self.STATUS[_stream_id]['LC'] = decoded['LC'] - # If we don't have a voice header then don't wait to decode it from the Embedded LC + # If we don't have a voice header then don't wait to decode the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: - self.STATUS[_stream_id] = const.LC_OPT + _dst_id + _rf_src + self.STATUS[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src + + + self._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) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) + + + self.STATUS[_stream_id]['LAST'] = pkt_time for _bridge in BRIDGES: @@ -236,9 +254,7 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - print('got here') - if (_target['SYSTEM'] != self._system) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): - print('then here') + if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] @@ -322,7 +338,7 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS['STREAM_START'] + call_duration = pkt_time - self.STATUS[_stream_id]['START'] self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %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 CONFIG['REPORTS']['REPORT']: @@ -421,7 +437,6 @@ class routerHBP(HBSYSTEM): else: self.STATUS[_slot]['RX_LC'] = const.LC_OPT + _dst_id + _rf_src - for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: @@ -433,73 +448,126 @@ class routerHBP(HBSYSTEM): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - # BEGIN 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 ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) - continue - if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) - continue - if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) - continue - if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - - # Set values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time + if _target_system['MODE'] == 'OPENBRIDGE': + # Is this a new call stream on the target? + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + _target_status[_stream_id]['LC'] = decoded['LC'] + self._logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + + # If we don't have a voice header then don't wait to decode the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + _target_status[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src + self._logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) + _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) + _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet header + _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: + dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = _tmp_data + dmrpkt #+ _data[53:55] + + else: + # 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 ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: + self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + continue + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: + self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + continue + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: + self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + continue + if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: + self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + + # Set values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time + + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - # Handle any necessary re-writes for the destination - if _system['TS'] != _target['TS']: - _tmp_bits = _bits ^ 1 << 7 - else: - _tmp_bits = _bits + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits - # Assemble transmit HBP packet header - _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] + # Assemble transmit HBP packet header + _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: - dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: - dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = _tmp_data + dmrpkt + _data[53:55] + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = _tmp_data + dmrpkt + _data[53:55] # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) @@ -690,5 +758,9 @@ if __name__ == '__main__': # Initialize the rule timer -- this if for user activated stuff rule_timer = task.LoopingCall(rule_timer_loop) rule_timer.start(60) + + # Initialize the stream trimmer + stream_trimmer = task.LoopingCall(stream_trimmer_loop) + stream_trimmer.start(10) reactor.run() \ No newline at end of file diff --git a/hb_config.py b/hb_config.py index 8633f4c..5600c99 100755 --- a/hb_config.py +++ b/hb_config.py @@ -156,7 +156,7 @@ def build_config(_config_file): CONFIG['SYSTEMS'].update({section: { 'MODE': config.get(section, 'MODE'), 'ENABLED': config.getboolean(section, 'ENABLED'), - 'NETWORK_ID': config.getint(section, 'NETWORK_ID'), + 'NETWORK_ID': hex(int(config.get(section, 'NETWORK_ID')))[2:].rjust(8,'0').decode('hex'), 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], From e78912d55776c27b1240c1256cf1334ccef883e5 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 27 Oct 2018 11:52:29 -0500 Subject: [PATCH 22/56] work with stale stream removal - not working 100% --- hb_confbridge.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 480072a..7ad278b 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -183,15 +183,14 @@ def rule_timer_loop(): # run this every 10 seconds to trim orphaned stream ids def stream_trimmer_loop(): - return - logger.info('(ALL OPENBRIDGE SYSTEMS) Trimming orphaned stream IDs from system lists') + logger.debug('(ALL OPENBRIDGE SYSTEMS) Trimming orphaned stream IDs from system lists') _now = time() for system in systems: remove_list = [] if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': for stream_id in systems[system].STATUS: - if systems[system].STATUS[stream_id]['LAST'] < _now + 1: + if systems[system].STATUS[stream_id]['LAST'] < _now + 5: remove_list.append(stream_id) for stream in remove_list: @@ -761,6 +760,6 @@ if __name__ == '__main__': # Initialize the stream trimmer stream_trimmer = task.LoopingCall(stream_trimmer_loop) - stream_trimmer.start(10) + stream_trimmer.start(5) reactor.run() \ No newline at end of file From 61164fa2908fb0a9990848fcb21be8a215226888 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 27 Oct 2018 12:29:29 -0500 Subject: [PATCH 23/56] inactive stream id removal working Please test this more... I think it's working. --- hb_confbridge.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 7ad278b..e7ed3e1 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -183,19 +183,20 @@ def rule_timer_loop(): # run this every 10 seconds to trim orphaned stream ids def stream_trimmer_loop(): - logger.debug('(ALL OPENBRIDGE SYSTEMS) Trimming orphaned stream IDs from system lists') + logger.debug('(ALL OPENBRIDGE SYSTEMS) Trimming inactive stream IDs from system lists') _now = time() for system in systems: remove_list = [] if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': for stream_id in systems[system].STATUS: - if systems[system].STATUS[stream_id]['LAST'] < _now + 5: + if systems[system].STATUS[stream_id]['LAST'] < _now - 5: + remove_list.append(stream_id) - for stream in remove_list: + for stream_id in remove_list: removed = systems[system].STATUS.pop(stream_id) - logger.warning('STALE OPENBRIDGE STREAM ID REMOVED FROM SYSTEM: %s, STREAM ID %s', system, int_id(stream)) + logger.debug('Inactive OpenBridge Stream ID removed from System: %s, Stream ID %s', system, int_id(stream_id)) class routerOBP(OPENBRIDGE): @@ -343,6 +344,7 @@ class routerOBP(OPENBRIDGE): if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) removed = self.STATUS.pop(_stream_id) + self._logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) if not removed: self_logger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) From 8ce98d148e833705a8824d65ce986185dd8937d9 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 8 Nov 2018 08:26:25 -0600 Subject: [PATCH 24/56] Timers intialized set when ACTIVE = False Fixed initial timer problem --- hb_confbridge.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index e7ed3e1..3691b99 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -90,7 +90,10 @@ def make_bridges(_hb_confbridge_bridges): for i, e in enumerate(_system['OFF']): _system['OFF'][i] = hex_str_3(_system['OFF'][i]) _system['TIMEOUT'] = _system['TIMEOUT']*60 - _system['TIMER'] = time() + _system['TIMEOUT'] + if _system['ACTIVE'] == True: + _system['TIMER'] = time() + _system['TIMEOUT'] + else: + _system['TIMER'] = time() return bridge_file.BRIDGES From 84ac4c24c6c58f5b8bdcf1879cc383e3544d9ec6 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Thu, 8 Nov 2018 08:28:47 -0600 Subject: [PATCH 25/56] Fixed minor reporting bug --- hb_confbridge.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index e7ed3e1..fd05fcb 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -178,8 +178,8 @@ def rule_timer_loop(): logger.debug('Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) if CONFIG['REPORTS']['REPORT']: - report_server.send_clients('bridge updated') - + #report_server.send_clients('bridge updated') + report_server.send_bridge() # run this every 10 seconds to trim orphaned stream ids def stream_trimmer_loop(): @@ -764,4 +764,4 @@ if __name__ == '__main__': stream_trimmer = task.LoopingCall(stream_trimmer_loop) stream_trimmer.start(5) - reactor.run() \ No newline at end of file + reactor.run() From 57bfd9b203fee41b6e96c50d8e164f304bcb058f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 8 Nov 2018 08:39:31 -0600 Subject: [PATCH 26/56] Fix bridge table reporting interval --- hb_confbridge.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 7ca1648..490f2ea 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -45,7 +45,7 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, build_reg_acl +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, build_reg_acl from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const import hb_config @@ -66,6 +66,28 @@ __status__ = 'pre-alpha' # Module gobal varaibles +# Timed loop used for reporting HBP status +# +# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE +def config_reports(_config, _logger, _factory): + if True: #_config['REPORTS']['REPORT']: + def reporting_loop(_logger, _server): + _logger.debug('Periodic reporting loop started') + _server.send_config() + _server.send_bridge() + + _logger.info('HBlink TCP reporting server configured') + + report_server = _factory(_config, _logger) + report_server.clients = [] + reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server) + + reporting = task.LoopingCall(reporting_loop, _logger, report_server) + reporting.start(_config['REPORTS']['REPORT_INTERVAL']) + + return report_server + + # Import Bridging rules # Note: A stanza *must* exist for any MASTER or CLIENT configured in the main # configuration file and listed as "active". It can be empty, @@ -181,8 +203,8 @@ def rule_timer_loop(): logger.debug('Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) if CONFIG['REPORTS']['REPORT']: - #report_server.send_clients('bridge updated') - report_server.send_bridge() + report_server.send_clients('bridge updated') + # run this every 10 seconds to trim orphaned stream ids def stream_trimmer_loop(): @@ -767,4 +789,4 @@ if __name__ == '__main__': stream_trimmer = task.LoopingCall(stream_trimmer_loop) stream_trimmer.start(5) - reactor.run() + reactor.run() \ No newline at end of file From 316b566b9d025692bc87fd22be488fe97115dd4e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 8 Nov 2018 09:02:24 -0600 Subject: [PATCH 27/56] an OB to OB Support --- hb_confbridge.py | 188 ++++++++++++++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 68 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 490f2ea..ff90b8b 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -282,78 +282,130 @@ class routerOBP(OPENBRIDGE): if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - - # BEGIN 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 ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) - continue - if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) - continue - if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) - continue - if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - - # Set values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time + + if _target_system['MODE'] == 'OPENBRIDGE': + # Is this a new call stream on the target? + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + _target_status[_stream_id]['LC'] = decoded['LC'] + self._logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + + # If we don't have a voice header then don't wait to decode the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + _target_status[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src + self._logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) + _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) + _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet header + _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - if (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_stream_id]['LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - # Handle any necessary re-writes for the destination - if _system['TS'] != _target['TS']: - _tmp_bits = _bits ^ 1 << 7 + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: + dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = _tmp_data + dmrpkt #+ _data[53:55] + else: - _tmp_bits = _bits + # BEGIN 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 ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + continue + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + continue + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + continue + if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + + # Set values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time - # Assemble transmit HBP packet header - _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] + if (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_stream_id]['LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: - dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: - dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = _tmp_data + dmrpkt + _data[53:55] + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits + + # Assemble transmit HBP packet header + _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = _tmp_data + dmrpkt + _data[53:55] # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) From db18b418b6aaf40b6e737ca4661eaec7e5726f03 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Thu, 8 Nov 2018 09:39:50 -0600 Subject: [PATCH 28/56] OB to OB Support Added --- hb_confbridge.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index ff90b8b..fe21710 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -279,10 +279,9 @@ class routerOBP(OPENBRIDGE): if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): for _target in BRIDGES[_bridge]: - if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']) and (CONFIG['SYSTEMS'][_target['SYSTEM']]['MODE'] != 'OPENBRIDGE'): + if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - if _target_system['MODE'] == 'OPENBRIDGE': # Is this a new call stream on the target? if (_stream_id not in _target_status): @@ -841,4 +840,4 @@ if __name__ == '__main__': stream_trimmer = task.LoopingCall(stream_trimmer_loop) stream_trimmer.start(5) - reactor.run() \ No newline at end of file + reactor.run() From 3cfee4d009c664010091287df80ab4550afdb76a Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 8 Nov 2018 09:41:08 -0600 Subject: [PATCH 29/56] OB to OB Support WORKING --- hblink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink.py b/hblink.py index 1b203f1..c185e6f 100755 --- a/hblink.py +++ b/hblink.py @@ -214,7 +214,7 @@ class OPENBRIDGE(DatagramProtocol): def datagramReceived(self, _packet, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! - # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) + #self._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] From da42770ef5d8a6e8051c7670229b682de42a0e72 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 8 Nov 2018 13:38:12 -0600 Subject: [PATCH 30/56] indent logging line to reduce frequency --- hb_confbridge.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index fe21710..a4320e1 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -617,8 +617,7 @@ class routerHBP(HBSYSTEM): _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: From fe8b2e35959c9bea068134336e0c5fc5ead8f09c Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Sat, 10 Nov 2018 15:36:19 -0600 Subject: [PATCH 31/56] Incremental Updates - nothing big --- hb_confbridge.py | 14 ++++++++------ hblink.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index a4320e1..19b6b46 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -307,7 +307,8 @@ class routerOBP(OPENBRIDGE): _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) - + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 @@ -378,7 +379,7 @@ class routerOBP(OPENBRIDGE): _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: @@ -521,7 +522,7 @@ class routerHBP(HBSYSTEM): for _target in BRIDGES[_bridge]: if _target['SYSTEM'] != self._system: - if _target['ACTIVE']: + if _target['ACTIVE']: _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] @@ -550,7 +551,8 @@ class routerHBP(HBSYSTEM): _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) - + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 @@ -575,7 +577,7 @@ class routerHBP(HBSYSTEM): dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] dmrpkt = dmrbits.tobytes() _tmp_data = _tmp_data + dmrpkt #+ _data[53:55] - + else: # BEGIN STANDARD CONTENTION HANDLING # @@ -617,7 +619,7 @@ class routerHBP(HBSYSTEM): _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: diff --git a/hblink.py b/hblink.py index c185e6f..6c45eae 100755 --- a/hblink.py +++ b/hblink.py @@ -237,7 +237,7 @@ class OPENBRIDGE(DatagramProtocol): # 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: - self._logger.info('(%s) OpenBridge HMAC failed, packet discarded', self._system) + self._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:])) #************************************************ From 8acaded9dcee6b3071e3685d3aa042afe56d586d Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Sun, 11 Nov 2018 15:17:35 -0600 Subject: [PATCH 32/56] Added logging, and fixed instance STATUS dictionary for HBP systems - Start time was incorrectly recorded. --- hb_confbridge.py | 231 +++++++++++++++++++++++++---------------------- 1 file changed, 123 insertions(+), 108 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 19b6b46..1fffb59 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -57,40 +57,39 @@ import cPickle as pickle # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS' -__copyright__ = 'Copyright (c) 2016-2018PEER Cortney T. Buffington, N0MJS and the K0USY Group' +__copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' __credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' __license__ = 'GNU GPLv3' __maintainer__ = 'Cort Buffington, N0MJS' __email__ = 'n0mjs@me.com' -__status__ = 'pre-alpha' # Module gobal varaibles # Timed loop used for reporting HBP status # # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE -def config_reports(_config, _logger, _factory): +def config_reports(_config, _logger, _factory): if True: #_config['REPORTS']['REPORT']: def reporting_loop(_logger, _server): _logger.debug('Periodic reporting loop started') _server.send_config() _server.send_bridge() - + _logger.info('HBlink TCP reporting server configured') - + report_server = _factory(_config, _logger) report_server.clients = [] reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server) - + reporting = task.LoopingCall(reporting_loop, _logger, report_server) reporting.start(_config['REPORTS']['REPORT_INTERVAL']) - + return report_server # Import Bridging rules # Note: A stanza *must* exist for any MASTER or CLIENT configured in the main -# configuration file and listed as "active". It can be empty, +# configuration file and listed as "active". It can be empty, # but it has to exist. def make_bridges(_hb_confbridge_bridges): try: @@ -98,14 +97,14 @@ def make_bridges(_hb_confbridge_bridges): logger.info('Routing bridges file found and bridges imported') except ImportError: sys.exit('Routing bridges file not found or invalid') - + # Convert integer GROUP ID numbers from the config into hex strings # we need to send in the actual data packets. for _bridge in bridge_file.BRIDGES: for _system in bridge_file.BRIDGES[_bridge]: if _system['SYSTEM'] not in CONFIG['SYSTEMS']: sys.exit('ERROR: Conference bridges found for system not configured main configuration') - + _system['TGID'] = hex_str_3(_system['TGID']) for i, e in enumerate(_system['ON']): _system['ON'][i] = hex_str_3(_system['ON'][i]) @@ -133,7 +132,7 @@ def build_acl(_sub_acl): ACL_ACTION = sections[0] entries_str = sections[1] - + for entry in entries_str.split(','): if '-' in entry: start,end = entry.split('-') @@ -143,9 +142,9 @@ def build_acl(_sub_acl): else: id = int(entry) ACL.add(hex_str_3(id)) - + logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL))) - + except ImportError: logger.info('ACL file not found or invalid - all subscriber IDs are valid') ACL_ACTION = 'NONE' @@ -168,13 +167,13 @@ def build_acl(_sub_acl): else: def allow_sub(_sub): return True - + return ACL # Run this every minute for rule timer updates def rule_timer_loop(): - logger.info('(ALL HBSYSTEMS) Rule timer loop started') + logger.debug('(ALL HBSYSTEMS) Rule timer loop started') _now = time() for _bridge in BRIDGES: @@ -210,26 +209,36 @@ def rule_timer_loop(): def stream_trimmer_loop(): logger.debug('(ALL OPENBRIDGE SYSTEMS) Trimming inactive stream IDs from system lists') _now = time() - + for system in systems: remove_list = [] if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': for stream_id in systems[system].STATUS: if systems[system].STATUS[stream_id]['LAST'] < _now - 5: - remove_list.append(stream_id) - for stream_id in remove_list: + _system = systems[system].STATUS[stream_id] + _config = CONFIG['SYSTEMS'][system] + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ + system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) + # self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) removed = systems[system].STATUS.pop(stream_id) logger.debug('Inactive OpenBridge Stream ID removed from System: %s, Stream ID %s', system, int_id(stream_id)) + for system in systems: + if CONFIG['SYSTEMS'][system]['MODE'] != 'OPENBRIDGE': + for slot in range(1,3): + _slot = systems[system].STATUS[slot] + if _slot['RX_TYPE'] != hb_const.HBPF_SLT_VTERM and _slot['RX_TIME'] < _now - 5: + _slot['RX_TYPE'] = hb_const.HBPF_SLT_VTERM + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ + system, int_id(_slot['RX_STREAM_ID']), get_alias(_slot['RX_RFS'], subscriber_ids), int_id(_slot['RX_RFS']), get_alias(_slot['RX_TGID'], talkgroup_ids), int_id(_slot['RX_TGID']), slot, _slot['RX_TIME'] - _slot['RX_START']) - class routerOBP(OPENBRIDGE): - + def __init__(self, _name, _config, _logger, _report): OPENBRIDGE.__init__(self, _name, _config, _logger, _report) self.STATUS = {} - + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() @@ -237,14 +246,14 @@ class routerOBP(OPENBRIDGE): _bits = int_id(_data[15]) if _call_type == 'group': - + # Check for ACL match, and return if the subscriber is not allowed if allow_sub(_rf_src) == False: self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id)) return - - # Is this a new call stream? - if (_stream_id not in self.STATUS): + + # Is this a new call stream? + if (_stream_id not in self.STATUS): # This is a new call stream self.STATUS[_stream_id] = { 'START': pkt_time, @@ -257,33 +266,33 @@ class routerOBP(OPENBRIDGE): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) self.STATUS[_stream_id]['LC'] = decoded['LC'] - + # If we don't have a voice header then don't wait to decode the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: self.STATUS[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src - - + + self._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) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) - - + + self.STATUS[_stream_id]['LAST'] = pkt_time for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: - + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): - + for _target in BRIDGES[_bridge]: if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']): _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] if _target_system['MODE'] == 'OPENBRIDGE': - # Is this a new call stream on the target? + # Is this a new call stream on the target? if (_stream_id not in _target_status): # This is a new call stream on the target _target_status[_stream_id] = { @@ -297,26 +306,26 @@ class routerOBP(OPENBRIDGE): decoded = decode.voice_head_term(dmrpkt) _target_status[_stream_id]['LC'] = decoded['LC'] self._logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) - + # If we don't have a voice header then don't wait to decode the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: _target_status[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src self._logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) - + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) self._logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - # Record the time of this packet so we can later identify a stale stream + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 _tmp_bits = _bits & ~(1 << 7) - + # Assemble transmit HBP packet header _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET # MUST RE-WRITE DESTINATION TGID IF DIFFERENT # if _dst_id != rule['DST_GROUP']: @@ -333,7 +342,7 @@ class routerOBP(OPENBRIDGE): dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] dmrpkt = dmrbits.tobytes() _tmp_data = _tmp_data + dmrpkt #+ _data[53:55] - + else: # BEGIN CONTENTION HANDLING # @@ -364,11 +373,11 @@ class routerOBP(OPENBRIDGE): self.STATUS[_stream_id]['CONTENTION'] = True self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue - + # Set values for the contention handler to test next time there is a frame to forward _target_status[_target['TS']]['TX_TIME'] = pkt_time - - if (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + + if (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): # Record the DST TGID and Stream ID _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id @@ -380,16 +389,16 @@ class routerOBP(OPENBRIDGE): _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) self._logger.info('(%s) Conference Bridge: %s, Call Bridged HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - + # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: _tmp_bits = _bits ^ 1 << 7 else: _tmp_bits = _bits - + # Assemble transmit HBP packet header _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET # MUST RE-WRITE DESTINATION TGID IF DIFFERENT # if _dst_id != rule['DST_GROUP']: @@ -406,13 +415,13 @@ class routerOBP(OPENBRIDGE): dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] dmrpkt = dmrbits.tobytes() _tmp_data = _tmp_data + dmrpkt + _data[53:55] - + # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - - + + + # Final actions - Is this a voice terminator? if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_stream_id]['START'] @@ -426,16 +435,19 @@ class routerOBP(OPENBRIDGE): self_logger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) class routerHBP(HBSYSTEM): - + def __init__(self, _name, _config, _logger, _report): HBSYSTEM.__init__(self, _name, _config, _logger, _report) - + # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" # In TX_EMB_LC, 2-5 are burst B-E self.STATUS = { 1: { + 'RX_ACTIVE': False, + 'TX_ACTIVE': False, 'RX_START': time(), + 'TX_START': time(), 'RX_SEQ': '\x00', 'RX_RFS': '\x00', 'TX_RFS': '\x00', @@ -457,7 +469,10 @@ class routerHBP(HBSYSTEM): } }, 2: { + 'RX_ACTIVE': False, + 'TX_ACTIVE': False, 'RX_START': time(), + 'TX_START': time(), 'RX_SEQ': '\x00', 'RX_RFS': '\x00', 'TX_RFS': '\x00', @@ -486,30 +501,30 @@ class routerHBP(HBSYSTEM): _bits = int_id(_data[15]) if _call_type == 'group': - + # Check for ACL match, and return if the subscriber is not allowed if allow_sub(_rf_src) == False: self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id)) return - - # Is this a new call stream? + + # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): self._logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) return - + # This is a new call stream - self.STATUS['RX_START'] = pkt_time + self.STATUS[_slot]['RX_START'] = pkt_time self._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) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) - + # If we can, use the LC from the voice header as to keep all options intact if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) self.STATUS[_slot]['RX_LC'] = decoded['LC'] - + # If we don't have a voice header then don't wait to decode it from the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: @@ -517,17 +532,17 @@ class routerHBP(HBSYSTEM): for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: - + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): - + for _target in BRIDGES[_bridge]: if _target['SYSTEM'] != self._system: if _target['ACTIVE']: _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - + if _target_system['MODE'] == 'OPENBRIDGE': - # Is this a new call stream on the target? + # Is this a new call stream on the target? if (_stream_id not in _target_status): # This is a new call stream on the target _target_status[_stream_id] = { @@ -541,26 +556,26 @@ class routerHBP(HBSYSTEM): decoded = decode.voice_head_term(dmrpkt) _target_status[_stream_id]['LC'] = decoded['LC'] self._logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) - + # If we don't have a voice header then don't wait to decode the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: _target_status[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src self._logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) - + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) self._logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - # Record the time of this packet so we can later identify a stale stream + + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 _tmp_bits = _bits & ~(1 << 7) - + # Assemble transmit HBP packet header _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET # MUST RE-WRITE DESTINATION TGID IF DIFFERENT # if _dst_id != rule['DST_GROUP']: @@ -604,11 +619,11 @@ class routerHBP(HBSYSTEM): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue - + # Set values for the contention handler to test next time there is a frame to forward _target_status[_target['TS']]['TX_TIME'] = pkt_time - - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): # Record the DST TGID and Stream ID _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id @@ -620,16 +635,16 @@ class routerHBP(HBSYSTEM): _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - + # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: _tmp_bits = _bits ^ 1 << 7 else: _tmp_bits = _bits - + # Assemble transmit HBP packet header _tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20] - + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET # MUST RE-WRITE DESTINATION TGID IF DIFFERENT # if _dst_id != rule['DST_GROUP']: @@ -646,36 +661,36 @@ class routerHBP(HBSYSTEM): dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] dmrpkt = dmrbits.tobytes() _tmp_data = _tmp_data + dmrpkt + _data[53:55] - + # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - - + + + # Final actions - Is this a voice terminator? if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS['RX_START'] + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %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 CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) - + # # Begin in-band signalling for call end. This has nothign to do with routing traffic directly. # - + # Iterate the rules dictionary - + for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: if _system['SYSTEM'] == self._system: - + # TGID matches a rule source, reset its timer if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): _system['TIMER'] = pkt_time + _system['TIMEOUT'] self._logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) - + # TGID matches an ACTIVATION trigger if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: # Set the matching rule as ACTIVE @@ -692,7 +707,7 @@ class routerHBP(HBSYSTEM): if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON': _system['TIMER'] = pkt_time + _system['TIMEOUT'] self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) - + # TGID matches an DE-ACTIVATION trigger if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: # Set the matching rule as ACTIVE @@ -713,11 +728,11 @@ class routerHBP(HBSYSTEM): _system['TIMER'] = pkt_time self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) - # + # # END IN-BAND SIGNALLING # - - + + # Mark status variables for use later self.STATUS[_slot]['RX_SEQ'] = _seq self.STATUS[_slot]['RX_RFS'] = _rf_src @@ -725,16 +740,16 @@ class routerHBP(HBSYSTEM): self.STATUS[_slot]['RX_TGID'] = _dst_id self.STATUS[_slot]['RX_TIME'] = pkt_time self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - + # # Socket-based reporting section # class confbridgeReportFactory(reportFactory): - + def send_bridge(self): serialized = pickle.dumps(BRIDGES, protocol=pickle.HIGHEST_PROTOCOL) self.send_clients(REPORT_OPCODES['BRIDGE_SND']+serialized) - + def send_bridgeEvent(self, _data): self.send_clients(REPORT_OPCODES['BRDG_EVENT']+_data) @@ -744,13 +759,13 @@ class confbridgeReportFactory(reportFactory): #************************************************ if __name__ == '__main__': - + import argparse import sys import os import signal from dmr_utils.utils import try_download, mk_id_dict - + # Change the current directory to the location of the application os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) @@ -766,27 +781,27 @@ if __name__ == '__main__': # Call the external routine to build the configuration dictionary CONFIG = hb_config.build_config(cli_args.CONFIG_FILE) - + # Start the system logger if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = hb_log.config_logging(CONFIG['LOGGER']) logger.debug('Logging system started, anything from here on gets logged') - + # Set up the signal handler def sig_handler(_signal, _frame): logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal)) hblink_handler(_signal, _frame, logger) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() - + # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - + # Build the Access Control List REG_ACL = build_reg_acl('reg_acl', logger) - + # ID ALIAS CREATION # Download if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True: @@ -796,32 +811,32 @@ if __name__ == '__main__': # Try updating subscriber aliases file result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME']) logger.info(result) - + # Make Dictionaries peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE']) if peer_ids: logger.info('ID ALIAS MAPPER: peer_ids dictionary is available') - + subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE']) if subscriber_ids: logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available') - + talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE']) if talkgroup_ids: logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available') - + # Build the routing rules file BRIDGES = make_bridges('hb_confbridge_rules') - + # Build the Access Control List ACL = build_acl('sub_acl') - + # Build the Registration Access Control List REG_ACL = build_reg_acl('reg_acl', logger) - + # INITIALIZE THE REPORTING LOOP report_server = config_reports(CONFIG, logger, confbridgeReportFactory) - + # HBlink instance creation logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: @@ -832,11 +847,11 @@ if __name__ == '__main__': systems[system] = routerHBP(system, CONFIG, logger, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - + # Initialize the rule timer -- this if for user activated stuff rule_timer = task.LoopingCall(rule_timer_loop) rule_timer.start(60) - + # Initialize the stream trimmer stream_trimmer = task.LoopingCall(stream_trimmer_loop) stream_trimmer.start(5) From b639f830578d32aa1845a0b9797e14e5eb508ff3 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 12 Nov 2018 16:39:33 -0600 Subject: [PATCH 33/56] OpenBridge Improvements, bug fixes, logging --- hb_confbridge.py | 99 +++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 1fffb59..ef0007e 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -211,27 +211,31 @@ def stream_trimmer_loop(): _now = time() for system in systems: - remove_list = [] - if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - for stream_id in systems[system].STATUS: - if systems[system].STATUS[stream_id]['LAST'] < _now - 5: - remove_list.append(stream_id) - for stream_id in remove_list: - _system = systems[system].STATUS[stream_id] - _config = CONFIG['SYSTEMS'][system] - logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ - system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) - # self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) - removed = systems[system].STATUS.pop(stream_id) - logger.debug('Inactive OpenBridge Stream ID removed from System: %s, Stream ID %s', system, int_id(stream_id)) - for system in systems: + # HBP systems, master and peer if CONFIG['SYSTEMS'][system]['MODE'] != 'OPENBRIDGE': for slot in range(1,3): _slot = systems[system].STATUS[slot] if _slot['RX_TYPE'] != hb_const.HBPF_SLT_VTERM and _slot['RX_TIME'] < _now - 5: _slot['RX_TYPE'] = hb_const.HBPF_SLT_VTERM - logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ - system, int_id(_slot['RX_STREAM_ID']), get_alias(_slot['RX_RFS'], subscriber_ids), int_id(_slot['RX_RFS']), get_alias(_slot['RX_TGID'], talkgroup_ids), int_id(_slot['RX_TGID']), slot, _slot['RX_TIME'] - _slot['RX_START']) + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s TGID %s, TS %s, Duration: %s', \ + system, int_id(_slot['RX_STREAM_ID']), int_id(_slot['RX_RFS']), int_id(_slot['RX_TGID']), slot, _slot['RX_TIME'] - _slot['RX_START']) + # OBP systems + # We can't delete items from a dicationry that's being iterated, so we have to make a temporarly list of entrys to remove later + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + remove_list = [] + for stream_id in systems[system].STATUS: + if systems[system].STATUS[stream_id]['LAST'] < _now - 5: + remove_list.append(stream_id) + for stream_id in remove_list: + if stream_id in systems[system].STATUS: + _system = systems[system].STATUS[stream_id] + _config = CONFIG['SYSTEMS'][system] + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ + system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) + # self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) + removed = systems[system].STATUS.pop(stream_id) + else: + logger.error('(%s) Attemped to remove OpenBridge Stream ID %s not in the Stream ID list: %s', system, int_id(stream_id), [id for id in systems[system].STATUS]) class routerOBP(OPENBRIDGE): @@ -374,11 +378,11 @@ class routerOBP(OPENBRIDGE): self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue - # Set values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time - - if (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + # Is this a new call stream? + if (_target_status[_target['TS']]['TX_STREAM_ID'] != _stream_id): #(_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): + #if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_START'] = pkt_time _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id _target_status[_target['TS']]['TX_RFS'] = _rf_src @@ -387,8 +391,11 @@ class routerOBP(OPENBRIDGE): _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: @@ -444,8 +451,6 @@ class routerHBP(HBSYSTEM): # In TX_EMB_LC, 2-5 are burst B-E self.STATUS = { 1: { - 'RX_ACTIVE': False, - 'TX_ACTIVE': False, 'RX_START': time(), 'TX_START': time(), 'RX_SEQ': '\x00', @@ -469,8 +474,6 @@ class routerHBP(HBSYSTEM): } }, 2: { - 'RX_ACTIVE': False, - 'TX_ACTIVE': False, 'RX_START': time(), 'TX_START': time(), 'RX_SEQ': '\x00', @@ -620,21 +623,23 @@ class routerHBP(HBSYSTEM): self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue - # Set values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time - + # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_START'] = pkt_time + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time # Handle any necessary re-writes for the destination if _system['TS'] != _target['TS']: @@ -848,12 +853,18 @@ if __name__ == '__main__': reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) + def loopingErrHandle(failure): + logger.error('STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error in timed loop.\n %s', failure) + reactor.stop() + # Initialize the rule timer -- this if for user activated stuff - rule_timer = task.LoopingCall(rule_timer_loop) - rule_timer.start(60) + rule_timer_task = task.LoopingCall(rule_timer_loop) + rule_timer = rule_timer_task.start(60) + rule_timer.addErrback(loopingErrHandle) # Initialize the stream trimmer - stream_trimmer = task.LoopingCall(stream_trimmer_loop) - stream_trimmer.start(5) + stream_trimmer_task = task.LoopingCall(stream_trimmer_loop) + stream_trimmer = stream_trimmer_task.start(5) + stream_trimmer.addErrback(loopingErrHandle) reactor.run() From e5978f79ca793129cfdc17f308b6a3fe2ade00bb Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 21 Nov 2018 10:24:19 -0600 Subject: [PATCH 34/56] Consolidate ACLs into HBlink.py MAJOR CHANGE: Move ACLs into the main hblink.cfg configuraiton file and process all ingress ACLs in hblink.py itself. This means removing all other ACL processing from other programs, except hb_bridge_all.py which uses the main hblink.py ACLs for egress processing. --- acl.py | 104 ---------------- hb_bridge_all.py | 149 +++++++--------------- hb_bridge_all_rules_SAMPLE.py | 62 --------- hb_confbridge.py | 77 +----------- hb_config.py | 108 +++++++++++++--- hb_const.py | 5 +- hblink-SAMPLE.cfg | 64 +++++++++- hblink.py | 228 ++++++++++++++++++---------------- reg_acl-SAMPLE.py | 11 -- sub_acl-SAMPLE.py | 9 -- 10 files changed, 325 insertions(+), 492 deletions(-) delete mode 100755 acl.py delete mode 100755 hb_bridge_all_rules_SAMPLE.py delete mode 100755 reg_acl-SAMPLE.py delete mode 100755 sub_acl-SAMPLE.py diff --git a/acl.py b/acl.py deleted file mode 100755 index 8eeba5f..0000000 --- a/acl.py +++ /dev/null @@ -1,104 +0,0 @@ -############################################################################### -# Copyright (C) 2018 Cortney T. Buffington, N0MJS -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - -from dmr_utils.utils import int_id - -# Lowest possible Subscirber and/or talkgroup IDs allowed by ETSI standard -ID_MIN = 1 -ID_MAX = 16776415 - - -# Checks the supplied ID against the ID given, and the ACL list, and the action -# Returns True if the ID should be allowed, False if it should not be -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 acl_build(_acl): - if not _acl: - return(True, set((ID_MIN, ID_MAX))) - - acl = set() - sections = _acl.split(':') - - if sections[0] == 'PERMIT': - action = True - else: - action = False - - for entry in sections[1].split(','): - if entry == 'ALL': - acl.add((ID_MIN, ID_MAX)) - break - - elif '-' in entry: - start,end = entry.split('-') - start,end = int(start), int(end) - if (ID_MIN <= start <= ID_MAX) or (ID_MIN <= end <= ID_MAX): - acl.add((start, end)) - else: - pass #logger message here - else: - id = int(entry) - if (ID_MIN <= id <= ID_MAX): - acl.add((id, id)) - else: - pass #logger message here - - return (action, acl) - - -if __name__ == '__main__': - from time import time - from pprint import pprint - - ACL = { - 'SUB': { - 'K0USY': { - 1: 'PERMIT:1-5,3120101,3120124', - 2: 'DENY:1-5,3120101,3120124' - } - }, - 'TGID': { - 'GLOBAL': { - 1: 'PERMIT:ALL', - 2: 'DENY:ALL' - }, - 'K0USY': { - 1: 'PERMIT:1-5,3120,31201', - 2: 'DENY:1-5,3120,31201' - } - } - } - - for acl in ACL: - if 'GLOBAL' not in ACL[acl]: - ACL[acl].update({'GLOBAL': {1:'PERMIT:ALL',2:'PERMIT:ALL'}}) - for acltype in ACL[acl]: - for slot in ACL[acl][acltype]: - ACL[acl][acltype][slot] = acl_build(ACL[acl][acltype][slot]) - - pprint(ACL) - print - - print(acl_check('\x00\x00\x01', ACL['TGID']['GLOBAL'][1])) - print(acl_check('\x00\x00\x01', ACL['TGID']['K0USY'][2])) \ No newline at end of file diff --git a/hb_bridge_all.py b/hb_bridge_all.py index c371d0a..29dc967 100755 --- a/hb_bridge_all.py +++ b/hb_bridge_all.py @@ -45,10 +45,9 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, build_reg_acl +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const -from acl import acl_check, acl_build import hb_config import hb_log import hb_const @@ -65,19 +64,6 @@ __status__ = 'pre-alpha' # Module gobal varaibles -# Import rules -- at this point, just ACLs -def import_rules(_rules): - try: - rules_file = import_module(_rules) - logger.info('Rules file found and bridges imported') - return rules_file - except ImportError: - logger.info('Rules file not found. Initializing defaults') - rules_file = ModuleType('rules_file') - rules_file.ACL = {'SID':{}, 'TGID':{}} - return rules_file - - class bridgeallSYSTEM(HBSYSTEM): def __init__(self, _name, _config, _logger, _report): @@ -140,32 +126,6 @@ class bridgeallSYSTEM(HBSYSTEM): if _call_type == 'group': - # Check for GLOBAL Subscriber ID ACL Match - if acl_check(_rf_src, ACL['SID']['GLOBAL'][_slot]) == False: - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS GLOBAL ACL*** SID: %s SLOT: %s HBP Peer %s', self._system, int_id(_rf_src), _slot, int_id(_peer_id)) - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - return - # Check for SYSTEM Subscriber ID ACL Match - if acl_check(_rf_src, ACL['SID'][self._system][_slot]) == False: - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS SYSTEM ACL*** SID: %s SLOT: %s HBP Peer %s', self._system, int_id(_rf_src), _slot, int_id(_peer_id)) - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - return - - # Check for GLOBAL Talkgroup ID ACL Match - if acl_check(_dst_id, ACL['TGID']['GLOBAL'][_slot]) == False: - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS GLOBAL ACL*** TGID: %s SLOT: %s HBP Peer %s', self._system, int_id(_dst_id), _slot, int_id(_peer_id)) - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - return - # Check for SYSTEM Talkgroup ID ID ACL Match - if acl_check(_dst_id, ACL['TGID'][self._system][_slot]) == False: - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS SYSTEM ACL*** TGID: %s SLOT: %s HBP Peer %s', self._system, int_id(_dst_id), _slot, int_id(_peer_id)) - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - return - # Is this is a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): self.STATUS['RX_START'] = pkt_time @@ -191,39 +151,45 @@ class bridgeallSYSTEM(HBSYSTEM): _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] - - # Check for GLOBAL Subscriber ID ACL Match - if acl_check(_rf_src, ACL['SID']['GLOBAL'][_slot]) == False: - if (_stream_id != _target_status[_slot]['TX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS GLOBAL ACL*** SID: %s SLOT: %s HBP Peer %s', _target, int_id(_rf_src), _slot, int_id(_peer_id)) - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - return - # Check for SYSTEM Subscriber ID ACL Match - if acl_check(_rf_src, ACL['SID'][_target][_slot]) == False: - if (_stream_id != _target_status[_slot]['TX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS SYSTEM ACL*** SID: %s SLOT: %s HBP Peer %s', _target, int_id(_rf_src), _slot, int_id(_peer_id)) - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - return - - # Check for GLOBAL Talkgroup ID ACL Match - if acl_check(_dst_id, ACL['TGID']['GLOBAL'][_slot]) == False: - if (_stream_id != _target_status[_slot]['TX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS GLOBAL ACL*** TGID: %s SLOT: %s HBP Peer %s', _target, int_id(_dst_id), _slot, int_id(_peer_id)) - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - return - # Check for SYSTEM Talkgroup ID ID ACL Match - if acl_check(_dst_id, ACL['TGID'][_target][_slot]) == False: - if (_stream_id != _target_status[_slot]['TX_STREAM_ID']): - self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS SYSTEM ACL*** TGID: %s HBP Peer %s', _target, int_id(_dst_id), int_id(_peer_id)) - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - return + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + + # ACL Processing + if self._CONFIG['GLOBAL']['USE_ACL']: + if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', _target_system, int_id(_stream_id), int_id(_rf_src)) + self._laststrid = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', _target_system, int_id(_stream_id), int_id(_dst_id)) + self._laststrid = _stream_id + return + if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL', _target_system, int_id(_stream_id), int_id(_dst_id)) + self._laststrid = _stream_id + return + if self._target_system['USE_ACL']: + if not acl_check(_rf_src, _target_system['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', _target_system, int_id(_stream_id), int_id(_rf_src)) + self._laststrid = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, _target_system['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL', _target_system, int_id(_stream_id), int_id(_dst_id)) + self._laststrid = _stream_id + return + if _slot == 2 and not acl_check(_dst_id, _target_system['TG2_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%s) CALL DROPPED ON EGRESS WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL', _target_system, int_id(_stream_id), int_id(_dst_id)) + self._laststrid = _stream_id + return + self._laststrid = _stream_id systems[_target].send_system(_data) #self._logger.debug('(%s) Packet routed to system: %s', self._system, _target) - - - - #************************************************ @@ -269,9 +235,6 @@ if __name__ == '__main__': # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - - # Build the Access Control List - REG_ACL = build_reg_acl('reg_acl', logger) # ID ALIAS CREATION # Download @@ -295,43 +258,21 @@ if __name__ == '__main__': talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE']) if talkgroup_ids: logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available') - - # Import rules file - rules_file = import_rules('hb_bridge_all_rules') - - # Create ACLs - ACL = rules_file.ACL - - for acl_type in ACL: - if acl_type != 'SID' and acl_type != 'TGID': - sys.exit(('TERMINATE: SID or TGID stanzas not in ACL!!! Exiting to save you grief later')) - - if 'GLOBAL' not in ACL[acl_type]: - ACL[acl_type].update({'GLOBAL': {1:'PERMIT:ALL',2:'PERMIT:ALL'}}) - - for system_acl in ACL[acl_type]: - if system_acl not in CONFIG['SYSTEMS'] and system_acl != 'GLOBAL': - sys.exit(('TERMINATE: {} ACL configured for system {} that does not exist!!! Exiting to save you grief later'.format(acl_type, system_acl))) - for slot in ACL[acl_type][system_acl]: - ACL[acl_type][system_acl][slot] = acl_build(ACL[acl_type][system_acl][slot]) - - for system in CONFIG['SYSTEMS']: - for acl_type in ACL: - if system not in ACL[acl_type]: - logger.warning('No %s ACL for system %s - initializing \'PERMIT:ALL\'', acl_type, system) - ACL[acl_type].update({system: {1: acl_build('PERMIT:ALL'), 2: acl_build('PERMIT:ALL')}}) - - # Build the Registration Access Control List - REG_ACL = build_reg_acl('reg_acl', logger) + # INITIALIZE THE REPORTING LOOP report_server = config_reports(CONFIG, logger, reportFactory) + # HBlink instance creation - logger.info('HBlink \'hb_bridge_all.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'HBlink.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: - systems[system] = bridgeallSYSTEM(system, CONFIG, logger, report_server) + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + logger.critical('%s FATAL: Instance is mode \'OPENBRIDGE\', \n\t\t...Which would be tragic for Bridge All, since it carries multiple call\n\t\tstreams simultaneously. hb_bridge_all.py onlyl works with MMDVM-based systems', system) + sys.exit('hb_bridge_all.py cannot function with systems that are not MMDVM devices. System {} is configured as an OPENBRIDGE'.format(system)) + else: + systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) diff --git a/hb_bridge_all_rules_SAMPLE.py b/hb_bridge_all_rules_SAMPLE.py deleted file mode 100755 index e06039a..0000000 --- a/hb_bridge_all_rules_SAMPLE.py +++ /dev/null @@ -1,62 +0,0 @@ -# ACL Entries -# -# The 'action' May be PERMIT|DENY -# Each entry may be a single radio id, a hypenated range (e.g. 1-2999), or the string 'ALL'. -# if "ALL" is used, you may not include any other ranges or individual IDs. -# Format: -# ACL = 'action:id|start-end|,id|start-end,....' -# -# Sections exist for both TGIDs and Subscriber IDs. -# Sections exist for glboal actions, and per-system actions. -# ***FIRST MATCH EXITS*** - -# SID - Subscriber ID section. -# TGID - Talkgroup ID section. -# -# "GLOBAL" affects ALL systems -# "SYSTEM NAME" affects the system in quetion -# ACLs are applied both ingress AND egress -# If you omit GLOBAL or SYSTEM level ACLs, they will be initilzied -# automatically as "PERMIT:ALL" -# Each system (or global) has two sections 1 and 2, which correspond -# to timeslots 1 and 2 respectively -# -# EXAMPLE: -#ACL = { -# 'SID': { -# 'GLOBAL': { -# 1: 'PERMIT:ALL', -# 2: 'PERMIT:ALL' -# }, -# 'LINK': { -# 1: 'DENY:3120121', -# 2: 'PERMIT:ALL' -# } -# }, -# 'TGID': { -# 'GLOBAL': { -# 1: 'PERMIT:ALL', -# 2: 'PERMIT:ALL' -# }, -# 'LINK': { -# 1: 'DENY:1-5,1616', -# 2: 'PERMIT:3120' -# } -# } -#} - -ACL = { - 'SID': { - 'GLOBAL': { - 1: 'PERMIT:ALL', - 2: 'PERMIT:ALL' - } - }, - 'TGID': { - 'GLOBAL': { - 1: 'PERMIT:ALL', - 2: 'PERMIT:ALL' - } - } -} - diff --git a/hb_confbridge.py b/hb_confbridge.py index ef0007e..a7832ab 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -45,7 +45,7 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, build_reg_acl +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const import hb_config @@ -115,61 +115,8 @@ def make_bridges(_hb_confbridge_bridges): _system['TIMER'] = time() + _system['TIMEOUT'] else: _system['TIMER'] = time() - return bridge_file.BRIDGES - - -# Import subscriber ACL -# ACL may be a single list of subscriber IDs -# Global action is to allow or deny them. Multiple lists with different actions and ranges -# are not yet implemented. -def build_acl(_sub_acl): - ACL = set() - try: - acl_file = import_module(_sub_acl) - logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs') - sections = acl_file.ACL.split(':') - ACL_ACTION = sections[0] - entries_str = sections[1] - - - for entry in entries_str.split(','): - if '-' in entry: - start,end = entry.split('-') - start,end = int(start), int(end) - for id in range(start, end+1): - ACL.add(hex_str_3(id)) - else: - id = int(entry) - ACL.add(hex_str_3(id)) - - logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL))) - - except ImportError: - logger.info('ACL file not found or invalid - all subscriber IDs are valid') - ACL_ACTION = 'NONE' - - # Depending on which type of ACL is used (PERMIT, DENY... or there isn't one) - # define a differnet function to be used to check the ACL - global allow_sub - if ACL_ACTION == 'PERMIT': - def allow_sub(_sub): - if _sub in ACL: - return True - else: - return False - elif ACL_ACTION == 'DENY': - def allow_sub(_sub): - if _sub not in ACL: - return True - else: - return False - else: - def allow_sub(_sub): - return True - - return ACL - + # Run this every minute for rule timer updates def rule_timer_loop(): @@ -250,12 +197,6 @@ class routerOBP(OPENBRIDGE): _bits = int_id(_data[15]) if _call_type == 'group': - - # Check for ACL match, and return if the subscriber is not allowed - if allow_sub(_rf_src) == False: - self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id)) - return - # Is this a new call stream? if (_stream_id not in self.STATUS): # This is a new call stream @@ -505,11 +446,6 @@ class routerHBP(HBSYSTEM): if _call_type == 'group': - # Check for ACL match, and return if the subscriber is not allowed - if allow_sub(_rf_src) == False: - self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id)) - return - # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): @@ -804,9 +740,6 @@ if __name__ == '__main__': for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - # Build the Access Control List - REG_ACL = build_reg_acl('reg_acl', logger) - # ID ALIAS CREATION # Download if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True: @@ -833,12 +766,6 @@ if __name__ == '__main__': # Build the routing rules file BRIDGES = make_bridges('hb_confbridge_rules') - # Build the Access Control List - ACL = build_acl('sub_acl') - - # Build the Registration Access Control List - REG_ACL = build_reg_acl('reg_acl', logger) - # INITIALIZE THE REPORTING LOOP report_server = config_reports(CONFIG, logger, confbridgeReportFactory) diff --git a/hb_config.py b/hb_config.py index 5600c99..d7024fc 100755 --- a/hb_config.py +++ b/hb_config.py @@ -28,6 +28,7 @@ change. import ConfigParser import sys +import hb_const as const from socket import gethostbyname @@ -39,6 +40,61 @@ __license__ = 'GNU GPLv3' __maintainer__ = 'Cort Buffington, N0MJS' __email__ = 'n0mjs@me.com' +# Processing of ALS goes here. It's separated from the acl_build function because this +# code is hblink config-file format specific, and acl_build is abstracted +def process_acls(_config): + # Global registration ACL + _config['GLOBAL']['REG_ACL'] = acl_build(_config['GLOBAL']['REG_ACL'], const.PEER_MAX) + + # Global subscriber and TGID ACLs + for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: + _config['GLOBAL'][acl] = acl_build(_config['GLOBAL'][acl], const.ID_MAX) + + # System level ACLs + for system in _config['SYSTEMS']: + # Registration ACLs (which make no sense for peer systems) + if _config['SYSTEMS'][system]['MODE'] == 'MASTER': + _config['SYSTEMS'][system]['REG_ACL'] = acl_build(_config['SYSTEMS'][system]['REG_ACL'], const.PEER_MAX) + + # Subscriber and TGID ACLs (valid for all system types) + for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: + _config['SYSTEMS'][system][acl] = acl_build(_config['SYSTEMS'][system][acl], const.ID_MAX) + +# Create an access control list that is programatically useable from human readable: +# ORIGINAL: 'DENY:1-5,3120101,3120124' +# PROCESSED: (False, set([(1, 5), (3120124, 3120124), (3120101, 3120101)])) +def acl_build(_acl, _max): + if not _acl: + return(True, set((const.ID_MIN, _max))) + + acl = set() + sections = _acl.split(':') + + if sections[0] == 'PERMIT': + action = True + else: + action = False + + for entry in sections[1].split(','): + if entry == 'ALL': + acl.add((const.ID_MIN, _max)) + break + + elif '-' in entry: + start,end = entry.split('-') + start,end = int(start), int(end) + if (const.ID_MIN <= start <= _max) or (const.ID_MIN <= end <= _max): + acl.add((start, end)) + else: + sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE (} - {})IN RANGE-BASED ENTRY: {}'.format(const.ID_MIN, _max, entry)) + else: + id = int(entry) + if (const.ID_MIN <= id <= _max): + acl.add((id, id)) + else: + sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {}) IN SINGLE ID ENTRY: {}'.format(const.ID_MIN, _max, entry)) + + return (action, acl) def build_config(_config_file): config = ConfigParser.ConfigParser() @@ -51,7 +107,6 @@ def build_config(_config_file): CONFIG['REPORTS'] = {} CONFIG['LOGGER'] = {} CONFIG['ALIASES'] = {} - CONFIG['AMBE'] = {} CONFIG['SYSTEMS'] = {} try: @@ -60,7 +115,12 @@ def build_config(_config_file): CONFIG['GLOBAL'].update({ 'PATH': config.get(section, 'PATH'), 'PING_TIME': config.getint(section, 'PING_TIME'), - 'MAX_MISSED': config.getint(section, 'MAX_MISSED') + 'MAX_MISSED': config.getint(section, 'MAX_MISSED'), + 'USE_ACL': config.get(section, 'USE_ACL'), + 'REG_ACL': config.get(section, 'REG_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') }) elif section == 'REPORTS': @@ -91,12 +151,6 @@ def build_config(_config_file): 'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400, }) - elif section == 'AMBE': - CONFIG['AMBE'].update({ - 'EXPORT_IP': gethostbyname(config.get(section, 'EXPORT_IP')), - 'EXPORT_PORT': config.getint(section, 'EXPORT_PORT'), - }) - elif config.getboolean(section, 'ENABLED'): if config.get(section, 'MODE') == 'PEER': CONFIG['SYSTEMS'].update({section: { @@ -127,7 +181,11 @@ def build_config(_config_file): 'SOFTWARE_ID': config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'PACKAGE_ID': config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), - 'OPTIONS': config.get(section, 'OPTIONS') + 'OPTIONS': config.get(section, 'OPTIONS'), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') }}) CONFIG['SYSTEMS'][section].update({'STATS': { 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES @@ -148,7 +206,12 @@ def build_config(_config_file): 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE'), - 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME') + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'REG_ACL': config.get(section, 'REG_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') }}) CONFIG['SYSTEMS'][section].update({'PEERS': {}}) @@ -163,19 +226,20 @@ def build_config(_config_file): 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_ACL'), + 'TG2_ACL': 'PERMIT:ALL' }}) except ConfigParser.Error, err: - print "Cannot parse configuration file. %s" %err - sys.exit('Could not parse configuration file, exiting...') + sys.exit('Error processing configuration file -- {}'.format(err)) + process_acls(CONFIG) + return CONFIG - - - - # Used to run this file direclty and print the config, # which might be useful for debugging if __name__ == '__main__': @@ -183,6 +247,7 @@ if __name__ == '__main__': import os import argparse from pprint import pprint + from dmr_utils.utils import int_id # Change the current directory to the location of the application os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) @@ -197,5 +262,14 @@ if __name__ == '__main__': if not cli_args.CONFIG_FILE: cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' + CONFIG = build_config(cli_args.CONFIG_FILE) + pprint(CONFIG) - pprint(build_config(cli_args.CONFIG_FILE)) + 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] + + print acl_check('\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL']) \ No newline at end of file diff --git a/hb_const.py b/hb_const.py index 7732b73..578b363 100755 --- a/hb_const.py +++ b/hb_const.py @@ -46,4 +46,7 @@ HBPF_VOICE = 0x0 HBPF_VOICE_SYNC = 0x1 HBPF_DATA_SYNC = 0x2 HBPF_SLT_VHEAD = 0x1 -HBPF_SLT_VTERM = 0x2 \ No newline at end of file +HBPF_SLT_VTERM = 0x2 + +# Higheset peer ID permitted by HBP +PEER_MAX = 4294967295 \ No newline at end of file diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index af1e833..55b4675 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -4,10 +4,47 @@ # - how often the Master maintenance loop runs # MAX_MISSED - how many pings are missed before we give up and re-register # - number of times the master maintenance loop runs before de-registering a peer +# +# ACLs: +# +# Access Control Lists are a very powerful tool for administering your system. +# But they consume packet processing time. Disable them if you are not using them. +# But be aware that, as of now, the confiuration stanzas still need the ACL +# sections configured even if you're not using them. +# +# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!! +# +# The 'action' May be PERMIT|DENY +# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999) +# Format: +# ACL = 'action:id|start-end|,id|start-end,....' +# --for example-- +# SUB_ACL: DENY:1,1000-2000,4500-60000,17 +# +# ACL Types: +# REG_ACL: peer radio IDs for registration (only used on HBP master systems) +# SUB_ACL: subscriber IDs for end-users +# TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1 +# TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2 +# +# ACLs may be repeated for individual systems if needed for granularity +# Global ACLs will be processed BEFORE the system level ACLs +# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes' +# All elements, processing continues. Packets are discarded at the first +# negative match, or 'reject' from an ACL element. +# +# If you do not wish to use ACLs, set them to 'PERMIT:ALL' +# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all +# traffic is passed as TS 1 between OpenBridges [GLOBAL] PATH: ./ PING_TIME: 5 MAX_MISSED: 3 +USE_ACL: True +REG_ACL: PERMIT:ALL +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL # NOT YET WORKING: NETWORK REPORTING CONFIGURATION @@ -87,7 +124,12 @@ EXPORT_PORT: 1234 # connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that # will be sent to the other server to identify this connection. # other parameters follow the other system types. -[3199] +# +# ACLs: +# OpenBridge does not 'register', so registration ACL is meaningless. +# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. +# Otherwise ACLs work as described in the global stanza +[OBP-1] MODE: OPENBRIDGE ENABLED: True IP: @@ -96,6 +138,9 @@ NETWORK_ID: 3129100 PASSPHRASE: password TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 +USE_ACL: True +SUB_ACL: 1 +TGID_ACL: PERMIT:ALL # MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS # HomeBrew Protocol Master instances go here. @@ -103,6 +148,9 @@ TARGET_PORT: 62035 # Port should be the port you want this master to listen on. It must be unique # and unused by anything else. # Repeat - if True, the master repeats traffic to peers, False, it does nothing. +# +# ACLs: +# See comments in the GLOBAL stanza [MASTER-1] MODE: MASTER ENABLED: True @@ -112,6 +160,11 @@ IP: PORT: 54000 PASSPHRASE: s3cr37w0rd GROUP_HANGTIME: 5 +USE_ACL: True +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL # PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS # There are a LOT of errors in the HB Protocol specifications on this one! @@ -122,6 +175,9 @@ GROUP_HANGTIME: 5 # Height is in meters # Setting Loose to True relaxes the validation on packets received from the master. # This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc. +# +# ACLs: +# See comments in the GLOBAL stanza [REPEATER-1] MODE: PEER ENABLED: True @@ -148,4 +204,8 @@ URL: www.w1abc.org SOFTWARE_ID: 20170620 PACKAGE_ID: MMDVM_HBlink GROUP_HANGTIME: 5 -OPTIONS: +OPTIONS: +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index 6c45eae..65e339b 100755 --- a/hblink.py +++ b/hblink.py @@ -48,6 +48,7 @@ from twisted.internet import reactor, task # Other files we pull from -- this is mostly for readability and segmentation import hb_log import hb_config +import hb_const as const from dmr_utils.utils import int_id, hex_str_4 # Imports for the reporting server @@ -93,93 +94,15 @@ def hblink_handler(_signal, _frame, _logger): _logger.info('SHUTDOWN: DE-REGISTER SYSTEM: %s', system) systems[system].dereg() - -# Import subscriber registration ACL -# REG_ACL may be a single list of subscriber IDs -# Global action is to allow or deny them. Multiple lists with different actions and ranges -# are not yet implemented. -def build_reg_acl(_reg_acl, _logger): - REG_ACL = set() - try: - acl_file = import_module(_reg_acl) - _logger.info('Registration ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs') - sections = acl_file.REG_ACL.split(':') - REG_ACL_ACTION = sections[0] - entries_str = sections[1] - - for entry in entries_str.split(','): - if '-' in entry: - start,end = entry.split('-') - start,end = int(start), int(end) - for id in range(start, end+1): - REG_ACL.add(hex_str_4(id)) - else: - id = int(entry) - REG_ACL.add(hex_str_4(id)) - - _logger.info('Registration ACL loaded: action "{}" for {:,} registration IDs'.format( REG_ACL_ACTION, len(REG_ACL))) +# Check a supplied ID against the ACL provided. Returns action (True|False) based +# on matching and the action specified. +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] - except ImportError: - _logger.info('Registration ACL file not found or invalid - all IDs may register with this system') - REG_ACL_ACTION = 'NONE' - - # Depending on which type of REG_ACL is used (PERMIT, DENY... or there isn't one) - # define a differnet function to be used to check the ACL - global allow_reg - if REG_ACL_ACTION == 'PERMIT': - def allow_reg(_id): - if _id in REG_ACL: - return True - else: - return False - elif REG_ACL_ACTION == 'DENY': - def allow_reg(_id): - if _id not in REG_ACL: - return True - else: - return False - else: - def allow_reg(_id): - return True - - return REG_ACL - -#************************************************ -# AMBE CLASS: Used to parse out AMBE and send to gateway -#************************************************ - -class AMBE: - def __init__(self, _config, _logger): - self._CONFIG = _config - self._logger = _logger - - self._sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) - self._exp_ip = self._CONFIG['AMBE']['EXPORT_IP'] - self._exp_port = self._CONFIG['AMBE']['EXPORT_PORT'] - - def parseAMBE(self, _peer, _data): - _seq = int_id(_data[4:5]) - _srcID = int_id(_data[5:8]) - _dstID = int_id(_data[8:11]) - _rptID = int_id(_data[11:15]) - _bits = int_id(_data[15:16]) # SCDV NNNN (Slot|Call type|Data|Voice|Seq or Data type) - _slot = 2 if _bits & 0x80 else 1 - _callType = 1 if (_bits & 0x40) else 0 - _frameType = (_bits & 0x30) >> 4 - _voiceSeq = (_bits & 0x0f) - _streamID = int_id(_data[16:20]) - self._logger.debug('(%s) seq: %d srcID: %d dstID: %d rptID: %d bits: %0X slot:%d callType: %d frameType: %d voiceSeq: %d streamID: %0X', - _peer, _seq, _srcID, _dstID, _rptID, _bits, _slot, _callType, _frameType, _voiceSeq, _streamID ) - - #logger.debug('Frame 1:(%s)', self.ByteToHex(_data)) - _dmr_frame = BitArray('0x'+ahex(_data[20:])) - _ambe = _dmr_frame[0:108] + _dmr_frame[156:264] - #_sock.sendto(_ambe.tobytes(), ("127.0.0.1", 31000)) - - ambeBytes = _ambe.tobytes() - self._sock.sendto(ambeBytes[0:9], (self._exp_ip, self._exp_port)) - self._sock.sendto(ambeBytes[9:18], (self._exp_ip, self._exp_port)) - self._sock.sendto(ambeBytes[18:27], (self._exp_ip, self._exp_port)) #************************************************ @@ -194,6 +117,7 @@ class OPENBRIDGE(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] + self._laststrid = '' def dereg(self): self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) @@ -206,7 +130,7 @@ class OPENBRIDGE(DatagramProtocol): # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # self._logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) else: - self._logger.error('(%s) OpenBridge system was asked to send non DMRD packet') + self._logger.error('(%s) OpenBridge system was asked to send non DMRD packet', self._system) def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass @@ -233,12 +157,42 @@ class OPENBRIDGE(DatagramProtocol): _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #self._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 + if _slot != 1: + self._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 self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): + if self._laststrid != _stream_id: + self._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 = _stream_id + return + if self._config['USE_ACL']: + if not acl_check(_rf_src, self._config['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if not acl_check(_dst_id, self._config['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + self._laststrid = _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) else: self._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:])) - + #************************************************ # HB MASTER CLASS @@ -252,6 +206,7 @@ class HBSYSTEM(DatagramProtocol): self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] + self._laststrid = '' # Define shortcuts and generic function names based on the type of system we are if self._config['MODE'] == 'MASTER': @@ -267,10 +222,6 @@ class HBSYSTEM(DatagramProtocol): self.maintenance_loop = self.peer_maintenance_loop self.datagramReceived = self.peer_datagramReceived self.dereg = self.peer_dereg - - # Configure for AMBE audio export if enabled - if self._config['EXPORT_AMBE']: - self._ambe = AMBE(_config, _logger) def startProtocol(self): # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds @@ -363,10 +314,41 @@ class HBSYSTEM(DatagramProtocol): _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #self._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)) - - # If AMBE audio exporting is configured... - if self._config['EXPORT_AMBE']: - self._ambe.parseAMBE(self._system, _data) + + # ACL Processing + if self._CONFIG['GLOBAL']['USE_ACL']: + if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if self._config['USE_ACL']: + if not acl_check(_rf_src, self._config['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + self._laststrid = _stream_id # The basic purpose of a master is to repeat to the peers if self._config['REPEAT'] == True: @@ -377,13 +359,16 @@ class HBSYSTEM(DatagramProtocol): #self.send_peer(_peer, _data[:11] + self._config['RADIO_ID'] + _data[15:]) #self._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] - if allow_reg(_peer_id): # Check for valid Radio ID - self._peers.update({_peer_id: { # Build the configuration data strcuture for the peer + # Check for valid Radio ID + if acl_check(_peer_id, self._CONFIG['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', 'PINGS_RECEIVED': 0, 'LAST_PING': time(), @@ -515,10 +500,42 @@ class HBSYSTEM(DatagramProtocol): _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] self._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)) - - # If AMBE audio exporting is configured... - if self._config['EXPORT_AMBE']: - self._ambe.parseAMBE(self._system, _data) + + # ACL Processing + if self._CONFIG['GLOBAL']['USE_ACL']: + if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if self._config['USE_ACL']: + if not acl_check(_rf_src, self._config['SUB_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): + if self._laststrid != _stream_id: + self._logger.debug('(%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 = _stream_id + return + self._laststrid = _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) @@ -701,9 +718,6 @@ if __name__ == '__main__': for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - # Build the Registration Access Control List - REG_ACL = build_reg_acl('reg_acl', logger) - # INITIALIZE THE REPORTING LOOP report_server = config_reports(CONFIG, logger, reportFactory) @@ -718,4 +732,4 @@ if __name__ == '__main__': reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - reactor.run() + reactor.run() \ No newline at end of file diff --git a/reg_acl-SAMPLE.py b/reg_acl-SAMPLE.py deleted file mode 100755 index 9a9e701..0000000 --- a/reg_acl-SAMPLE.py +++ /dev/null @@ -1,11 +0,0 @@ -# -# Used to limit HomeBrew repeater Protocol registrations. -# -# If this is the SAMPLE file, you'll need to made a copy or start from scratch -# with one called reg_acl.py -# -# The 'action' May be PERMIT|DENY -# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999) -# Format: -# ACL = 'action:id|start-end|,id|start-end,....' -REG_ACL = 'DENY:1' diff --git a/sub_acl-SAMPLE.py b/sub_acl-SAMPLE.py deleted file mode 100755 index c249001..0000000 --- a/sub_acl-SAMPLE.py +++ /dev/null @@ -1,9 +0,0 @@ -# -# To use this feature, you'll need to copy this, or create a file called -# sub_acl.py that's like this one, with your local parameters in it. -# -# The 'action' May be PERMIT|DENY -# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999) -# Format: -# ACL = 'action:id|start-end|,id|start-end,....' -ACL = 'DENY:0-2999,4000000-4000999' From 145d2f45376674d63ba79477b42f3db59b14ed98 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Wed, 21 Nov 2018 10:58:16 -0600 Subject: [PATCH 35/56] Fixed typo in registration ACL processing --- hblink.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hblink.py b/hblink.py index 65e339b..1f5691c 100755 --- a/hblink.py +++ b/hblink.py @@ -366,7 +366,7 @@ class HBSYSTEM(DatagramProtocol): elif _command == 'RPTL': # RPTLogin -- a repeater wants to login _peer_id = _data[4:8] # Check for valid Radio ID - if acl_check(_peer_id, self._CONFIG['REG_ACL']) and acl_check(_peer_id, self._config['REG_ACL']): + 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', @@ -732,4 +732,4 @@ if __name__ == '__main__': reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - reactor.run() \ No newline at end of file + reactor.run() From c5f2811a4557a19ed73ae86623bdac98743f3bd6 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Wed, 21 Nov 2018 20:55:54 -0600 Subject: [PATCH 36/56] Update ACL logging info --- hblink.py | 71 ++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/hblink.py b/hblink.py index 1f5691c..e406ad9 100755 --- a/hblink.py +++ b/hblink.py @@ -70,21 +70,21 @@ systems = {} # Timed loop used for reporting HBP status # # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE -def config_reports(_config, _logger, _factory): +def config_reports(_config, _logger, _factory): if True: #_config['REPORTS']['REPORT']: def reporting_loop(_logger, _server): _logger.debug('Periodic reporting loop started') _server.send_config() - + _logger.info('HBlink TCP reporting server configured') - + report_server = _factory(_config, _logger) report_server.clients = [] reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server) - + reporting = task.LoopingCall(reporting_loop, _logger, report_server) reporting.start(_config['REPORTS']['REPORT_INTERVAL']) - + return report_server @@ -102,7 +102,7 @@ def acl_check(_id, _acl): if entry[0] <= id <= entry[1]: return _acl[0] return not _acl[0] - + #************************************************ @@ -121,7 +121,7 @@ class OPENBRIDGE(DatagramProtocol): def dereg(self): self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) - + def send_system(self, _packet): if _packet[:4] == 'DMRD': _packet = _packet[:11] + self._config['NETWORK_ID'] + _packet[15:] @@ -144,7 +144,7 @@ class OPENBRIDGE(DatagramProtocol): _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] @@ -157,12 +157,12 @@ class OPENBRIDGE(DatagramProtocol): _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #self._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 if _slot != 1: self._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']): @@ -186,13 +186,12 @@ class OPENBRIDGE(DatagramProtocol): self._logger.debug('(%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 = _stream_id return - self._laststrid = _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) else: self._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:])) - + #************************************************ # HB MASTER CLASS @@ -207,7 +206,7 @@ class HBSYSTEM(DatagramProtocol): self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] self._laststrid = '' - + # Define shortcuts and generic function names based on the type of system we are if self._config['MODE'] == 'MASTER': self._peers = self._CONFIG['SYSTEMS'][self._system]['PEERS'] @@ -215,7 +214,7 @@ class HBSYSTEM(DatagramProtocol): self.maintenance_loop = self.master_maintenance_loop self.datagramReceived = self.master_datagramReceived self.dereg = self.master_dereg - + elif self._config['MODE'] == 'PEER': self._stats = self._config['STATS'] self.send_system = self.send_master @@ -227,7 +226,7 @@ class HBSYSTEM(DatagramProtocol): # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds self._system_maintenance = task.LoopingCall(self.maintenance_loop) self._system_maintenance_loop = self._system_maintenance.start(self._CONFIG['GLOBAL']['PING_TIME']) - + # Aliased in __init__ to maintenance_loop if system is a master def master_maintenance_loop(self): self._logger.debug('(%s) Master maintenance loop started', self._system) @@ -238,8 +237,8 @@ class HBSYSTEM(DatagramProtocol): self._logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) # Remove any timed out peers from the configuration del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] - - # Aliased in __init__ to maintenance_loop if system is a peer + + # Aliased in __init__ to maintenance_loop if system is a peer def peer_maintenance_loop(self): self._logger.debug('(%s) Peer maintenance loop started', self._system) if self._stats['PING_OUTSTANDING']: @@ -281,16 +280,16 @@ class HBSYSTEM(DatagramProtocol): def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass - + def master_dereg(self): for _peer in self._peers: self.send_peer(_peer, 'MSTCL'+_peer) self._logger.info('(%s) De-Registration sent to Peer: %s (%s)', self._system, self._peers[_peer]['CALLSIGN'], self._peers[_peer]['RADIO_ID']) - + def peer_dereg(self): self.send_master('RPTCL'+self._config['RADIO_ID']) self._logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) - + # Aliased in __init__ to datagramReceived if system is a master def master_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! @@ -314,7 +313,7 @@ class HBSYSTEM(DatagramProtocol): _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] #self._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)) - + # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): @@ -348,7 +347,6 @@ class HBSYSTEM(DatagramProtocol): self._logger.debug('(%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 = _stream_id return - self._laststrid = _stream_id # The basic purpose of a master is to repeat to the peers if self._config['REPEAT'] == True: @@ -368,7 +366,7 @@ class HBSYSTEM(DatagramProtocol): # 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: { + self._peers.update({_peer_id: { 'CONNECTION': 'RPTL-RECEIVED', 'PINGS_RECEIVED': 0, 'LAST_PING': time(), @@ -500,7 +498,7 @@ class HBSYSTEM(DatagramProtocol): _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] self._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']): @@ -534,8 +532,7 @@ class HBSYSTEM(DatagramProtocol): self._logger.debug('(%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 = _stream_id return - self._laststrid = _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) @@ -650,12 +647,12 @@ class report(NetstringReceiver): self.send_config() else: self._factory._logger.error('got unknown opcode') - + class reportFactory(Factory): def __init__(self, config, logger): self._config = config self._logger = logger - + def buildProtocol(self, addr): if (addr.host) in self._config['REPORTS']['REPORT_CLIENTS'] or '*' in self._config['REPORTS']['REPORT_CLIENTS']: self._logger.debug('Permitting report server connection attempt from: %s:%s', addr.host, addr.port) @@ -663,15 +660,15 @@ class reportFactory(Factory): else: self._logger.error('Invalid report server connection attempt from: %s:%s', addr.host, addr.port) return None - + def send_clients(self, _message): for client in self.clients: client.sendString(_message) - + def send_config(self): serialized = pickle.dumps(self._config['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL) self.send_clients(REPORT_OPCODES['CONFIG_SND']+serialized) - + #************************************************ # MAIN PROGRAM LOOP STARTS HERE @@ -683,8 +680,8 @@ if __name__ == '__main__': import sys import os import signal - - + + # Change the current directory to the location of the application os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) @@ -700,7 +697,7 @@ if __name__ == '__main__': # Call the external routine to build the configuration dictionary CONFIG = hb_config.build_config(cli_args.CONFIG_FILE) - + # Call the external routing to start the system logger if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL @@ -713,11 +710,11 @@ if __name__ == '__main__': hblink_handler(_signal, _frame, logger) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() - + # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - + # INITIALIZE THE REPORTING LOOP report_server = config_reports(CONFIG, logger, reportFactory) From 2aafcf4583b58c1b2c09e2a79487afe204bda6a3 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 24 Nov 2018 08:39:16 -0600 Subject: [PATCH 37/56] Update hblink-SAMPLE.cfg --- hblink-SAMPLE.cfg | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 55b4675..739dcf3 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -105,13 +105,6 @@ PEER_URL: https://ham-digital.org/status/rptrs.csv SUBSCRIBER_URL: https://ham-digital.org/status/users.csv STALE_DAYS: 7 -# EXPORT AMBE DATA -# This is for exporting AMBE audio frames to an an "external" process for -# decoding or other nefarious actions. -[AMBE] -EXPORT_IP: 127.0.0.1 -EXPORT_PORT: 1234 - # OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS # OpenBridge is a protocol originall created by DMR+ for connection between an # IPSC2 server and Brandmeister. It has been implemented here at the suggestion From 5bbe8a25113db67398791fce8f833c3dfea5571c Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 24 Nov 2018 08:41:11 -0600 Subject: [PATCH 38/56] Update hblink-SAMPLE.cfg --- hblink-SAMPLE.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 739dcf3..4e08e91 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -9,7 +9,7 @@ # # Access Control Lists are a very powerful tool for administering your system. # But they consume packet processing time. Disable them if you are not using them. -# But be aware that, as of now, the confiuration stanzas still need the ACL +# But be aware that, as of now, the configuration stanzas still need the ACL # sections configured even if you're not using them. # # REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!! From 71e794dd52c382eebe1d164988ecaf0d8f206b1d Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 24 Nov 2018 10:20:47 -0600 Subject: [PATCH 39/56] modified logging methodology configure a logger in each module, create the proper configuration in the __main__ of each file --- hb_bridge_all.py | 47 ++++------- hb_confbridge.py | 135 +++++++++++++++----------------- hb_log.py | 17 ++-- hb_parrot.py | 46 +++++------ hblink.py | 197 +++++++++++++++++++++++++++-------------------- 5 files changed, 217 insertions(+), 225 deletions(-) diff --git a/hb_bridge_all.py b/hb_bridge_all.py index 29dc967..d0da717 100755 --- a/hb_bridge_all.py +++ b/hb_bridge_all.py @@ -45,13 +45,18 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const import hb_config import hb_log import hb_const +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + + # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS' __copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' @@ -66,8 +71,8 @@ __status__ = 'pre-alpha' class bridgeallSYSTEM(HBSYSTEM): - def __init__(self, _name, _config, _logger, _report): - HBSYSTEM.__init__(self, _name, _config, _logger, _report) + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -228,42 +233,20 @@ if __name__ == '__main__': # Set up the signal handler def sig_handler(_signal, _frame): logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame, logger) + hblink_handler(_signal, _frame) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - - # ID ALIAS CREATION - # Download - if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True: - # Try updating peer aliases file - result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME']) - logger.info(result) - # Try updating subscriber aliases file - result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME']) - logger.info(result) - - # Make Dictionaries - peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE']) - if peer_ids: - logger.info('ID ALIAS MAPPER: peer_ids dictionary is available') - - subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE']) - if subscriber_ids: - logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available') - - talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE']) - if talkgroup_ids: - logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available') - + # Create the name-number mapping dictionaries + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) + # INITIALIZE THE REPORTING LOOP - report_server = config_reports(CONFIG, logger, reportFactory) - - + report_server = config_reports(CONFIG, reportFactory) + # HBlink instance creation logger.info('HBlink \'HBlink.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: @@ -272,7 +255,7 @@ if __name__ == '__main__': logger.critical('%s FATAL: Instance is mode \'OPENBRIDGE\', \n\t\t...Which would be tragic for Bridge All, since it carries multiple call\n\t\tstreams simultaneously. hb_bridge_all.py onlyl works with MMDVM-based systems', system) sys.exit('hb_bridge_all.py cannot function with systems that are not MMDVM devices. System {} is configured as an OPENBRIDGE'.format(system)) else: - systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) + systems[system] = HBSYSTEM(system, CONFIG, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) diff --git a/hb_confbridge.py b/hb_confbridge.py index a7832ab..32b7005 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -45,7 +45,7 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const import hb_config @@ -55,6 +55,11 @@ import hb_const # Stuff for socket reporting import cPickle as pickle +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + + # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS' __copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' @@ -68,20 +73,20 @@ __email__ = 'n0mjs@me.com' # Timed loop used for reporting HBP status # # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE -def config_reports(_config, _logger, _factory): +def config_reports(_config, _factory): if True: #_config['REPORTS']['REPORT']: - def reporting_loop(_logger, _server): - _logger.debug('Periodic reporting loop started') + def reporting_loop(logger, _server): + logger.debug('Periodic reporting loop started') _server.send_config() _server.send_bridge() - _logger.info('HBlink TCP reporting server configured') + logger.info('HBlink TCP reporting server configured') - report_server = _factory(_config, _logger) + report_server = _factory(_config) report_server.clients = [] reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server) - reporting = task.LoopingCall(reporting_loop, _logger, report_server) + reporting = task.LoopingCall(reporting_loop, logger, report_server) reporting.start(_config['REPORTS']['REPORT_INTERVAL']) return report_server @@ -186,8 +191,8 @@ def stream_trimmer_loop(): class routerOBP(OPENBRIDGE): - def __init__(self, _name, _config, _logger, _report): - OPENBRIDGE.__init__(self, _name, _config, _logger, _report) + def __init__(self, _name, _config, _report): + OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} @@ -218,7 +223,7 @@ class routerOBP(OPENBRIDGE): self.STATUS[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src - self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + 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) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) @@ -250,18 +255,18 @@ class routerOBP(OPENBRIDGE): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) _target_status[_stream_id]['LC'] = decoded['LC'] - self._logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) # If we don't have a voice header then don't wait to decode the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: _target_status[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src - self._logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time @@ -301,22 +306,22 @@ class routerOBP(OPENBRIDGE): if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if self.STATUS[_stream_id]['CONTENTION'] == False: self.STATUS[_stream_id]['CONTENTION'] = True - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + 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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) continue if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if self.STATUS[_stream_id]['CONTENTION'] == False: self.STATUS[_stream_id]['CONTENTION'] = True - self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) continue if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): if self.STATUS[_stream_id]['CONTENTION'] == False: self.STATUS[_stream_id]['CONTENTION'] = True - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + 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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) continue if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): if self.STATUS[_stream_id]['CONTENTION'] == False: self.STATUS[_stream_id]['CONTENTION'] = True - self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + 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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue # Is this a new call stream? @@ -332,8 +337,8 @@ class routerOBP(OPENBRIDGE): _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_target['TS']]['TX_TIME'] = pkt_time @@ -366,26 +371,26 @@ class routerOBP(OPENBRIDGE): # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) - #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Final actions - Is this a voice terminator? if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_stream_id]['START'] - self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ + logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %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 CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) removed = self.STATUS.pop(_stream_id) - self._logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) + logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) if not removed: - self_logger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) class routerHBP(HBSYSTEM): - def __init__(self, _name, _config, _logger, _report): - HBSYSTEM.__init__(self, _name, _config, _logger, _report) + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -449,12 +454,12 @@ class routerHBP(HBSYSTEM): # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - self._logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) return # This is a new call stream self.STATUS[_slot]['RX_START'] = pkt_time - self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + 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) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id))) @@ -494,18 +499,18 @@ class routerHBP(HBSYSTEM): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) _target_status[_stream_id]['LC'] = decoded['LC'] - self._logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + logger.debug('(%s) Created LC for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) # If we don't have a voice header then don't wait to decode the Embedded LC # just make a new one from the HBP header. This is good enough, and it saves lots of time else: _target_status[_stream_id]['LC'] = const.LC_OPT + _dst_id + _rf_src - self._logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) + logger.info('(%s) Created LC with *LATE ENTRY* for OpenBridge destination: System: %s, TGID: %s', self._system, _target['SYSTEM'], int_id(_target['TGID'])) _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(_target_status[_stream_id]['LC']) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(_target_status[_stream_id]['LC']) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time @@ -544,19 +549,19 @@ class routerHBP(HBSYSTEM): # if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + 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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) continue if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) continue if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + 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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) continue if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO): if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq: - self._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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + 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['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue # Is this a new call stream? @@ -571,8 +576,8 @@ class routerHBP(HBSYSTEM): _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - self._logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - self._logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_target['TS']]['TX_TIME'] = pkt_time @@ -605,14 +610,14 @@ class routerHBP(HBSYSTEM): # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) - #self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) # Final actions - Is this a voice terminator? if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_slot]['RX_START'] - self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ + logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %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 CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) @@ -630,7 +635,7 @@ class routerHBP(HBSYSTEM): # TGID matches a rule source, reset its timer if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): _system['TIMER'] = pkt_time + _system['TIMEOUT'] - self._logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) + logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) # TGID matches an ACTIVATION trigger if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: @@ -639,15 +644,15 @@ class routerHBP(HBSYSTEM): if _system['ACTIVE'] == False: _system['ACTIVE'] = True _system['TIMER'] = pkt_time + _system['TIMEOUT'] - self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) + logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) # Cancel the timer if we've enabled an "OFF" type timeout if _system['TO_TYPE'] == 'OFF': _system['TIMER'] = pkt_time - self._logger.info('(%s) Bridge: %s set to "OFF" with an on timer rule: timeout timer cancelled', self._system, _bridge) + logger.info('(%s) Bridge: %s set to "OFF" with an on timer rule: timeout timer cancelled', self._system, _bridge) # Reset the timer for the rule if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON': _system['TIMER'] = pkt_time + _system['TIMEOUT'] - self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) + logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) # TGID matches an DE-ACTIVATION trigger if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: @@ -655,19 +660,19 @@ class routerHBP(HBSYSTEM): if _dst_id in _system['OFF']: if _system['ACTIVE'] == True: _system['ACTIVE'] = False - self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) + logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) # Cancel the timer if we've enabled an "ON" type timeout if _system['TO_TYPE'] == 'ON': _system['TIMER'] = pkt_time - self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) + logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) # Reset the timer for the rule if _system['ACTIVE'] == False and _system['TO_TYPE'] == 'OFF': _system['TIMER'] = pkt_time + _system['TIMEOUT'] - self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) + logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) # Cancel the timer if we've enabled an "ON" type timeout if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: _system['TIMER'] = pkt_time - self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) + logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) # # END IN-BAND SIGNALLING @@ -705,7 +710,6 @@ if __name__ == '__main__': import sys import os import signal - from dmr_utils.utils import try_download, mk_id_dict # Change the current directory to the location of the application os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) @@ -732,51 +736,31 @@ if __name__ == '__main__': # Set up the signal handler def sig_handler(_signal, _frame): logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame, logger) + hblink_handler(_signal, _frame) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - - # ID ALIAS CREATION - # Download - if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True: - # Try updating peer aliases file - result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME']) - logger.info(result) - # Try updating subscriber aliases file - result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME']) - logger.info(result) - - # Make Dictionaries - peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE']) - if peer_ids: - logger.info('ID ALIAS MAPPER: peer_ids dictionary is available') - - subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE']) - if subscriber_ids: - logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available') - - talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE']) - if talkgroup_ids: - logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available') + + # Create the name-number mapping dictionaries + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) # Build the routing rules file BRIDGES = make_bridges('hb_confbridge_rules') # INITIALIZE THE REPORTING LOOP - report_server = config_reports(CONFIG, logger, confbridgeReportFactory) + report_server = config_reports(CONFIG, confbridgeReportFactory) # HBlink instance creation - logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'hb_confbridge.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - systems[system] = routerOBP(system, CONFIG, logger, report_server) + systems[system] = routerOBP(system, CONFIG, report_server) else: - systems[system] = routerHBP(system, CONFIG, logger, report_server) + systems[system] = routerHBP(system, CONFIG, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) @@ -793,5 +777,6 @@ if __name__ == '__main__': stream_trimmer_task = task.LoopingCall(stream_trimmer_loop) stream_trimmer = stream_trimmer_task.start(5) stream_trimmer.addErrback(loopingErrHandle) + reactor.run() diff --git a/hb_log.py b/hb_log.py index 9260618..f510777 100755 --- a/hb_log.py +++ b/hb_log.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # ############################################################################### -# Copyright (C) 2016 Cortney T. Buffington, N0MJS +# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ from logging.config import dictConfig # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS' -__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group' +__copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' __credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' __license__ = 'GNU GPLv3' __maintainer__ = 'Cort Buffington, N0MJS' @@ -83,13 +83,12 @@ def config_logging(_logger): 'formatter': 'syslog', } }, - 'loggers': { - _logger['LOG_NAME']: { - 'handlers': _logger['LOG_HANDLERS'].split(','), - 'level': _logger['LOG_LEVEL'], - 'propagate': True, - } - } + + 'root': { + 'handlers': _logger['LOG_HANDLERS'].split(','), + 'level': _logger['LOG_LEVEL'], + 'propagate': True, + }, }) return logging.getLogger(_logger['LOG_NAME']) \ No newline at end of file diff --git a/hb_parrot.py b/hb_parrot.py index 4f95d71..5d459cc 100755 --- a/hb_parrot.py +++ b/hb_parrot.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # ############################################################################### -# Copyright (C) 2016 Cortney T. Buffington, N0MJS (and Mike Zingman N4IRR) +# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS (and Mike Zingman N4IRR) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,13 +35,18 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main hblink module -from hblink import HBSYSTEM, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, build_reg_acl +from hblink import HBSYSTEM, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases from dmr_utils.utils import hex_str_3, int_id, get_alias from dmr_utils import decode, bptc, const import hb_config import hb_log import hb_const +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + + # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS and Mike Zingman, N4IRR' __copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group' @@ -55,8 +60,8 @@ __status__ = 'pre-alpha' class parrot(HBSYSTEM): - def __init__(self, _name, _config, _logger, _report): - HBSYSTEM.__init__(self, _name, _config, _logger, _report) + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -119,14 +124,14 @@ class parrot(HBSYSTEM): # Is this is a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): self.STATUS['RX_START'] = pkt_time - self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s', \ + logger.info('(%s) *CALL START* 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) # Final actions - Is this a voice terminator? if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS['RX_START'] - self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \ + logger.info('(%s) *CALL END* 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) self.CALL_DATA.append(_data) sleep(2) @@ -187,16 +192,13 @@ if __name__ == '__main__': # Set up the signal handler def sig_handler(_signal, _frame): logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame, logger) + hblink_handler(_signal, _frame) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - - # Build the Access Control List - REG_ACL = build_reg_acl('reg_acl', logger) # ID ALIAS CREATION # Download @@ -208,27 +210,21 @@ if __name__ == '__main__': result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME']) logger.info(result) - # Make Dictionaries - peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE']) - if peer_ids: - logger.info('ID ALIAS MAPPER: peer_ids dictionary is available') - - subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE']) - if subscriber_ids: - logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available') - - talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE']) - if talkgroup_ids: - logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available') + # Create the name-number mapping dictionaries + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) # INITIALIZE THE REPORTING LOOP - report_server = config_reports(CONFIG, logger, reportFactory) + report_server = config_reports(CONFIG, reportFactory) # HBlink instance creation - logger.info('HBlink \'hb_parrot.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'hb_parrot.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: - systems[system] = parrot(system, CONFIG, logger, report_server) + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + logger.critical('%s FATAL: Instance is mode \'OPENBRIDGE\', \n\t\t...Which would be tragic for parrot, since it carries multiple call\n\t\tstreams simultaneously. hb_parrot.py onlyl works with MMDVM-based systems', system) + sys.exit('hb_parrot.py cannot function with systems that are not MMDVM devices. System {} is configured as an OPENBRIDGE'.format(system)) + else: + systems[system] = HBSYSTEM(system, CONFIG, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) diff --git a/hblink.py b/hblink.py index e406ad9..a556587 100755 --- a/hblink.py +++ b/hblink.py @@ -49,12 +49,16 @@ from twisted.internet import reactor, task import hb_log import hb_config import hb_const as const -from dmr_utils.utils import int_id, hex_str_4 +from dmr_utils.utils import int_id, hex_str_4, try_download, mk_id_dict # Imports for the reporting server import cPickle as pickle from reporting_const import * +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS' __copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' @@ -70,28 +74,28 @@ systems = {} # Timed loop used for reporting HBP status # # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE -def config_reports(_config, _logger, _factory): +def config_reports(_config, _factory): if True: #_config['REPORTS']['REPORT']: def reporting_loop(_logger, _server): _logger.debug('Periodic reporting loop started') _server.send_config() - _logger.info('HBlink TCP reporting server configured') + logger.info('HBlink TCP reporting server configured') - report_server = _factory(_config, _logger) + report_server = _factory(_config) report_server.clients = [] reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server) - reporting = task.LoopingCall(reporting_loop, _logger, report_server) + reporting = task.LoopingCall(reporting_loop, logger, report_server) reporting.start(_config['REPORTS']['REPORT_INTERVAL']) return report_server # Shut ourselves down gracefully by disconnecting from the masters and peers. -def hblink_handler(_signal, _frame, _logger): +def hblink_handler(_signal, _frame): for system in systems: - _logger.info('SHUTDOWN: DE-REGISTER SYSTEM: %s', system) + logger.info('SHUTDOWN: DE-REGISTER SYSTEM: %s', system) systems[system].dereg() # Check a supplied ID against the ACL provided. Returns action (True|False) based @@ -110,17 +114,16 @@ def acl_check(_id, _acl): #************************************************ class OPENBRIDGE(DatagramProtocol): - def __init__(self, _name, _config, _logger, _report): + def __init__(self, _name, _config, _report): # Define a few shortcuts to make the rest of the class more readable self._CONFIG = _config self._system = _name - self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] self._laststrid = '' def dereg(self): - self._logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) + logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) def send_system(self, _packet): if _packet[:4] == 'DMRD': @@ -128,9 +131,9 @@ class OPENBRIDGE(DatagramProtocol): _packet += hmac_new(self._config['PASSPHRASE'],_packet,sha1).digest() self.transport.write(_packet, (self._config['TARGET_IP'], self._config['TARGET_PORT'])) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - # self._logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) + # logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet)) else: - self._logger.error('(%s) OpenBridge system was asked to send non DMRD packet', self._system) + logger.error('(%s) OpenBridge system was asked to send non DMRD packet', self._system) def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass @@ -138,7 +141,7 @@ class OPENBRIDGE(DatagramProtocol): def datagramReceived(self, _packet, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! - #self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_packet)) + #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] @@ -156,41 +159,41 @@ class OPENBRIDGE(DatagramProtocol): _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] - #self._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)) + #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 if _slot != 1: - self._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)) + 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 self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', self._system, int_id(_stream_id), int_id(_rf_src)) + logger.debug('(%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 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if self._laststrid != _stream_id: - self._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)) + 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 = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_rf_src)) + logger.debug('(%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 = _stream_id return if not acl_check(_dst_id, self._config['TG1_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _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: - self._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:])) + 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:])) #************************************************ @@ -198,11 +201,10 @@ class OPENBRIDGE(DatagramProtocol): #************************************************ class HBSYSTEM(DatagramProtocol): - def __init__(self, _name, _config, _logger, _report): + def __init__(self, _name, _config, _report): # Define a few shortcuts to make the rest of the class more readable self._CONFIG = _config self._system = _name - self._logger = _logger self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] self._laststrid = '' @@ -229,18 +231,18 @@ class HBSYSTEM(DatagramProtocol): # Aliased in __init__ to maintenance_loop if system is a master def master_maintenance_loop(self): - self._logger.debug('(%s) Master maintenance loop started', self._system) + logger.debug('(%s) Master maintenance loop started', self._system) for peer in self._peers: _this_peer = self._peers[peer] # Check to see if any of the peers have been quiet (no ping) longer than allowed if _this_peer['LAST_PING']+self._CONFIG['GLOBAL']['PING_TIME']*self._CONFIG['GLOBAL']['MAX_MISSED'] < time(): - self._logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) + logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) # Remove any timed out peers from the configuration del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] # Aliased in __init__ to maintenance_loop if system is a peer def peer_maintenance_loop(self): - self._logger.debug('(%s) Peer maintenance loop started', self._system) + logger.debug('(%s) Peer maintenance loop started', self._system) if self._stats['PING_OUTSTANDING']: self._stats['NUM_OUTSTANDING'] += 1 # If we're not connected, zero out the stats and send a login request RPTL @@ -251,32 +253,32 @@ class HBSYSTEM(DatagramProtocol): self._stats['PING_OUTSTANDING'] = False self._stats['CONNECTION'] = 'RPTL_SENT' self.send_master('RPTL'+self._config['RADIO_ID']) - self._logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) + logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) # If we are connected, sent a ping to the master and increment the counter if self._stats['CONNECTION'] == 'YES': self.send_master('RPTPING'+self._config['RADIO_ID']) - self._logger.debug('(%s) RPTPING Sent to Master. Total Sent: %s, Total Missed: %s, Currently Outstanding: %s', self._system, self._stats['PINGS_SENT'], self._stats['PINGS_SENT'] - self._stats['PINGS_ACKD'], self._stats['NUM_OUTSTANDING']) + logger.debug('(%s) RPTPING Sent to Master. Total Sent: %s, Total Missed: %s, Currently Outstanding: %s', self._system, self._stats['PINGS_SENT'], self._stats['PINGS_SENT'] - self._stats['PINGS_ACKD'], self._stats['NUM_OUTSTANDING']) self._stats['PINGS_SENT'] += 1 self._stats['PING_OUTSTANDING'] = True def send_peers(self, _packet): for _peer in self._peers: self.send_peer(_peer, _packet) - #self._logger.debug('(%s) Packet sent to peer %s', self._system, self._peers[_peer]['RADIO_ID']) + #logger.debug('(%s) Packet sent to peer %s', self._system, self._peers[_peer]['RADIO_ID']) def send_peer(self, _peer, _packet): if _packet[:4] == 'DMRD': _packet = _packet[:11] + _peer + _packet[15:] self.transport.write(_packet, self._peers[_peer]['SOCKADDR']) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - #self._logger.debug('(%s) TX Packet to %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet)) + #logger.debug('(%s) TX Packet to %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet)) def send_master(self, _packet): if _packet[:4] == 'DMRD': _packet = _packet[:11] + self._config['RADIO_ID'] + _packet[15:] self.transport.write(_packet, self._config['MASTER_SOCKADDR']) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - # self._logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) + # logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass @@ -284,16 +286,16 @@ class HBSYSTEM(DatagramProtocol): def master_dereg(self): for _peer in self._peers: self.send_peer(_peer, 'MSTCL'+_peer) - self._logger.info('(%s) De-Registration sent to Peer: %s (%s)', self._system, self._peers[_peer]['CALLSIGN'], self._peers[_peer]['RADIO_ID']) + logger.info('(%s) De-Registration sent to Peer: %s (%s)', self._system, self._peers[_peer]['CALLSIGN'], self._peers[_peer]['RADIO_ID']) def peer_dereg(self): self.send_master('RPTCL'+self._config['RADIO_ID']) - self._logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) + logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) # Aliased in __init__ to datagramReceived if system is a master def master_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! - # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) + # 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] @@ -312,39 +314,39 @@ class HBSYSTEM(DatagramProtocol): _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] - #self._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)) + #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)) # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', self._system, int_id(_stream_id), int_id(_rf_src)) + logger.debug('(%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 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_rf_src)) + logger.debug('(%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 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return @@ -355,7 +357,7 @@ class HBSYSTEM(DatagramProtocol): #self.send_peer(_peer, _data) self.send_peer(_peer, _data[:11] + _peer + _data[15:]) #self.send_peer(_peer, _data[:11] + self._config['RADIO_ID'] + _data[15:]) - #self._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)) + #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 @@ -390,14 +392,14 @@ class HBSYSTEM(DatagramProtocol): 'SOFTWARE_ID': '', 'PACKAGE_ID': '', }}) - self._logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._system, int_id(_peer_id), _sockaddr[0], _sockaddr[1]) + logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._system, int_id(_peer_id), _sockaddr[0], _sockaddr[1]) _salt_str = hex_str_4(self._peers[_peer_id]['SALT']) self.send_peer(_peer_id, 'RPTACK'+_salt_str) self._peers[_peer_id]['CONNECTION'] = 'CHALLENGE_SENT' - self._logger.info('(%s) Sent Challenge Response to %s for login: %s', self._system, int_id(_peer_id), self._peers[_peer_id]['SALT']) + 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('MSTNAK'+_peer_id, _sockaddr) - self._logger.warning('(%s) Invalid Login from Radio ID: %s Denied by Registation ACL', self._system, int_id(_peer_id)) + logger.warning('(%s) Invalid Login from Radio ID: %s Denied by Registation ACL', self._system, int_id(_peer_id)) elif _command == 'RPTK': # Repeater has answered our login challenge _peer_id = _data[4:8] @@ -412,14 +414,14 @@ class HBSYSTEM(DatagramProtocol): if _sent_hash == _calc_hash: _this_peer['CONNECTION'] = 'WAITING_CONFIG' self.send_peer(_peer_id, 'RPTACK'+_peer_id) - self._logger.info('(%s) Peer %s has completed the login exchange successfully', self._system, _this_peer['RADIO_ID']) + logger.info('(%s) Peer %s has completed the login exchange successfully', self._system, _this_peer['RADIO_ID']) else: - self._logger.info('(%s) Peer %s has FAILED the login exchange successfully', self._system, _this_peer['RADIO_ID']) + logger.info('(%s) Peer %s has FAILED the login exchange successfully', self._system, _this_peer['RADIO_ID']) self.transport.write('MSTNAK'+_peer_id, _sockaddr) del self._peers[_peer_id] else: self.transport.write('MSTNAK'+_peer_id, _sockaddr) - self._logger.warning('(%s) Login challenge from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) + 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 @@ -427,7 +429,7 @@ class HBSYSTEM(DatagramProtocol): if _peer_id in self._peers \ and self._peers[_peer_id]['CONNECTION'] == 'YES' \ and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: - self._logger.info('(%s) Peer is closing down: %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id)) + logger.info('(%s) Peer is closing down: %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id)) self.transport.write('MSTNAK'+_peer_id, _sockaddr) del self._peers[_peer_id] @@ -455,10 +457,10 @@ class HBSYSTEM(DatagramProtocol): _this_peer['PACKAGE_ID'] = _data[262:302] self.send_peer(_peer_id, 'RPTACK'+_peer_id) - self._logger.info('(%s) Peer %s (%s) has sent repeater configuration', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) + logger.info('(%s) Peer %s (%s) has sent repeater configuration', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) else: self.transport.write('MSTNAK'+_peer_id, _sockaddr) - self._logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) + 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] @@ -468,18 +470,18 @@ class HBSYSTEM(DatagramProtocol): self._peers[_peer_id]['PINGS_RECEIVED'] += 1 self._peers[_peer_id]['LAST_PING'] = time() self.send_peer(_peer_id, 'MSTPONG'+_peer_id) - self._logger.debug('(%s) Received and answered RPTPING from peer %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_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('MSTNAK'+_peer_id, _sockaddr) - self._logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) + logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) else: - self._logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) + logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) # Aliased in __init__ to datagramReceived if system is a peer def peer_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! - # self._logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data)) + # 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: @@ -497,39 +499,39 @@ class HBSYSTEM(DatagramProtocol): _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] - self._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)) + 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 != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL', self._system, int_id(_stream_id), int_id(_rf_src)) + logger.debug('(%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 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL', self._system, int_id(_stream_id), int_id(_rf_src)) + logger.debug('(%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 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): if self._laststrid != _stream_id: - self._logger.debug('(%s) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL', self._system, int_id(_stream_id), int_id(_dst_id)) + logger.debug('(%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 = _stream_id return @@ -540,14 +542,14 @@ class HBSYSTEM(DatagramProtocol): 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 - self._logger.warning('(%s) MSTNAK Received. Resetting connection to the Master.', self._system) + logger.warning('(%s) MSTNAK Received. Resetting connection to the Master.', self._system) self._stats['CONNECTION'] = 'NO' # Disconnect ourselves and re-register 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] - self._logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32)) + logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32)) _pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest() _pass_hash = bhex(_pass_hash) self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash) @@ -556,7 +558,7 @@ class HBSYSTEM(DatagramProtocol): 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 - self._logger.info('(%s) Repeater Authentication Accepted', self._system) + logger.info('(%s) Repeater Authentication Accepted', self._system) _config_packet = self._config['RADIO_ID']+\ self._config['CALLSIGN']+\ self._config['RX_FREQ']+\ @@ -575,35 +577,35 @@ class HBSYSTEM(DatagramProtocol): self.send_master('RPTC'+_config_packet) self._stats['CONNECTION'] = 'CONFIG-SENT' - self._logger.info('(%s) Repeater Configuration Sent', self._system) + logger.info('(%s) Repeater Configuration Sent', self._system) else: self._stats['CONNECTION'] = 'NO' - self._logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) + 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 - self._logger.info('(%s) Repeater Configuration Accepted', self._system) + logger.info('(%s) Repeater Configuration Accepted', self._system) if self._config['OPTIONS']: self.send_master('RPTO'+self._config['RADIO_ID']+self._config['OPTIONS']) self._stats['CONNECTION'] = 'OPTIONS-SENT' - self._logger.info('(%s) Sent options: (%s)', self._system, self._config['OPTIONS']) + logger.info('(%s) Sent options: (%s)', self._system, self._config['OPTIONS']) else: self._stats['CONNECTION'] = 'YES' - self._logger.info('(%s) Connection to Master Completed', self._system) + logger.info('(%s) Connection to Master Completed', self._system) else: self._stats['CONNECTION'] = 'NO' - self._logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) + 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 - self._logger.info('(%s) Repeater Options Accepted', self._system) + logger.info('(%s) Repeater Options Accepted', self._system) self._stats['CONNECTION'] = 'YES' - self._logger.info('(%s) Connection to Master Completed with options', self._system) + logger.info('(%s) Connection to Master Completed with options', self._system) else: self._stats['CONNECTION'] = 'NO' - self._logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) + 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] @@ -611,16 +613,16 @@ class HBSYSTEM(DatagramProtocol): self._stats['PING_OUTSTANDING'] = False self._stats['NUM_OUTSTANDING'] = 0 self._stats['PINGS_ACKD'] += 1 - self._logger.debug('(%s) MSTPONG Received. Pongs Since Connected: %s', self._system, self._stats['PINGS_ACKD']) + 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' - self._logger.info('(%s) MSTCL Recieved', self._system) + logger.info('(%s) MSTCL Recieved', self._system) else: - self._logger.error('(%s) Received an invalid command in packet: %s', self._system, ahex(_data)) + logger.error('(%s) Received an invalid command in packet: %s', self._system, ahex(_data)) # # Socket-based reporting section @@ -649,16 +651,15 @@ class report(NetstringReceiver): self._factory._logger.error('got unknown opcode') class reportFactory(Factory): - def __init__(self, config, logger): + def __init__(self, config): self._config = config - self._logger = logger def buildProtocol(self, addr): if (addr.host) in self._config['REPORTS']['REPORT_CLIENTS'] or '*' in self._config['REPORTS']['REPORT_CLIENTS']: - self._logger.debug('Permitting report server connection attempt from: %s:%s', addr.host, addr.port) + logger.debug('Permitting report server connection attempt from: %s:%s', addr.host, addr.port) return report(self) else: - self._logger.error('Invalid report server connection attempt from: %s:%s', addr.host, addr.port) + logger.error('Invalid report server connection attempt from: %s:%s', addr.host, addr.port) return None def send_clients(self, _message): @@ -670,6 +671,32 @@ class reportFactory(Factory): self.send_clients(REPORT_OPCODES['CONFIG_SND']+serialized) +# ID ALIAS CREATION +# Download +def mk_aliases(_config): + if _config['ALIASES']['TRY_DOWNLOAD'] == True: + # Try updating peer aliases file + result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE'], _config['ALIASES']['PEER_URL'], _config['ALIASES']['STALE_TIME']) + logger.info(result) + # Try updating subscriber aliases file + result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE'], _config['ALIASES']['SUBSCRIBER_URL'], _config['ALIASES']['STALE_TIME']) + logger.info(result) + + # Make Dictionaries + peer_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE']) + if peer_ids: + logger.info('ID ALIAS MAPPER: peer_ids dictionary is available') + + subscriber_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE']) + if subscriber_ids: + logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available') + + talkgroup_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['TGID_FILE']) + if talkgroup_ids: + logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available') + + return peer_ids, subscriber_ids, talkgroup_ids + #************************************************ # MAIN PROGRAM LOOP STARTS HERE #************************************************ @@ -707,7 +734,7 @@ if __name__ == '__main__': # Set up the signal handler def sig_handler(_signal, _frame): logger.info('SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame, logger) + hblink_handler(_signal, _frame) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() @@ -715,17 +742,19 @@ if __name__ == '__main__': for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) + # INITIALIZE THE REPORTING LOOP - report_server = config_reports(CONFIG, logger, reportFactory) + report_server = config_reports(CONFIG, reportFactory) # HBlink instance creation logger.info('HBlink \'HBlink.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - systems[system] = OPENBRIDGE(system, CONFIG, logger, report_server) + systems[system] = OPENBRIDGE(system, CONFIG, report_server) else: - systems[system] = HBSYSTEM(system, CONFIG, logger, report_server) + systems[system] = HBSYSTEM(system, CONFIG, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) From fc948004a538ca121f55bd56faa59306c21d0c17 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 24 Nov 2018 10:21:08 -0600 Subject: [PATCH 40/56] Update hb_log.py --- hb_log.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hb_log.py b/hb_log.py index f510777..41726c1 100755 --- a/hb_log.py +++ b/hb_log.py @@ -83,7 +83,6 @@ def config_logging(_logger): 'formatter': 'syslog', } }, - 'root': { 'handlers': _logger['LOG_HANDLERS'].split(','), 'level': _logger['LOG_LEVEL'], From 7f9073ac5dc5e30fa91083969748ea09eb8c2a29 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 25 Nov 2018 10:22:43 -0600 Subject: [PATCH 41/56] Improved stream idendification for ACL logging --- hblink.py | 96 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/hblink.py b/hblink.py index a556587..646103b 100755 --- a/hblink.py +++ b/hblink.py @@ -38,7 +38,7 @@ from hmac import new as hmac_new, compare_digest from time import time from bitstring import BitArray from importlib import import_module -import socket +from collections import deque # Twisted is pretty important, so I keep it separate from twisted.internet.protocol import DatagramProtocol, Factory, Protocol @@ -108,7 +108,6 @@ def acl_check(_id, _acl): return not _acl[0] - #************************************************ # OPENBRIDGE CLASS #************************************************ @@ -120,7 +119,7 @@ class OPENBRIDGE(DatagramProtocol): self._system = _name self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - self._laststrid = '' + self._laststrid = deque([], 10) def dereg(self): logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) @@ -169,25 +168,25 @@ class OPENBRIDGE(DatagramProtocol): # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): - if self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + 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 self._laststrid != _stream_id: + 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 = _stream_id + self._laststrid.append(_stream_id) return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): - if self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + 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 self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + 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 @@ -207,7 +206,8 @@ class HBSYSTEM(DatagramProtocol): self._system = _name self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - self._laststrid = '' + self._laststrid1 = '' + self._laststrid2 = '' # Define shortcuts and generic function names based on the type of system we are if self._config['MODE'] == 'MASTER': @@ -320,34 +320,40 @@ class HBSYSTEM(DatagramProtocol): if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if self._laststrid != _stream_id: - logger.debug('(%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 = _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)) + if _slot == 1: + self._laststrid1 = _stream_id + else: + self._laststrid2 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): - if self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + if self._laststrid1 != _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._laststrid1 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): - if self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + if self._laststrid2 != _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._laststrid2 = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid != _stream_id: - logger.debug('(%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 = _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)) + if _slot == 1: + self._laststrid1 = _stream_id + else: + self._laststrid2 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): - if self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + if self._laststrid1 != _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._laststrid1 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): - if self._laststrid != _stream_id: - logger.debug('(%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 = _stream_id + if self._laststrid2 != _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._laststrid2 = _stream_id return # The basic purpose of a master is to repeat to the peers @@ -506,33 +512,39 @@ class HBSYSTEM(DatagramProtocol): if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): if self._laststrid != _stream_id: logger.debug('(%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 = _stream_id + if _slot == 1: + self._laststrid1 = _stream_id + else: + self._laststrid2 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG1_ACL']): - if self._laststrid != _stream_id: + if self._laststrid1 != _stream_id: logger.debug('(%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 = _stream_id + self._laststrid1 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._CONFIG['GLOBAL']['TG2_ACL']): - if self._laststrid != _stream_id: + if self._laststrid2 != _stream_id: logger.debug('(%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 = _stream_id + self._laststrid2 = _stream_id return if self._config['USE_ACL']: if not acl_check(_rf_src, self._config['SUB_ACL']): if self._laststrid != _stream_id: logger.debug('(%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 = _stream_id + if _slot == 1: + self._laststrid1 = _stream_id + else: + self._laststrid2 = _stream_id return if _slot == 1 and not acl_check(_dst_id, self._config['TG1_ACL']): - if self._laststrid != _stream_id: + if self._laststrid1 != _stream_id: logger.debug('(%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 = _stream_id + self._laststrid1 = _stream_id return if _slot == 2 and not acl_check(_dst_id, self._config['TG2_ACL']): - if self._laststrid != _stream_id: + if self._laststrid2 != _stream_id: logger.debug('(%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 = _stream_id + self._laststrid2 = _stream_id return @@ -757,5 +769,5 @@ if __name__ == '__main__': systems[system] = HBSYSTEM(system, CONFIG, report_server) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - + reactor.run() From 21406c3f808c19e9fe7bcad58b63d7c3e67dc115 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Sun, 25 Nov 2018 10:23:58 -0600 Subject: [PATCH 42/56] fix logger bugs --- hblink.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hblink.py b/hblink.py index a556587..6d198fd 100755 --- a/hblink.py +++ b/hblink.py @@ -633,10 +633,10 @@ class report(NetstringReceiver): def connectionMade(self): self._factory.clients.append(self) - self._factory._logger.info('HBlink reporting client connected: %s', self.transport.getPeer()) + logger.info('HBlink reporting client connected: %s', self.transport.getPeer()) def connectionLost(self, reason): - self._factory._logger.info('HBlink reporting client disconnected: %s', self.transport.getPeer()) + logger.info('HBlink reporting client disconnected: %s', self.transport.getPeer()) self._factory.clients.remove(self) def stringReceived(self, data): @@ -645,10 +645,10 @@ class report(NetstringReceiver): def process_message(self, _message): opcode = _message[:1] if opcode == REPORT_OPCODES['CONFIG_REQ']: - self._factory._logger.info('HBlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer()) + logger.info('HBlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer()) self.send_config() else: - self._factory._logger.error('got unknown opcode') + logger.error('got unknown opcode') class reportFactory(Factory): def __init__(self, config): From 479eb5a9bb1c057c41aec55f93524b61252a8cae Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 25 Nov 2018 10:36:14 -0600 Subject: [PATCH 43/56] Added BSD style copyright notice --- hb_bridge_all.py | 3 ++- hb_confbridge.py | 3 ++- hb_parrot.py | 3 ++- hblink.py | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/hb_bridge_all.py b/hb_bridge_all.py index d0da717..472d172 100755 --- a/hb_bridge_all.py +++ b/hb_bridge_all.py @@ -228,6 +228,7 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = hb_log.config_logging(CONFIG['LOGGER']) + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018\n\tThe Founding Members of the K0USY Group. All rights reserved.\n') logger.debug('Logging system started, anything from here on gets logged') # Set up the signal handler @@ -248,7 +249,7 @@ if __name__ == '__main__': report_server = config_reports(CONFIG, reportFactory) # HBlink instance creation - logger.info('HBlink \'HBlink.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'hb_bridge_all.py\' -- SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': diff --git a/hb_confbridge.py b/hb_confbridge.py index 32b7005..6ffcaa7 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -731,6 +731,7 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = hb_log.config_logging(CONFIG['LOGGER']) + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018\n\tThe Founding Members of the K0USY Group. All rights reserved.\n') logger.debug('Logging system started, anything from here on gets logged') # Set up the signal handler @@ -754,7 +755,7 @@ if __name__ == '__main__': report_server = config_reports(CONFIG, confbridgeReportFactory) # HBlink instance creation - logger.info('HBlink \'hb_confbridge.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'hb_confbridge.py\' -- SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': diff --git a/hb_parrot.py b/hb_parrot.py index 5d459cc..5173adf 100755 --- a/hb_parrot.py +++ b/hb_parrot.py @@ -187,6 +187,7 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = hb_log.config_logging(CONFIG['LOGGER']) + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018\n\tThe Founding Members of the K0USY Group. All rights reserved.\n') logger.debug('Logging system started, anything from here on gets logged') # Set up the signal handler @@ -217,7 +218,7 @@ if __name__ == '__main__': report_server = config_reports(CONFIG, reportFactory) # HBlink instance creation - logger.info('HBlink \'hb_parrot.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'hb_parrot.py\' (c) 2017 Mike Zingman, N4IRR -- SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': diff --git a/hblink.py b/hblink.py index 646103b..934ff45 100755 --- a/hblink.py +++ b/hblink.py @@ -67,7 +67,6 @@ __license__ = 'GNU GPLv3' __maintainer__ = 'Cort Buffington, N0MJS' __email__ = 'n0mjs@me.com' - # Global variables used whether we are a module or __main__ systems = {} @@ -741,8 +740,9 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = hb_log.config_logging(CONFIG['LOGGER']) + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018\n\tThe Founding Members of the K0USY Group. All rights reserved.\n') logger.debug('Logging system started, anything from here on gets logged') - + # Set up the signal handler def sig_handler(_signal, _frame): logger.info('SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal)) @@ -760,7 +760,7 @@ if __name__ == '__main__': report_server = config_reports(CONFIG, reportFactory) # HBlink instance creation - logger.info('HBlink \'HBlink.py\' (c) 2016-2018 N0MJS & the K0USY Group - SYSTEM STARTING...') + logger.info('HBlink \'HBlink.py\' -- SYSTEM STARTING...') for system in CONFIG['SYSTEMS']: if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': From 9e5cc5311c24bf5b842db49e31052f9411c25936 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 25 Nov 2018 10:40:42 -0600 Subject: [PATCH 44/56] logging typo fixed --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 6ffcaa7..2d13b0f 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -736,7 +736,7 @@ if __name__ == '__main__': # Set up the signal handler def sig_handler(_signal, _frame): - logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal)) + logger.info('SHUTDOWN: CONFBRIDGE IS TERMINATING WITH SIGNAL %s', str(_signal)) hblink_handler(_signal, _frame) logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() From 8a6225bd8e0df40100481a4abb3c614bb261f87f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 25 Nov 2018 10:47:00 -0600 Subject: [PATCH 45/56] fix signals --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 2d13b0f..0fca13b 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -742,7 +742,7 @@ if __name__ == '__main__': reactor.stop() # Set signal handers so that we can gracefully exit if need be - for sig in [signal.SIGTERM, signal.SIGINT]: + for sig in [signal.SIGINT, signal.SIGTERM]: signal.signal(sig, sig_handler) # Create the name-number mapping dictionaries From 83692f037ca1747e15d9856958e8dd1855874b63 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 25 Nov 2018 15:07:55 -0600 Subject: [PATCH 46/56] Add maximum peer limit for master instances --- hb_config.py | 1 + hblink-SAMPLE.cfg | 5 ++++ hblink.py | 71 +++++++++++++++++++++++++---------------------- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/hb_config.py b/hb_config.py index d7024fc..eebf04f 100755 --- a/hb_config.py +++ b/hb_config.py @@ -202,6 +202,7 @@ def build_config(_config_file): 'MODE': config.get(section, 'MODE'), 'ENABLED': config.getboolean(section, 'ENABLED'), 'REPEAT': config.getboolean(section, 'REPEAT'), + 'MAX_PEERS': config.getint(section, 'MAX_PEERS'), 'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'), 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 4e08e91..46b40d0 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -142,12 +142,17 @@ TGID_ACL: PERMIT:ALL # and unused by anything else. # Repeat - if True, the master repeats traffic to peers, False, it does nothing. # +# MAX_PEERS -- maximun number of peers that may be connect to this master +# at any given time. This is very handy if you're allowing hotspots to +# connect, or using a limited computer like a Raspberry Pi. +# # ACLs: # See comments in the GLOBAL stanza [MASTER-1] MODE: MASTER ENABLED: True REPEAT: True +MAX_PEERS: 10 EXPORT_AMBE: False IP: PORT: 54000 diff --git a/hblink.py b/hblink.py index b03674d..1ff847a 100755 --- a/hblink.py +++ b/hblink.py @@ -370,41 +370,46 @@ class HBSYSTEM(DatagramProtocol): elif _command == 'RPTL': # RPTLogin -- a repeater wants to login _peer_id = _data[4:8] - # 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', - '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 = hex_str_4(self._peers[_peer_id]['SALT']) - self.send_peer(_peer_id, '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']) + # 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', + '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 = hex_str_4(self._peers[_peer_id]['SALT']) + self.send_peer(_peer_id, '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('MSTNAK'+_peer_id, _sockaddr) + logger.warning('(%s) Invalid Login from Radio ID: %s Denied by Registation ACL', self._system, int_id(_peer_id)) else: self.transport.write('MSTNAK'+_peer_id, _sockaddr) - logger.warning('(%s) Invalid Login from Radio ID: %s Denied by Registation ACL', self._system, int_id(_peer_id)) + 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] From bb4a8ff4d53d4f2e007076a88be2a7f092fefa95 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Sun, 25 Nov 2018 15:13:36 -0600 Subject: [PATCH 47/56] fix typo in peer limit check --- hblink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink.py b/hblink.py index 1ff847a..2068fcd 100755 --- a/hblink.py +++ b/hblink.py @@ -371,7 +371,7 @@ class HBSYSTEM(DatagramProtocol): 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']): + 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 From c45da1de0571dee67ee8a78ebbc2aabb6f485b63 Mon Sep 17 00:00:00 2001 From: Steve N4IRS Date: Mon, 26 Nov 2018 09:00:48 -0500 Subject: [PATCH 48/56] Fix typo in SUB_ACL of OB example --- hblink-SAMPLE.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 46b40d0..d0b5bc0 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -132,7 +132,7 @@ PASSPHRASE: password TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 USE_ACL: True -SUB_ACL: 1 +SUB_ACL: DENY:1 TGID_ACL: PERMIT:ALL # MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS From 17552507bb29d580b49c685345a488ab7294e353 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 26 Nov 2018 11:52:45 -0600 Subject: [PATCH 49/56] clean up --- hb_config.py | 18 ++++++++---------- hblink.py | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/hb_config.py b/hb_config.py index eebf04f..c3e3ef6 100755 --- a/hb_config.py +++ b/hb_config.py @@ -45,17 +45,17 @@ __email__ = 'n0mjs@me.com' def process_acls(_config): # Global registration ACL _config['GLOBAL']['REG_ACL'] = acl_build(_config['GLOBAL']['REG_ACL'], const.PEER_MAX) - + # Global subscriber and TGID ACLs for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: _config['GLOBAL'][acl] = acl_build(_config['GLOBAL'][acl], const.ID_MAX) - + # System level ACLs for system in _config['SYSTEMS']: # Registration ACLs (which make no sense for peer systems) if _config['SYSTEMS'][system]['MODE'] == 'MASTER': _config['SYSTEMS'][system]['REG_ACL'] = acl_build(_config['SYSTEMS'][system]['REG_ACL'], const.PEER_MAX) - + # Subscriber and TGID ACLs (valid for all system types) for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: _config['SYSTEMS'][system][acl] = acl_build(_config['SYSTEMS'][system][acl], const.ID_MAX) @@ -66,20 +66,20 @@ def process_acls(_config): def acl_build(_acl, _max): if not _acl: return(True, set((const.ID_MIN, _max))) - + acl = set() sections = _acl.split(':') - + if sections[0] == 'PERMIT': action = True else: action = False - + for entry in sections[1].split(','): if entry == 'ALL': acl.add((const.ID_MIN, _max)) break - + elif '-' in entry: start,end = entry.split('-') start,end = int(start), int(end) @@ -157,7 +157,6 @@ def build_config(_config_file): 'MODE': config.get(section, 'MODE'), 'ENABLED': config.getboolean(section, 'ENABLED'), 'LOOSE': config.getboolean(section, 'LOOSE'), - 'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'), 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), @@ -203,7 +202,6 @@ def build_config(_config_file): 'ENABLED': config.getboolean(section, 'ENABLED'), 'REPEAT': config.getboolean(section, 'REPEAT'), 'MAX_PEERS': config.getint(section, 'MAX_PEERS'), - 'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'), 'IP': gethostbyname(config.get(section, 'IP')), 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE'), @@ -273,4 +271,4 @@ if __name__ == '__main__': return _acl[0] return not _acl[0] - print acl_check('\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL']) \ No newline at end of file + print acl_check('\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL']) diff --git a/hblink.py b/hblink.py index 2068fcd..dccdeb7 100755 --- a/hblink.py +++ b/hblink.py @@ -234,7 +234,7 @@ class HBSYSTEM(DatagramProtocol): for peer in self._peers: _this_peer = self._peers[peer] # Check to see if any of the peers have been quiet (no ping) longer than allowed - if _this_peer['LAST_PING']+self._CONFIG['GLOBAL']['PING_TIME']*self._CONFIG['GLOBAL']['MAX_MISSED'] < time(): + if _this_peer['LAST_PING']+(self._CONFIG['GLOBAL']['PING_TIME']*self._CONFIG['GLOBAL']['MAX_MISSED']) < time(): logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) # Remove any timed out peers from the configuration del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] From 26137b898b29924334678a4507dd7630d7eaf45a Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Tue, 27 Nov 2018 11:21:46 -0600 Subject: [PATCH 50/56] Fixed master maintenance loop problem where I tried to delete an item from a dictionary I was iterating... silly me! --- hblink.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hblink.py b/hblink.py index dccdeb7..52cd6d8 100755 --- a/hblink.py +++ b/hblink.py @@ -231,13 +231,16 @@ class HBSYSTEM(DatagramProtocol): # Aliased in __init__ to maintenance_loop if system is a master def master_maintenance_loop(self): logger.debug('(%s) Master maintenance loop started', self._system) + remove_list = [] for peer in self._peers: _this_peer = self._peers[peer] # Check to see if any of the peers have been quiet (no ping) longer than allowed if _this_peer['LAST_PING']+(self._CONFIG['GLOBAL']['PING_TIME']*self._CONFIG['GLOBAL']['MAX_MISSED']) < time(): - logger.info('(%s) Peer %s (%s) has timed out', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) - # Remove any timed out peers from the configuration - del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] + remove_list.append(peer) + for peer in remove_list: + logger.info('(%s) Peer %s (%s) has timed out and is being removed', self._system, self._peers[peer]['CALLSIGN'], self._peers[peer]['RADIO_ID']) + # Remove any timed out peers from the configuration + del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer] # Aliased in __init__ to maintenance_loop if system is a peer def peer_maintenance_loop(self): @@ -483,7 +486,7 @@ class HBSYSTEM(DatagramProtocol): 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('MSTNAK'+_peer_id, _sockaddr) - logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) + logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id)) else: logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) From bf2a99ec0048cbba3179c28bb1614e85b8396ebe Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Tue, 27 Nov 2018 20:59:39 -0600 Subject: [PATCH 51/56] fix packet length to not confuse YSF2DMR when OBP is the source --- hb_confbridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 0fca13b..7dff8fa 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -367,7 +367,7 @@ class routerOBP(OPENBRIDGE): elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] dmrpkt = dmrbits.tobytes() - _tmp_data = _tmp_data + dmrpkt + _data[53:55] + _tmp_data = _tmp_data + dmrpkt + '\x00\x00' # Add two bytes of nothing since OBP doesn't include BER & RSSI bytes #_data[53:55] # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) From 17c4a03c0b74e2ed0d9bf9ee68e27c0ca8802600 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 28 Nov 2018 07:34:32 -0600 Subject: [PATCH 52/56] remove stream ID from target systems at call end --- hb_confbridge.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hb_confbridge.py b/hb_confbridge.py index 7dff8fa..dfcca47 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -287,6 +287,8 @@ class routerOBP(OPENBRIDGE): # Create a voice terminator packet (FULL LC) elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + removed = _target_status.pop(_stream_id) + logger.info('(%s) OpenBridge sourced call stream end, remove Stream ID from destination: System: %s, Stream ID: %s', self._system, _target['SYSTEM'], int_id(_stream_id)) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] From ce3ab6710b298bf9f41780f7187ef816d2b5cab6 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Wed, 28 Nov 2018 07:44:23 -0600 Subject: [PATCH 53/56] Comment out OBP outbound stream ID removal - mulitple terminators breaks this. --- hb_confbridge.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index dfcca47..89646b6 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -287,8 +287,8 @@ class routerOBP(OPENBRIDGE): # Create a voice terminator packet (FULL LC) elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] - removed = _target_status.pop(_stream_id) - logger.info('(%s) OpenBridge sourced call stream end, remove Stream ID from destination: System: %s, Stream ID: %s', self._system, _target['SYSTEM'], int_id(_stream_id)) + #removed = _target_status.pop(_stream_id) + #logger.debug('(%s) OpenBridge sourced call stream end, remove Stream ID from destination: System: %s, Stream ID: %s', self._system, _target['SYSTEM'], int_id(_stream_id)) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] @@ -533,6 +533,8 @@ class routerHBP(HBSYSTEM): # Create a voice terminator packet (FULL LC) elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + #removed = _target_status.pop(_stream_id) + #logger.debug('(%s) OpenBridge sourced call stream end, remove Stream ID from destination: System: %s, Stream ID: %s', self._system, _target['SYSTEM'], int_id(_stream_id)) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] From d0fef204cf33549fe85e799c2f2d727c13e553ed Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 28 Nov 2018 07:53:07 -0600 Subject: [PATCH 54/56] Don't mark properly ended OBP calls as timed out --- hb_confbridge.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 89646b6..7d44631 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -182,8 +182,12 @@ def stream_trimmer_loop(): if stream_id in systems[system].STATUS: _system = systems[system].STATUS[stream_id] _config = CONFIG['SYSTEMS'][system] - logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ - system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) + if systems[system].STATUS[stream_id]['REMOVE'] == True: + logger.info('(%s) *REMOVE ENDED* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ + system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) + else: + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ + system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) # self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) removed = systems[system].STATUS.pop(stream_id) else: @@ -210,6 +214,7 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TGID': _dst_id, + 'REMOVE': False } # If we can, use the LC from the voice header as to keep all options intact @@ -250,6 +255,7 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TGID': _dst_id, + 'REMOVE': False } # If we can, use the LC from the voice header as to keep all options intact if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: @@ -287,8 +293,7 @@ class routerOBP(OPENBRIDGE): # Create a voice terminator packet (FULL LC) elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] - #removed = _target_status.pop(_stream_id) - #logger.debug('(%s) OpenBridge sourced call stream end, remove Stream ID from destination: System: %s, Stream ID: %s', self._system, _target['SYSTEM'], int_id(_stream_id)) + _target_status[_stream_id]['REMOVE'] = True # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] @@ -496,6 +501,7 @@ class routerHBP(HBSYSTEM): 'CONTENTION':False, 'RFS': _rf_src, 'TGID': _dst_id, + 'REMOVE': False } # If we can, use the LC from the voice header as to keep all options intact if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD: @@ -533,8 +539,7 @@ class routerHBP(HBSYSTEM): # Create a voice terminator packet (FULL LC) elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM: dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] - #removed = _target_status.pop(_stream_id) - #logger.debug('(%s) OpenBridge sourced call stream end, remove Stream ID from destination: System: %s, Stream ID: %s', self._system, _target['SYSTEM'], int_id(_stream_id)) + _target_status[_stream_id]['REMOVE'] = True # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] From ff2818016d1269383f4697e6b00985a6ada632b5 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Thu, 29 Nov 2018 10:27:32 -0600 Subject: [PATCH 55/56] Lengthen OBP active queue length --- hb_confbridge.py | 2 +- hblink.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hb_confbridge.py b/hb_confbridge.py index 7d44631..73c142a 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.py @@ -183,7 +183,7 @@ def stream_trimmer_loop(): _system = systems[system].STATUS[stream_id] _config = CONFIG['SYSTEMS'][system] if systems[system].STATUS[stream_id]['REMOVE'] == True: - logger.info('(%s) *REMOVE ENDED* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ + logger.debug('(%s) *REMOVE ENDED* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ system, int_id(stream_id), get_alias(int_id(_system['RFS']), subscriber_ids), get_alias(int_id(_config['NETWORK_ID']), peer_ids), get_alias(int_id(_system['TGID']), talkgroup_ids), _system['LAST'] - _system['START']) else: logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %s', \ diff --git a/hblink.py b/hblink.py index 52cd6d8..33f25dc 100755 --- a/hblink.py +++ b/hblink.py @@ -118,7 +118,7 @@ class OPENBRIDGE(DatagramProtocol): self._system = _name self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] - self._laststrid = deque([], 10) + self._laststrid = deque([], 20) def dereg(self): logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) From bb65946a537240ef3c1da1d83ba59bf862b0943d Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Thu, 29 Nov 2018 13:54:51 -0600 Subject: [PATCH 56/56] added call type for voice CSBK --- hblink.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/hblink.py b/hblink.py index 33f25dc..fdd422a 100755 --- a/hblink.py +++ b/hblink.py @@ -153,7 +153,13 @@ class OPENBRIDGE(DatagramProtocol): _dst_id = _data[8:11] _bits = int_id(_data[15]) _slot = 2 if (_bits & 0x80) else 1 - _call_type = 'unit' if (_bits & 0x40) else 'group' + #_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] @@ -312,12 +318,17 @@ class HBSYSTEM(DatagramProtocol): _dst_id = _data[8:11] _bits = int_id(_data[15]) _slot = 2 if (_bits & 0x80) else 1 - _call_type = 'unit' if (_bits & 0x40) else 'group' + #_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)) - # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): @@ -508,7 +519,13 @@ class HBSYSTEM(DatagramProtocol): _dst_id = _data[8:11] _bits = int_id(_data[15]) _slot = 2 if (_bits & 0x80) else 1 - _call_type = 'unit' if (_bits & 0x40) else 'group' + #_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]