INITIAL BRANCH COMMIT

0) CONFIGURATION FILES WILL NEED UPDATED TO CHANGE NAMING
CONFIGURATIONS!

1) Updated use of socket address instead of discrete IP/port. This will
be needed for socket address based validation and is faster… duh…
should have done this in the beginning.

2) Changed all references to “clients” as HBP systems to “peers”. This
sets the stage for having a peer_id and an originator_id for DMRD
packets.

3) Found and squashed a number of other random bugs, including the
registration ACL not working in confbridge or bridge_all.

4) router is now retired. confbridge is the future.
This commit is contained in:
Cort Buffington 2018-08-07 16:05:27 -06:00
parent d7f7288b81
commit d1af0560c4
8 changed files with 217 additions and 215 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# 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)

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# 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 <FROM> SUB: %s REPEATER: %s <TO> 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 <FROM> SUB: %s PEER: %s <TO> 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)

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# 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

View File

@ -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))

338
hblink.py
View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# 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)

View File

@ -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'

View File

@ -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 <FROM> SUB: %s REPEATER: %s <TO> 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 <FROM> SUB: %s PEER: %s <TO> 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.