MANY CHANGES

Bridging works well. Backup and standard are consolidated to one
application, better documentation, bridging rules file greatly
simplified.
This commit is contained in:
Cort Buffington 2014-08-31 11:27:00 -05:00
parent 5e5719eb2e
commit 59224df788
4 changed files with 200 additions and 176 deletions

View File

@ -1,123 +0,0 @@
#!/usr/bin/env python
#
# This work is licensed under the Creative Commons Attribution-ShareAlike
# 3.0 Unported License.To view a copy of this license, visit
# http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
# Creative Commons, 444 Castro Street, Suite 900, Mountain View,
# California, 94041, USA.
# This is a sample application to bridge traffic between IPSC networks
from __future__ import print_function
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as h
import sys
from dmrlink import IPSC, NETWORK, networks, send_to_ipsc, dmr_nat, logger, hex_str_4, int_id
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
__version__ = '0.1b'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
__status__ = 'Beta'
# Import Bridging rules
# Note: A stanza *must* exist for any IPSC configured in the main
# configuration file. It can be empty, but it has to exist.
#
try:
from bridge_rules import RULES
logger.info('Bridge rules file found and rules imported')
except ImportError:
sys.exit('Bridging rules file not found or invalid')
# Import List of Bridges
# This is how we identify known bridges. If one of these is present
# and it's mode byte is set to bridge, we don't
#
try:
from known_bridges import BRIDGES
logger.info('Known bridges file found and bridge ID list imported ')
except ImportError:
logger.critical('(backup_bridge.py) NO BRIDGES FILE FOUND, INITIALIZING NULL')
BRIDGES = []
class bridgeIPSC(IPSC):
def __init__(self, *args, **kwargs):
IPSC.__init__(self, *args, **kwargs)
self.BRIDGE = False
self.ACTIVE_CALLS = []
logger.info('(%s) Initializing bridge status as: %s', self._network, self.BRIDGE)
def startProtocol(self):
IPSC.startProtocol(self)
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
def bridge_presence_loop(self):
_temp_bridge = True
for peer in BRIDGES:
_peer = hex_str_4(peer)
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
_temp_bridge = False
logger.debug('(%s) Peer %s is an active bridge', self._network, int_id(_peer))
if _peer == self._master['RADIO_ID'] \
and self._master['STATUS']['CONNECTED'] \
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
_temp_bridge = False
logger.debug('(%s) Master %s is an active bridge',self._network, int_id(_peer))
if self.BRIDGE != _temp_bridge:
logger.info('(%s) Changing bridge status to: %s', self._network, _temp_bridge )
self.BRIDGE = _temp_bridge
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
#
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
if _ts not in self.ACTIVE_CALLS:
self.ACTIVE_CALLS.append(_ts)
# send repeater wake up, but send them when a repeater is likely not TXing check time since end (see below)
if _end:
self.ACTIVE_CALLS.remove(_ts)
# flag the time here so we can test to see if the last call ended long enough ago to send a wake-up
# timer = time()
for rule in RULES[_network]['GROUP_VOICE']:
_target = rule['DST_NET']
# Matching for rules is against the Destination Group in the SOURCE packet (SRC_GROUP)
if rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and (self.BRIDGE == True or networks[_target].BRIDGE == True):
_tmp_data = _data
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
# Re-Write the destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
# Calculate and append the authentication hash for the target network... if necessary
if NETWORK[_target]['LOCAL']['AUTH_ENABLED']:
_tmp_data = self.hashed_packet(NETWORK[_target]['LOCAL']['AUTH_KEY'], _tmp_data)
# Send the packet to all peers in the target IPSC
send_to_ipsc(_target, _tmp_data)
if __name__ == '__main__':
logger.info('DMRlink \'bridge.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
networks[ipsc_network] = bridgeIPSC(ipsc_network)
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
reactor.run()

200
bridge.py
View File

@ -6,78 +6,200 @@
# Creative Commons, 444 Castro Street, Suite 900, Mountain View,
# California, 94041, USA.
# This is a sample application to bridge traffic between IPSC networks
# This is a sample application to bridge traffic between IPSC networks. it uses
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
# configuration files. Both files have their own documentation for use.
#
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
# determine which voice calls are bridged between IPSC networks and which are
# not.
#
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
# used when you want bridge.py to be "polite" or serve as a backup bridge. If
# a known bridge exists in either a source OR target IPSC network, then no
# bridging between those IPSC networks will take place. This behavior is
# dynamic and updates each keep-alive interval (main configuration file).
# For faster failover, configure a short keep-alive time and a low number of
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
# That gives a worst-case scenario of 15 seconds to fail over. Recovery will
# typically happen with a single "blip" in the transmission up to about 5
# seconds.
#
# While this file is listed as Beta status, K0USY Group depends on this code
# for the bridigng of it's many repeaters. We consider it reliable, but you
# get what you pay for... as usual, no guarantees.
from __future__ import print_function
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as h
import sys
from dmrlink import IPSC, NETWORK, networks, send_to_ipsc, dmr_nat, logger
from dmrlink import IPSC, NETWORK, networks, send_to_ipsc, dmr_nat, logger, hex_str_3, hex_str_4, int_id
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
__version__ = '0.2a'
__version__ = '0.2b'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
__status__ = 'Production'
__status__ = 'Beta'
NAT = 0
#NAT = '\x2f\x9b\x80'
# Notes and pieces of next steps...
# RPT_WAKE_UP = b'\x85' + NETWORK[_network]['LOCAL']['RADIO_ID] + b'\x00\x00\x00\x01' + b'\x01' + b'\x01'
# TS1 = 0, TS2 = 1
# Import Bridging rules
# Note: A stanza *must* exist for any IPSC configured in the main
# configuration file and listed as "active". It can be empty,
# but it has to exist.
#
try:
from bridge_rules import RULES
logger.info('Bridge rules file found and rules imported')
except ImportError:
sys.exit('Bridging rules file not found or invalid')
# Convert integer GROUP ID numbers from the config into hex strings
# we need to send in the actual data packets.
#
class bridgeIPSC(IPSC):
for _ipsc in RULES:
for _rule in RULES[_ipsc]['GROUP_VOICE']:
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
print()
# Import List of Bridges
# This is how we identify known bridges. If one of these is present
# and it's mode byte is set to bridge, we don't
#
try:
from known_bridges import BRIDGES
logger.info('Known bridges file found and bridge ID list imported ')
except ImportError:
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
BRIDGES = []
# The class methods we override, including __init__ will be different
# depending on whether or not there is a known_bridges.py file, and
# hence whether or not we will implement backup/polite bridging or
# not (standard bridging). So, we test to see if the known bridges
# data structure "BRIDGES" was imported and had entries or not. If
# it did not import or did not have any entries, standard bridging
# is used.
#
if BRIDGES:
logger.info('Initializing class methods for backup/polite bridging')
class bridgeIPSC(IPSC):
def __init__(self, *args, **kwargs):
IPSC.__init__(self, *args, **kwargs)
self.ACTIVE_CALLS = []
def __init__(self, *args, **kwargs):
IPSC.__init__(self, *args, **kwargs)
self.BRIDGE = False
self.ACTIVE_CALLS = []
logger.info('(%s) Initializing bridge status as: %s', self._network, self.BRIDGE)
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
def startProtocol(self):
IPSC.startProtocol(self)
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
#
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
if _ts not in self.ACTIVE_CALLS:
self.ACTIVE_CALLS.append(_ts)
# send repeater wake up, but send them when a repeater is likely not TXing check time since end (see below)
if _end:
self.ACTIVE_CALLS.remove(_ts)
# flag the time here so we can test to see if the last call ended long enough ago to send a wake-up
# timer = time()
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
# This is the backup/polite bridge maintenance loop
def bridge_presence_loop(self):
_temp_bridge = True
for peer in BRIDGES:
_peer = hex_str_4(peer)
for rule in RULES[_network]['GROUP_VOICE']:
# Matching for rules is against the Destination Group in the SOURCE packet (SRC_GROUP)
if rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts:
_tmp_data = _data
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
_temp_bridge = False
logger.debug('(%s) Peer %s is an active bridge', self._network, int_id(_peer))
if _peer == self._master['RADIO_ID'] \
and self._master['STATUS']['CONNECTED'] \
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
_temp_bridge = False
logger.debug('(%s) Master %s is an active bridge',self._network, int_id(_peer))
if self.BRIDGE != _temp_bridge:
logger.info('(%s) Changing bridge status to: %s', self._network, _temp_bridge )
self.BRIDGE = _temp_bridge
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
#
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
if _ts not in self.ACTIVE_CALLS:
self.ACTIVE_CALLS.append(_ts)
# send repeater wake up, but send them when a repeater is likely not TXing check time since end (see below)
if _end:
self.ACTIVE_CALLS.remove(_ts)
# flag the time here so we can test to see if the last call ended long enough ago to send a wake-up
# timer = time()
for rule in RULES[_network]['GROUP_VOICE']:
_target = rule['DST_NET']
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
# Re-Write the destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
# Matching for rules is against the Destination Group in the SOURCE packet (SRC_GROUP)
if rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and (self.BRIDGE == True or networks[_target].BRIDGE == True):
_tmp_data = _data
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
# Re-Write the destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
# Calculate and append the authentication hash for the target network... if necessary
if NETWORK[_target]['LOCAL']['AUTH_ENABLED']:
_tmp_data = self.hashed_packet(NETWORK[_target]['LOCAL']['AUTH_KEY'], _tmp_data)
# Send the packet to all peers in the target IPSC
send_to_ipsc(_target, _tmp_data)
else:
logger.info('Initializing class methods for standard bridging')
class bridgeIPSC(IPSC):
def __init__(self, *args, **kwargs):
IPSC.__init__(self, *args, **kwargs)
self.ACTIVE_CALLS = []
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
#
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
if _ts not in self.ACTIVE_CALLS:
self.ACTIVE_CALLS.append(_ts)
# send repeater wake up, but send them when a repeater is likely not TXing check time since end (see below)
if _end:
self.ACTIVE_CALLS.remove(_ts)
# flag the time here so we can test to see if the last call ended long enough ago to send a wake-up
# timer = time()
for rule in RULES[_network]['GROUP_VOICE']:
# Matching for rules is against the Destination Group in the SOURCE packet (SRC_GROUP)
if rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts:
_tmp_data = _data
_target = rule['DST_NET']
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
# Re-Write the destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
# Calculate and append the authentication hash for the target network... if necessary
if NETWORK[_target]['LOCAL']['AUTH_ENABLED']:
_tmp_data = self.hashed_packet(NETWORK[_target]['LOCAL']['AUTH_KEY'], _tmp_data)
# Send the packet to all peers in the target IPSC
send_to_ipsc(_target, _tmp_data)
# NAT doesn't work well... use at your own risk!
if NAT:
_tmp_data = dmr_nat(_tmp_data, _src_sub, NAT)
# Calculate and append the authentication hash for the target network... if necessary
if NETWORK[_target]['LOCAL']['AUTH_ENABLED']:
_tmp_data = self.hashed_packet(NETWORK[_target]['LOCAL']['AUTH_KEY'], _tmp_data)
# Send the packet to all peers in the target IPSC
send_to_ipsc(_target, _tmp_data)
if __name__ == '__main__':
logger.info('DMRlink \'bridge.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')

View File

@ -1,27 +1,31 @@
'''
The following is an example for your bridge_rules file. Note, all bridging is ONE-WAY!
Rules for an IPSC network indicate destination IPSC network for the Group ID specified
(allowing transcoding of the Group ID to a different value). Group IDs are specified
as hex strings.
(allowing transcoding of the Group ID to a different value). Group IDs used to be
hex strings, then a function was added to convert them, now that function has been
moved into the bridge.py (program file) to make this file as simple and easy as
possible
The IPSC name must match an IPSC name from dmrlink.cfg.
The IPSC name must match an IPSC name from dmrlink.cfg, and any IPSC network defined
as "active" in the dmrlink.cfg *MUST* have an entry here. It may be an empty entry,
but there must be one so that the data structure can be parsed.
The example below cross-patches TGID 1 on an IPSC network named "IPSC_FOO" with TGID 2
on an IPSC network named "IPSC_BAR".
on an IPSC network named "IPSC_BAR". Note, one entry must be made on EACH IPSC network
(IPSC_FOO and IPSC_BAR in this example) for bridging to occur in both directions.
THIS EXAMPLE WILL NOT WORK AS IT IS - YOU MUST SPECIFY NAMES AND GROUP IDS!!!
NOTE: Timeslot transcoding does not yet work (SRC_TS) and (DST_TS) are ignored
NOTES:
* Timeslot transcoding does not yet work (SRC_TS) and (DST_TS) are ignored.
* Only GROUP_VOICE is currently used by the bridge.py appication, the other
types are placeholders for when it does more.
'''
def id(_id):
# Create a 3 byte TGID or UID from an integer
return hex(_id)[2:].rjust(6,'0').decode('hex')
RULES = {
'IPSC_FOO': {
'GROUP_VOICE': [
{'SRC_GROUP': id(1), 'SRC_TS': 1, 'DST_NET': 'IPSC_BAR', 'DST_GROUP': id(2), 'DST_TS': 1},
{'SRC_GROUP': 1, 'SRC_TS': 1, 'DST_NET': 'IPSC_BAR', 'DST_GROUP': 2, 'DST_TS': 1},
# Repeat the above line for as many rules for this IPSC network as you want.
],
'PRIVATE_VOICE': [
@ -31,9 +35,9 @@ RULES = {
'PRIVATE_DATA': [
]
},
'IPSC_BAR:' {
'IPSC_BAR': {
'GROUP_VOICE': [
{'SRC_GROUP': id(2), 'SRC_TS': 1, 'DST_NET': 'IPSC_FOO', 'DST_GROUP': id(1), 'DST_TS': 1},
{'SRC_GROUP': 2, 'SRC_TS': 1, 'DST_NET': 'IPSC_FOO', 'DST_GROUP': 1, 'DST_TS': 1},
# Repeat the above line for as many rules for this IPSC network as you want.
],
'PRIVATE_VOICE': [
@ -43,4 +47,4 @@ RULES = {
'PRIVATE_DATA': [
]
}
}
}

View File

@ -1,5 +1,26 @@
'''
The following is an example for your "known_bridges" file. This is a
simple list (in python syntax) of integer DMR radios IDs of bridges
that we expect to encounter.
You should only add bridges that will be encountered - adding a bunch
of bridges just because will really slow things down, so don't do it.
Please note each line but the last must end in a comma. This is about
the only thing you can mess up... but I manage to bork that one every
3rd time or so I make updates, so watch out.
A bridge that is "encountered" means another bridge that might be in
the same IPSC network we're going to try to bridge for. This is useful
only in the case where we want to provide backup bridging service.
There are cases when you do NOT want to use this feature -- say for
example if one IPSC has two bridges but they're bridging different
talkgroups.
'''
BRIDGES = [
123456,
234567,
345678
]
]