Initial commit to branch

This commit is contained in:
Cort Buffington 2016-08-25 20:44:15 -05:00
parent c6af04f305
commit 9dff981ffc
4 changed files with 133 additions and 127 deletions

View File

@ -15,8 +15,7 @@ def build_config(_config_file):
CONFIG['GLOBAL'] = {}
CONFIG['LOGGER'] = {}
CONFIG['AMBE'] = {}
CONFIG['CLIENTS'] = {}
CONFIG['MASTERS'] = {}
CONFIG['SYSTEMS'] = {}
try:
for section in config.sections():
@ -48,7 +47,8 @@ def build_config(_config_file):
elif config.getboolean(section, 'ENABLED'):
# HomeBrew Client (Repeater) Configuration(s)
if config.get(section, 'MODE') == 'CLIENT':
CONFIG['CLIENTS'].update({section: {
CONFIG['SYSTEMS'].update({section: {
'MODE': config.get(section, 'MODE'),
'ENABLED': config.getboolean(section, 'ENABLED'),
'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'),
'IP': gethostbyname(config.get(section, 'IP')),
@ -72,7 +72,7 @@ def build_config(_config_file):
'SOFTWARE_ID': config.get(section, 'SOFTWARE_ID').ljust(40)[:40],
'PACKAGE_ID': config.get(section, 'PACKAGE_ID').ljust(40)[:40]
}})
CONFIG['CLIENTS'][section].update({'STATS': {
CONFIG['SYSTEMS'][section].update({'STATS': {
'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES
'PINGS_SENT': 0,
'PINGS_ACKD': 0,
@ -83,7 +83,8 @@ def build_config(_config_file):
elif config.get(section, 'MODE') == 'MASTER':
# HomeBrew Master Configuration
CONFIG['MASTERS'].update({section: {
CONFIG['SYSTEMS'].update({section: {
'MODE': config.get(section, 'MODE'),
'ENABLED': config.getboolean(section, 'ENABLED'),
'REPEAT': config.getboolean(section, 'REPEAT'),
'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'),
@ -91,11 +92,39 @@ def build_config(_config_file):
'PORT': config.getint(section, 'PORT'),
'PASSPHRASE': config.get(section, 'PASSPHRASE')
}})
CONFIG['MASTERS'][section].update({'CLIENTS': {}})
CONFIG['SYSTEMS'][section].update({'CLIENTS': {}})
except ConfigParser.Error, err:
# Very simple error reporting
print "Cannot parse configuration file. %s" %err
sys.exit('Could not parse configuration file, exiting...')
return CONFIG
return CONFIG
# Used to run this file direclty and print the config,
# which might be useful for debugging
if __name__ == '__main__':
import sys
import os
import argparse
from pprint import pprint
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg'
pprint(build_config(cli_args.CONFIG_FILE))

View File

@ -20,7 +20,7 @@ from twisted.internet import reactor
from twisted.internet import task
# Things we import from the main hblink module
from hblink import CONFIG, HBMASTER, HBCLIENT, logger, masters, clients, hex_str_3, int_id
from hblink import CONFIG, HBMASTER, HBCLIENT, logger, systems, hex_str_3, int_id
# Import Bridging rules
# Note: A stanza *must* exist for any MASTER or CLIENT configured in the main
@ -34,8 +34,8 @@ except ImportError:
# Convert integer GROUP ID numbers from the config into hex strings
# we need to send in the actual data packets.
for _system in RULES_FILE['MASTERS']:
for _rule in RULES_FILE['MASTERS'][_system]['GROUP_VOICE']:
for _system in RULES_FILE:
for _rule in RULES_FILE[_system]['GROUP_VOICE']:
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
_rule['SRC_TS'] = _rule['SRC_TS']
@ -44,27 +44,11 @@ for _system in RULES_FILE['MASTERS']:
_rule['ON'][i] = hex_str_3(_rule['ON'][i])
for i, e in enumerate(_rule['OFF']):
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
if _system not in CONFIG['MASTERS']:
sys.exit('ERROR: Routing rules found for MASTER system not configured in main configuration')
for _system in CONFIG['MASTERS']:
if _system not in RULES_FILE['MASTERS']:
sys.exit('ERROR: Routing rules not found for all MASTER systems configured')
for _system in RULES_FILE['CLIENTS']:
for _rule in RULES_FILE['CLIENTS'][_system]['GROUP_VOICE']:
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
_rule['SRC_TS'] = _rule['SRC_TS']
_rule['DST_TS'] = _rule['DST_TS']
for i, e in enumerate(_rule['ON']):
_rule['ON'][i] = hex_str_3(_rule['ON'][i])
for i, e in enumerate(_rule['OFF']):
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
if _system not in CONFIG['CLIENTS']:
sys.exit('ERROR: Routing rules found for CLIENT system not configured in main configuration')
for _system in CONFIG['CLIENTS']:
if _system not in RULES_FILE['CLIENTS']:
sys.exit('ERROR: Routing rules not found for all CLIENT systems configured')
if _system not in CONFIG['SYSTEMS']:
sys.exit('ERROR: Routing rules found for system not configured main configuration')
for _system in CONFIG['SYSTEMS']:
if _system not in RULES_FILE:
sys.exit('ERROR: Routing rules not found for all systems configured')
RULES = RULES_FILE
@ -84,15 +68,11 @@ __status__ = 'pre-alpha'
class routerMASTER(HBMASTER):
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data):
for rule in RULES['MASTERS'][self._master]['GROUP_VOICE']:
for rule in RULES[self._master]['GROUP_VOICE']:
_target = rule['DST_NET']
if _target in RULES['MASTERS']:
masters[_target].send_clients(_data)
logger.debug('(%s) Packet routed to master instance: %s', self._master, _target)
elif _target in RULES['CLIENTS']:
clients[_target].send_packet(_data)
logger.debug('(%s) Packet routed to client instance: %s', self._master, _target)
if _target in RULES:
systems[_target].send_system(_data)
logger.debug('(%s) Packet routed %s to system: %s', self._master, CONFIG[_target]['MODE'], _target)
else:
logger.debug('(%s) Packet router found no target for packet. Destination was: %s on target network %s', self._master, _dst_id, _target)
@ -102,15 +82,11 @@ class routerMASTER(HBMASTER):
class routerCLIENT(HBCLIENT):
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data):
for rule in RULES['CLIENTS'][self._client]['GROUP_VOICE']:
for rule in RULES[self._client]['GROUP_VOICE']:
_target = rule['DST_NET']
if _target in RULES['MASTERS']:
masters[_target].send_clients(_data)
logger.debug('(%s) Packet routed to master instance: %s', self._client, _target)
elif _target in RULES['CLIENTS']:
clients[_target].send_packet(_data)
logger.debug('(%s) Packet routed to client instance: %s', self._client, _target)
if _target in RULES:
system[_target].send_system(_data)
logger.debug('(%s) Packet routed to %s system: %s', self._client, CONFIG[_target]['MODE'], _target)
else:
logger.debug('(%s) Packet router found no target for packet. Destination was: %s on target network %s', self._client, _dst_id, _target)
@ -123,17 +99,14 @@ class routerCLIENT(HBCLIENT):
if __name__ == '__main__':
logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
# HBlink Master
for master in CONFIG['MASTERS']:
if CONFIG['MASTERS'][master]['ENABLED']:
masters[master] = routerMASTER(master)
reactor.listenUDP(CONFIG['MASTERS'][master]['PORT'], masters[master], interface=CONFIG['MASTERS'][master]['IP'])
logger.debug('MASTER instance created: %s, %s', master, masters[master])
for client in CONFIG['CLIENTS']:
if CONFIG['CLIENTS'][client]['ENABLED']:
clients[client] = routerCLIENT(client)
reactor.listenUDP(CONFIG['CLIENTS'][client]['PORT'], clients[client], interface=CONFIG['CLIENTS'][client]['IP'])
logger.debug('CLIENT instance created: %s, %s', client, clients[client])
# HBlink instance creation
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']:
if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER':
systems[system] = HBMASTER(system)
elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT':
systems[system] = HBCLIENT(system)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
reactor.run()

View File

@ -1,28 +1,28 @@
RULES = {
'MASTERS': {
'MASTER-1': {
'GROUP_HANGTIME': 5,
'GROUP_VOICE': [
{'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'CLIENT', 'DST_NET': 'REPEATER-1', 'DST_TS': 2, 'DST_GROUP': 2},
# When DMRD received on this MASTER, Time Slot 1, Talk Group 1; send to CLIENT-1 on Time Slot 2 Talk Group 2
# This rule is NOT enabled by default
# This rule can be enabled by transmitting on TGID 8
# This rule can be disabled by transmitting on TGID 9 or 10
# Repeat the above line for as many rules for this IPSC network as you want.
]
},
'MASTER-1': {
'GROUP_HANGTIME': 5,
'GROUP_VOICE': [
{'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'CLIENT', 'DST_NET': 'REPEATER-1', 'DST_TS': 2, 'DST_GROUP': 2},
# When DMRD received on this MASTER, Time Slot 1, Talk Group 1; send to CLIENT-1 on Time Slot 2 Talk Group 2
# This rule is NOT enabled by default
# This rule can be enabled by transmitting on TGID 8
# This rule can be disabled by transmitting on TGID 9 or 10
# Repeat the above line for as many rules for this IPSC network as you want.
]
},
'CLIENTS': {
'REPEATER-1': {
'GROUP_HANGTIME': 5,
'GROUP_VOICE': [
{'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'MASTER', 'DST_NET': 'MASTER-1', 'DST_TS': 2, 'DST_GROUP': 2},
# When DMRD received on this CLIENT, Time Slot 1, Talk Group 1; send to MASTER-1 on Time Slot 2 Talk Group 2
# This rule is NOT enabled by default
# This rule can be enabled by transmitting on TGID 8
# This rule can be disabled by transmitting on TGID 9 or 10
# Repeat the above line for as many rules for this IPSC network as you want.
]
},
'REPEATER-1': {
'GROUP_HANGTIME': 5,
'GROUP_VOICE': [
{'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'MASTER', 'DST_NET': 'MASTER-1', 'DST_TS': 2, 'DST_GROUP': 2},
# When DMRD received on this CLIENT, Time Slot 1, Talk Group 1; send to MASTER-1 on Time Slot 2 Talk Group 2
# This rule is NOT enabled by default
# This rule can be enabled by transmitting on TGID 8
# This rule can be disabled by transmitting on TGID 9 or 10
# Repeat the above line for as many rules for this IPSC network as you want.
]
},
}
}
if __name__ == '__main__':
from pprint import pprint
pprint(RULES)

View File

@ -47,8 +47,7 @@ __status__ = 'pre-alpha'
# Global variables used whether we are a module or __main__
masters = {}
clients = {}
systems = {}
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -80,17 +79,16 @@ logger.debug('Logging system started, anything from here on gets logged')
def handler(_signal, _frame):
logger.info('*** HBLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for client in clients:
this_client = clients[client]
this_client.send_packet('RPTCL'+CONFIG['CLIENTS'][client]['RADIO_ID'])
logger.info('(%s) De-Registering From the Master', client)
for master in masters:
this_master = masters[master]
for client in CONFIG['MASTERS'][master]['CLIENTS']:
this_master.send_packet(client, 'MSTCL'+client)
logger.info('(%s) Sending De-Registration to Client: %s', master, CONFIG['MASTERS'][master]['CLIENTS'][client]['RADIO_ID'])
for system in systems:
this_system = systems[system]
if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER':
for client in CONFIG['SYSTEMS'][system]['CLIENTS']:
this_system.send_client(client, 'MSTCL'+client)
logger.info('(%s) Sending De-Registration to Client: %s', system, CONFIG['SYSTEMS'][system]['CLIENTS'][client]['RADIO_ID'])
elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT':
this_system.send_master('RPTCL'+CONFIG['SYSTEMS'][system]['RADIO_ID'])
logger.info('(%s) De-Registering From the Master', system)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
@ -162,8 +160,9 @@ class HBMASTER(DatagramProtocol):
if len(args) == 1:
# Define a few shortcuts to make the rest of the class more readable
self._master = args[0]
self._config = CONFIG['MASTERS'][self._master]
self._clients = CONFIG['MASTERS'][self._master]['CLIENTS']
self._system = self._master
self._config = CONFIG['SYSTEMS'][self._master]
self._clients = CONFIG['SYSTEMS'][self._master]['CLIENTS']
# Configure for AMBE audio export if enabled
if self._config['EXPORT_AMBE']:
@ -186,19 +185,23 @@ class HBMASTER(DatagramProtocol):
if _this_client['LAST_PING']+CONFIG['GLOBAL']['PING_TIME']*CONFIG['GLOBAL']['MAX_MISSED'] < time():
logger.info('(%s) Client %s has timed out', self._master, _this_client['RADIO_ID'])
# Remove any timed out clients from the configuration
del CONFIG['MASTERS'][self._master]['CLIENTS'][client]
del CONFIG['SYSTEMS'][self._master]['CLIENTS'][client]
def send_clients(self, _packet):
for _client in self._clients:
self.send_packet(_client, _packet)
self.send_client(_client, _packet)
#logger.debug('(%s) Packet sent to client %s', self._master, self._clients[_client]['RADIO_ID'])
def send_packet(self, _client, _packet):
def send_client(self, _client, _packet):
_ip = self._clients[_client]['IP']
_port = self._clients[_client]['PORT']
self.transport.write(_packet, (_ip, _port))
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
#logger.debug('(%s) TX Packet to %s on port %s: %s', self._clients[_client]['RADIO_ID'], self._clients[_client]['IP'], self._clients[_client]['PORT'], h(_packet))
# Alias for other programs to use a common name to send a packet
# regardless of the system type (MASTER or CLIENT)
send_system = send_clients
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data):
pass
@ -229,7 +232,7 @@ class HBMASTER(DatagramProtocol):
if self._config['REPEAT'] == True:
for _client in self._clients:
if _client != _radio_id:
self.send_packet(_client, _data)
self.send_client(_client, _data)
logger.debug('(%s) Packet repeated to client: %s', self._master, int_id(_client))
# Userland actions -- typically this is the function you subclass for an application
@ -263,7 +266,7 @@ class HBMASTER(DatagramProtocol):
}})
logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._master, int_id(_radio_id), _host, _port)
_salt_str = hex_str_4(self._clients[_radio_id]['SALT'])
self.send_packet(_radio_id, 'RPTACK'+_salt_str)
self.send_client(_radio_id, 'RPTACK'+_salt_str)
self._clients[_radio_id]['CONNECTION'] = 'CHALLENGE_SENT'
logger.info('(%s) Sent Challenge Response to %s for login: %s', self._master, int_id(_radio_id), self._clients[_radio_id]['SALT'])
else:
@ -283,7 +286,7 @@ class HBMASTER(DatagramProtocol):
_calc_hash = a(sha256(_salt_str+self._config['PASSPHRASE']).hexdigest())
if _sent_hash == _calc_hash:
_this_client['CONNECTION'] = 'WAITING_CONFIG'
self.send_packet(_radio_id, 'RPTACK'+_radio_id)
self.send_client(_radio_id, 'RPTACK'+_radio_id)
logger.info('(%s) Client %s has completed the login exchange successfully', self._master, _this_client['RADIO_ID'])
else:
logger.info('(%s) Client %s has FAILED the login exchange successfully', self._master, _this_client['RADIO_ID'])
@ -327,7 +330,7 @@ class HBMASTER(DatagramProtocol):
_this_client['SOFTWARE_ID'] = _data[224:264]
_this_client['PACKAGE_ID'] = _data[264:304]
self.send_packet(_radio_id, 'RPTACK'+_radio_id)
self.send_client(_radio_id, 'RPTACK'+_radio_id)
logger.info('(%s) Client %s has sent repeater configuration', self._master, _this_client['RADIO_ID'])
else:
self.transport.write('MSTNAK'+_radio_id, (_host, _port))
@ -340,7 +343,7 @@ class HBMASTER(DatagramProtocol):
and self._clients[_radio_id]['IP'] == _host \
and self._clients[_radio_id]['PORT'] == _port:
self._clients[_radio_id]['LAST_PING'] = time()
self.send_packet(_radio_id, 'MSTPONG'+_radio_id)
self.send_client(_radio_id, 'MSTPONG'+_radio_id)
logger.debug('(%s) Received and answered RPTPING from client %s', self._master, int_id(_radio_id))
else:
self.transport.write('MSTNAK'+_radio_id, (_host, _port))
@ -358,7 +361,8 @@ class HBCLIENT(DatagramProtocol):
def __init__(self, *args, **kwargs):
if len(args) == 1:
self._client = args[0]
self._config = CONFIG['CLIENTS'][self._client]
self._system = self._client
self._config = CONFIG['SYSTEMS'][self._client]
self._stats = self._config['STATS']
# Configure for AMBE audio export if enabled
@ -381,19 +385,23 @@ class HBCLIENT(DatagramProtocol):
self._stats['PINGS_SENT'] = 0
self._stats['PINGS_ACKD'] = 0
self._stats['CONNECTION'] = 'RTPL_SENT'
self.send_packet('RPTL'+self._config['RADIO_ID'])
self.send_master('RPTL'+self._config['RADIO_ID'])
logger.info('(%s) Sending login request to master %s:%s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT'])
# If we are connected, sent a ping to the master and increment the counter
if self._stats['CONNECTION'] == 'YES':
self.send_packet('RPTPING'+self._config['RADIO_ID'])
self.send_master('RPTPING'+self._config['RADIO_ID'])
self._stats['PINGS_SENT'] += 1
logger.debug('(%s) RPTPING Sent to Master. Pings Since Connected: %s', self._client, self._stats['PINGS_SENT'])
def send_packet(self, _packet):
def send_master(self, _packet):
self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT']))
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
#logger.debug('(%s) TX Packet to %s:%s -- %s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT'], h(_packet))
# Alias for other programs to use a common name to send a packet
# regardless of the system type (MASTER or CLIENT)
send_system = send_master
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data):
pass
@ -433,7 +441,7 @@ class HBCLIENT(DatagramProtocol):
logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._client, int_id(_login_int32))
_pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest()
_pass_hash = a(_pass_hash)
self.send_packet('RPTK'+self._config['RADIO_ID']+_pass_hash)
self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash)
self._stats['CONNECTION'] = 'AUTHENTICATED'
elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge...
@ -455,7 +463,7 @@ class HBCLIENT(DatagramProtocol):
self._config['SOFTWARE_ID']+\
self._config['PACKAGE_ID']
self.send_packet('RPTC'+_config_packet)
self.send_master('RPTC'+_config_packet)
self._stats['CONNECTION'] = 'CONFIG-SENT'
logger.info('(%s) Repeater Configuration Sent', self._client)
else:
@ -492,18 +500,14 @@ class HBCLIENT(DatagramProtocol):
if __name__ == '__main__':
logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
# HBlink Master
for master in CONFIG['MASTERS']:
if CONFIG['MASTERS'][master]['ENABLED']:
masters[master] = HBMASTER(master)
reactor.listenUDP(CONFIG['MASTERS'][master]['PORT'], masters[master], interface=CONFIG['MASTERS'][master]['IP'])
logger.debug('MASTER instance created: %s, %s', master, masters[master])
# HBlink instance creation
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']:
if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER':
systems[system] = HBMASTER(system)
elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT':
systems[system] = HBCLIENT(system)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
# HBlink Client
for client in CONFIG['CLIENTS']:
if CONFIG['CLIENTS'][client]['ENABLED']:
clients[client] = HBCLIENT(client)
reactor.listenUDP(CONFIG['CLIENTS'][client]['PORT'], clients[client], interface=CONFIG['CLIENTS'][client]['IP'])
logger.debug('CLIENT instance created: %s, %s', client, clients[client])
reactor.run()