diff --git a/hb_bridge_all.py b/hb_bridge_all.py index a5ed430..f927d23 100755 --- a/hb_bridge_all.py +++ b/hb_bridge_all.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 @@ -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 +from hblink import HBSYSTEM, 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 from acl import acl_check, acl_build @@ -55,7 +55,7 @@ import hb_const # 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' @@ -133,7 +133,7 @@ class bridgeallSYSTEM(HBSYSTEM): } } - def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + 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]) @@ -143,40 +143,40 @@ class bridgeallSYSTEM(HBSYSTEM): # 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(_radio_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(_radio_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(_radio_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(_radio_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 - self._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(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + 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) # 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', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + 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) # Mark status variables for use later self.STATUS[_slot]['RX_RFS'] = _rf_src @@ -195,26 +195,26 @@ class bridgeallSYSTEM(HBSYSTEM): # 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(_radio_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(_radio_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(_radio_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(_radio_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 @@ -318,6 +318,9 @@ if __name__ == '__main__': 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) diff --git a/hb_confbridge.py b/hb_confbridge.py index 2173695..3e068b9 100755 --- a/hb_confbridge.py +++ b/hb_confbridge.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 @@ -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 +from hblink import HBSYSTEM, 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 @@ -57,7 +57,7 @@ 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 Cortney T. Buffington, N0MJS and the K0USY Group' +__copyright__ = 'Copyright (c) 2016-2018PEER 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' @@ -234,7 +234,7 @@ class routerSYSTEM(HBSYSTEM): } } - def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + 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]) @@ -243,21 +243,21 @@ class routerSYSTEM(HBSYSTEM): # 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(_radio_id), int_id(_dst_id)) + 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']): - self._logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s REPEATER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_radio_id), int_id(_dst_id), _slot) + 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._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(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + 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(_radio_id), int_id(_rf_src), _slot, int_id(_dst_id))) + 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: @@ -358,10 +358,10 @@ class routerSYSTEM(HBSYSTEM): # 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', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + 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(_radio_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration)) + 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. @@ -515,6 +515,9 @@ if __name__ == '__main__': # 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 b8cdf64..628f6ac 100755 --- a/hb_config.py +++ b/hb_config.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 @@ -33,7 +33,7 @@ from socket import gethostbyname # 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' @@ -98,14 +98,16 @@ def build_config(_config_file): }) elif config.getboolean(section, 'ENABLED'): - if config.get(section, 'MODE') == 'CLIENT': + if config.get(section, 'MODE') == 'PEER': CONFIG['SYSTEMS'].update({section: { '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'), + 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE'), @@ -148,7 +150,7 @@ def build_config(_config_file): 'PASSPHRASE': config.get(section, 'PASSPHRASE'), 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME') }}) - CONFIG['SYSTEMS'][section].update({'CLIENTS': {}}) + CONFIG['SYSTEMS'][section].update({'PEERS': {}}) except ConfigParser.Error, err: print "Cannot parse configuration file. %s" %err diff --git a/hb_parrot.py b/hb_parrot.py index e1e0e93..f2dbdf2 100755 --- a/hb_parrot.py +++ b/hb_parrot.py @@ -109,7 +109,7 @@ class parrot(HBSYSTEM): } self.CALL_DATA = [] - def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + 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]) @@ -120,14 +120,14 @@ class parrot(HBSYSTEM): 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', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + 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', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + 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) logger.info('(%s) Playing back transmission from subscriber: %s', self._system, int_id(_rf_src)) diff --git a/hblink.py b/hblink.py index 25b94d4..c003c3f 100755 --- a/hblink.py +++ b/hblink.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 @@ -20,11 +20,11 @@ ''' This program does very little on it's own. It is intended to be used as a module -to build applcaitons on top of the HomeBrew Repeater Protocol. By itself, it -will only act as a client or master for the systems specified in its configuration +to build applications on top of the HomeBrew Repeater Protocol. By itself, it +will only act as a peer or master for the systems specified in its configuration file (usually hblink.cfg). It is ALWAYS best practice to ensure that this program -works stand-alone before troubleshooting any applicaitons that use it. It has -sufficient logging to be used standalone as a troubeshooting application. +works stand-alone before troubleshooting any applications that use it. It has +sufficient logging to be used standalone as a troubleshooting application. ''' from __future__ import print_function @@ -55,7 +55,7 @@ from reporting_const import * # 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' @@ -86,7 +86,7 @@ def config_reports(_config, _logger, _factory): return report_server -# Shut ourselves down gracefully by disconnecting from the masters and clients. +# Shut ourselves down gracefully by disconnecting from the masters and peers. def hblink_handler(_signal, _frame, _logger): for system in systems: _logger.info('SHUTDOWN: DE-REGISTER SYSTEM: %s', system) @@ -94,16 +94,16 @@ def hblink_handler(_signal, _frame, _logger): # Import subscriber registration ACL -# ACL may be a single list of subscriber IDs +# 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_acl(_reg_acl): - ACL = set() +def build_reg_acl(_reg_acl, _logger): + REG_ACL = set() try: - logger.info('Registration ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs') acl_file = import_module(_reg_acl) - sections = acl_file.ACL.split(':') - ACL_ACTION = sections[0] + _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(','): @@ -111,29 +111,29 @@ def build_acl(_reg_acl): start,end = entry.split('-') start,end = int(start), int(end) for id in range(start, end+1): - ACL.add(hex_str_4(id)) + REG_ACL.add(hex_str_4(id)) else: id = int(entry) - ACL.add(hex_str_4(id)) + REG_ACL.add(hex_str_4(id)) - logger.info('Registration ACL loaded: action "{}" for {:,} registration IDs'.format(ACL_ACTION, len(ACL))) + _logger.info('Registration ACL loaded: action "{}" for {:,} registration IDs'.format( REG_ACL_ACTION, len(REG_ACL))) except ImportError: - logger.info('Registration ACL file not found or invalid - all IDs are valid') - ACL_ACTION = 'NONE' + _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 ACL is used (PERMIT, DENY... or there isn't one) + # 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 ACL_ACTION == 'PERMIT': + if REG_ACL_ACTION == 'PERMIT': def allow_reg(_id): - if _id in ACL: + if _id in REG_ACL: return True else: return False - elif ACL_ACTION == 'DENY': + elif REG_ACL_ACTION == 'DENY': def allow_reg(_id): - if _id not in ACL: + if _id not in REG_ACL: return True else: return False @@ -141,7 +141,7 @@ def build_acl(_reg_acl): def allow_reg(_id): return True - return ACL + return REG_ACL #************************************************ # AMBE CLASS: Used to parse out AMBE and send to gateway @@ -156,7 +156,7 @@ class AMBE: self._exp_ip = self._CONFIG['AMBE']['EXPORT_IP'] self._exp_port = self._CONFIG['AMBE']['EXPORT_PORT'] - def parseAMBE(self, _client, _data): + def parseAMBE(self, _peer, _data): _seq = int_id(_data[4:5]) _srcID = int_id(_data[5:8]) _dstID = int_id(_data[8:11]) @@ -168,7 +168,7 @@ class AMBE: _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', - _client, _seq, _srcID, _dstID, _rptID, _bits, _slot, _callType, _frameType, _voiceSeq, _streamID ) + _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:])) @@ -196,42 +196,42 @@ class HBSYSTEM(DatagramProtocol): # Define shortcuts and generic function names based on the type of system we are if self._config['MODE'] == 'MASTER': - self._clients = self._CONFIG['SYSTEMS'][self._system]['CLIENTS'] - self.send_system = self.send_clients + 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'] == 'CLIENT': + elif self._config['MODE'] == 'PEER': self._stats = self._config['STATS'] self.send_system = self.send_master - self.maintenance_loop = self.client_maintenance_loop - self.datagramReceived = self.client_datagramReceived - self.dereg = self.client_dereg + 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 clients. Run every 'PING_TIME' seconds + # 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) - for client in self._clients: - _this_client = self._clients[client] - # Check to see if any of the clients have been quiet (no ping) longer than allowed - if _this_client['LAST_PING']+self._CONFIG['GLOBAL']['PING_TIME']*self._CONFIG['GLOBAL']['MAX_MISSED'] < time(): - self._logger.info('(%s) Client %s (%s) has timed out', self._system, _this_client['CALLSIGN'], _this_client['RADIO_ID']) - # Remove any timed out clients from the configuration - del self._CONFIG['SYSTEMS'][self._system]['CLIENTS'][client] + 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']) + # 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 client - def client_maintenance_loop(self): - self._logger.debug('(%s) Client maintenance loop started', self._system) + # 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 @@ -250,53 +250,50 @@ class HBSYSTEM(DatagramProtocol): self._stats['PINGS_SENT'] += 1 self._stats['PING_OUTSTANDING'] = True - def send_clients(self, _packet): - for _client in self._clients: - self.send_client(_client, _packet) - #self._logger.debug('(%s) Packet sent to client %s', self._system, self._clients[_client]['RADIO_ID']) + 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']) - def send_client(self, _client, _packet): - _ip = self._clients[_client]['IP'] - _port = self._clients[_client]['PORT'] + def send_peer(self, _peer, _packet): if _packet[:4] == 'DMRD': - _packet = _packet[:11] + _client + _packet[15:] - self.transport.write(_packet, (_ip, _port)) + _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._clients[_client]['RADIO_ID'], self._clients[_client]['IP'], self._clients[_client]['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_IP'], self._config['MASTER_PORT'])) + 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, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + 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 _client in self._clients: - self.send_client(_client, 'MSTCL'+_client) - self._logger.info('(%s) De-Registration sent to Client: %s (%s)', self._system, self._clients[_client]['CALLSIGN'], self._clients[_client]['RADIO_ID']) + 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 client_dereg(self): + def peer_dereg(self): self.send_master('RPTCL'+self._config['RADIO_ID']) - self._logger.info('(%s) De-Registeration sent to Master: %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) + 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, (_host, _port)): + def master_datagramReceived(self, _data, _sockaddr): # Keep This Line Commented Unless HEAVILY Debugging! - # self._logger.debug('(%s) RX packet from %s:%s -- %s', self._system, _host, _port, ahex(_data)) + # 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 - _radio_id = _data[11:15] - if _radio_id in self._clients \ - and self._clients[_radio_id]['CONNECTION'] == 'YES' \ - and self._clients[_radio_id]['IP'] == _host \ - and self._clients[_radio_id]['PORT'] == _port: + _peer_id = _data[11:15] + if _peer_id in self._peers \ + and self._peers[_peer_id]['CONNECTION'] == 'YES' \ + and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: _seq = _data[4] _rf_src = _data[5:8] _dst_id = _data[8:11] @@ -312,29 +309,30 @@ class HBSYSTEM(DatagramProtocol): if self._config['EXPORT_AMBE']: self._ambe.parseAMBE(self._system, _data) - # The basic purpose of a master is to repeat to the clients + # The basic purpose of a master is to repeat to the peers if self._config['REPEAT'] == True: - for _client in self._clients: - if _client != _radio_id: - #self.send_client(_client, _data) - self.send_client(_client, _data[:11] + _client + _data[15:]) - #self.send_client(_client, _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 client: %s (%s) [Stream ID: %s]', self._system, _slot, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id), int_id(_dst_id), self._clients[_client]['CALLSIGN'], int_id(_client), int_id(_stream_id)) + for _peer in self._peers: + if _peer != _peer_id: + #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)) # Userland actions -- typically this is the function you subclass for an application - self.dmrd_received(_radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) + 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 - _radio_id = _data[4:8] - if allow_reg(_radio_id): # Future check here for valid Radio ID - self._clients.update({_radio_id: { # Build the configuration data strcuture for the client + _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 'CONNECTION': 'RPTL-RECEIVED', 'PINGS_RECEIVED': 0, 'LAST_PING': time(), - 'IP': _host, - 'PORT': _port, + 'SOCKADDR': _sockaddr, + 'IP': _sockaddr[0], + 'PORT': _sockaddr[1], 'SALT': randint(0,0xFFFFFFFF), - 'RADIO_ID': str(int(ahex(_radio_id), 16)), + 'RADIO_ID': str(int(ahex(_peer_id), 16)), 'CALLSIGN': '', 'RX_FREQ': '', 'TX_FREQ': '', @@ -350,108 +348,104 @@ class HBSYSTEM(DatagramProtocol): 'SOFTWARE_ID': '', 'PACKAGE_ID': '', }}) - self._logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._system, int_id(_radio_id), _host, _port) - _salt_str = hex_str_4(self._clients[_radio_id]['SALT']) - self.send_client(_radio_id, 'RPTACK'+_salt_str) - self._clients[_radio_id]['CONNECTION'] = 'CHALLENGE_SENT' - self._logger.info('(%s) Sent Challenge Response to %s for login: %s', self._system, int_id(_radio_id), self._clients[_radio_id]['SALT']) + self._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']) else: - self.transport.write('MSTNAK'+_radio_id, (_host, _port)) - self._logger.warning('(%s) Invalid Login from Radio ID: %s Denied by Registation ACL', self._system, int_id(_radio_id)) + 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)) elif _command == 'RPTK': # Repeater has answered our login challenge - _radio_id = _data[4:8] - if _radio_id in self._clients \ - and self._clients[_radio_id]['CONNECTION'] == 'CHALLENGE_SENT' \ - and self._clients[_radio_id]['IP'] == _host \ - and self._clients[_radio_id]['PORT'] == _port: - _this_client = self._clients[_radio_id] - _this_client['LAST_PING'] = time() + _peer_id = _data[4:8] + if _peer_id in self._peers \ + and self._peers[_peer_id]['CONNECTION'] == 'CHALLENGE_SENT' \ + and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: + _this_peer = self._peers[_peer_id] + _this_peer['LAST_PING'] = time() _sent_hash = _data[8:] - _salt_str = hex_str_4(_this_client['SALT']) + _salt_str = hex_str_4(_this_peer['SALT']) _calc_hash = bhex(sha256(_salt_str+self._config['PASSPHRASE']).hexdigest()) if _sent_hash == _calc_hash: - _this_client['CONNECTION'] = 'WAITING_CONFIG' - self.send_client(_radio_id, 'RPTACK'+_radio_id) - self._logger.info('(%s) Client %s has completed the login exchange successfully', self._system, _this_client['RADIO_ID']) + _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']) else: - self._logger.info('(%s) Client %s has FAILED the login exchange successfully', self._system, _this_client['RADIO_ID']) - self.transport.write('MSTNAK'+_radio_id, (_host, _port)) - del self._clients[_radio_id] + self._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'+_radio_id, (_host, _port)) - self._logger.warning('(%s) Login challenge from Radio ID that has not logged in: %s', self._system, int_id(_radio_id)) + 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)) elif _command == 'RPTC': # Repeater is sending it's configuraiton OR disconnecting if _data[:5] == 'RPTCL': # Disconnect command - _radio_id = _data[5:9] - if _radio_id in self._clients \ - and self._clients[_radio_id]['CONNECTION'] == 'YES' \ - and self._clients[_radio_id]['IP'] == _host \ - and self._clients[_radio_id]['PORT'] == _port: - self._logger.info('(%s) Client is closing down: %s (%s)', self._system, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id)) - self.transport.write('MSTNAK'+_radio_id, (_host, _port)) - del self._clients[_radio_id] + _peer_id = _data[5:9] + if _peer_id in self._peers \ + and self._peers[_peer_id]['CONNECTION'] == 'YES' \ + and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: + self._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] else: - _radio_id = _data[4:8] # Configure Command - if _radio_id in self._clients \ - and self._clients[_radio_id]['CONNECTION'] == 'WAITING_CONFIG' \ - and self._clients[_radio_id]['IP'] == _host \ - and self._clients[_radio_id]['PORT'] == _port: - _this_client = self._clients[_radio_id] - _this_client['CONNECTION'] = 'YES' - _this_client['LAST_PING'] = time() - _this_client['CALLSIGN'] = _data[8:16] - _this_client['RX_FREQ'] = _data[16:25] - _this_client['TX_FREQ'] = _data[25:34] - _this_client['TX_POWER'] = _data[34:36] - _this_client['COLORCODE'] = _data[36:38] - _this_client['LATITUDE'] = _data[38:46] - _this_client['LONGITUDE'] = _data[46:55] - _this_client['HEIGHT'] = _data[55:58] - _this_client['LOCATION'] = _data[58:78] - _this_client['DESCRIPTION'] = _data[78:97] - _this_client['SLOTS'] = _data[97:98] - _this_client['URL'] = _data[98:222] - _this_client['SOFTWARE_ID'] = _data[222:262] - _this_client['PACKAGE_ID'] = _data[262:302] + _peer_id = _data[4:8] # Configure Command + if _peer_id in self._peers \ + and self._peers[_peer_id]['CONNECTION'] == 'WAITING_CONFIG' \ + and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: + _this_peer = self._peers[_peer_id] + _this_peer['CONNECTION'] = 'YES' + _this_peer['LAST_PING'] = time() + _this_peer['CALLSIGN'] = _data[8:16] + _this_peer['RX_FREQ'] = _data[16:25] + _this_peer['TX_FREQ'] = _data[25:34] + _this_peer['TX_POWER'] = _data[34:36] + _this_peer['COLORCODE'] = _data[36:38] + _this_peer['LATITUDE'] = _data[38:46] + _this_peer['LONGITUDE'] = _data[46:55] + _this_peer['HEIGHT'] = _data[55:58] + _this_peer['LOCATION'] = _data[58:78] + _this_peer['DESCRIPTION'] = _data[78:97] + _this_peer['SLOTS'] = _data[97:98] + _this_peer['URL'] = _data[98:222] + _this_peer['SOFTWARE_ID'] = _data[222:262] + _this_peer['PACKAGE_ID'] = _data[262:302] - self.send_client(_radio_id, 'RPTACK'+_radio_id) - self._logger.info('(%s) Client %s (%s) has sent repeater configuration', self._system, _this_client['CALLSIGN'], _this_client['RADIO_ID']) + 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']) else: - self.transport.write('MSTNAK'+_radio_id, (_host, _port)) - self._logger.warning('(%s) Client info from Radio ID that has not logged in: %s', self._system, int_id(_radio_id)) + 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)) - elif _command == 'RPTP': # RPTPing -- client is pinging us - _radio_id = _data[7:11] - if _radio_id in self._clients \ - and self._clients[_radio_id]['CONNECTION'] == "YES" \ - and self._clients[_radio_id]['IP'] == _host \ - and self._clients[_radio_id]['PORT'] == _port: - self._clients[_radio_id]['PINGS_RECEIVED'] += 1 - self._clients[_radio_id]['LAST_PING'] = time() - self.send_client(_radio_id, 'MSTPONG'+_radio_id) - self._logger.debug('(%s) Received and answered RPTPING from client %s (%s)', self._system, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id)) + elif _command == 'RPTP': # RPTPing -- peer is pinging us + _peer_id = _data[7:11] + if _peer_id in self._peers \ + and self._peers[_peer_id]['CONNECTION'] == "YES" \ + and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: + self._peers[_peer_id]['PINGS_RECEIVED'] += 1 + self._peers[_peer_id]['LAST_PING'] = time() + self.send_peer(_peer_id, '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)) else: - self.transport.write('MSTNAK'+_radio_id, (_host, _port)) - self._logger.warning('(%s) Client info from Radio ID that has not logged in: %s', self._system, int_id(_radio_id)) + 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)) else: self._logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) - # Aliased in __init__ to datagramReceived if system is a client - def client_datagramReceived(self, _data, (_host, _port)): + # 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 -- %s', self._system, _host, _port, ahex(_data)) + # self._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_IP'] == _host and self._config['MASTER_PORT'] == _port: + if self._config['MASTER_SOCKADDR'] == _sockaddr: # Extract the command, which is various length, but only 4 significant characters _command = _data[:4] if _command == 'DMRD': # DMRData -- encapsulated DMR data frame - _radio_id = _data[11:15] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + _peer_id = _data[11:15] + if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation _seq = _data[4:5] _rf_src = _data[5:8] _dst_id = _data[8:11] @@ -468,11 +462,11 @@ class HBSYSTEM(DatagramProtocol): self._ambe.parseAMBE(self._system, _data) # Userland actions -- typically this is the function you subclass for an application - self.dmrd_received(_radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) + self.dmrd_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) elif _command == 'MSTN': # Actually MSTNAK -- a NACK from the master - _radio_id = _data[6:10] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + _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) self._stats['CONNECTION'] = 'NO' # Disconnect ourselves and re-register @@ -487,8 +481,8 @@ class HBSYSTEM(DatagramProtocol): self._stats['CONNECTION'] = 'AUTHENTICATED' elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge... - _radio_id = _data[6:10] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + _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) _config_packet = self._config['RADIO_ID']+\ self._config['CALLSIGN']+\ @@ -514,8 +508,8 @@ class HBSYSTEM(DatagramProtocol): self._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 - _radio_id = _data[6:10] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + _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) if self._config['OPTIONS']: self.send_master('RPTO'+self._config['RADIO_ID']+self._config['OPTIONS']) @@ -529,8 +523,8 @@ class HBSYSTEM(DatagramProtocol): self._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 - _radio_id = _data[6:10] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + _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) self._stats['CONNECTION'] = 'YES' self._logger.info('(%s) Connection to Master Completed with options', self._system) @@ -538,17 +532,17 @@ class HBSYSTEM(DatagramProtocol): self._stats['CONNECTION'] = 'NO' self._logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) - elif _command == 'MSTP': # Actually MSTPONG -- a reply to RPTPING (send by client) - _radio_id = _data[7:11] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + elif _command == 'MSTP': # Actually MSTPONG -- a reply to RPTPING (send by peer) + _peer_id = _data[7:11] + if self._config['LOOSE'] or _peer_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation self._stats['PING_OUTSTANDING'] = False self._stats['NUM_OUTSTANDING'] = 0 self._stats['PINGS_ACKD'] += 1 self._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 - _radio_id = _data[5:9] - if self._config['LOOSE'] or _radio_id == self._config['RADIO_ID']: # Validate the Radio_ID unless using loose validation + _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) @@ -648,8 +642,8 @@ if __name__ == '__main__': for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) - # Build the Access Control List - ACL = build_acl('reg_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, reportFactory) diff --git a/reg_acl-SAMPLE.py b/reg_acl-SAMPLE.py index 9e83034..9a9e701 100644 --- a/reg_acl-SAMPLE.py +++ b/reg_acl-SAMPLE.py @@ -8,4 +8,4 @@ # 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:1' +REG_ACL = 'DENY:1' diff --git a/hb_router.py b/retired/hb_router.py similarity index 96% rename from hb_router.py rename to retired/hb_router.py index 719454f..a3c0436 100755 --- a/hb_router.py +++ b/retired/hb_router.py @@ -227,7 +227,7 @@ class routerSYSTEM(HBSYSTEM): } } - def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + 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]) @@ -236,19 +236,19 @@ class routerSYSTEM(HBSYSTEM): # 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(_radio_id), int_id(_dst_id)) + 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']): - self._logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s REPEATER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_radio_id), int_id(_dst_id), _slot) + 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._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(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + 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 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: @@ -345,8 +345,8 @@ class routerSYSTEM(HBSYSTEM): # 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', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + 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) # # Begin in-band signalling for call end. This has nothign to do with routing traffic directly. diff --git a/hb_routing_rules-SAMPLE.py b/retired/hb_routing_rules-SAMPLE.py similarity index 100% rename from hb_routing_rules-SAMPLE.py rename to retired/hb_routing_rules-SAMPLE.py