ACL Features Added

This makes hb_bridge_all a more capable program with multiple ingress
and egress ACLs for SID and TGID filtering. It’s not as simple as
“blacklist” and “whitelist” since MULTIPLE endpoints exist, unlike an
MMDVM that has only one connection.
This commit is contained in:
Cort Buffington 2018-02-13 18:59:44 -06:00
parent a62630211f
commit 119f43cd59
4 changed files with 222 additions and 8 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ Icon
hblink.cfg
hb_routing_rules.py
hb_confbridge_rules.py
hb_bridge_all_rules.py
*.config
*.json
*.pickle

76
acl.py Normal file
View File

@ -0,0 +1,76 @@
from dmr_utils.utils import int_id
# Lowest possible Subscirber and/or talkgroup IDs allowed by ETSI standard
ID_MIN = 1
ID_MAX = 16776415
# Checks the supplied ID against the ID given, and the ACL list, and the action
# Returns True if the ID should be allowed, False if it should not be
def acl_check(_id, _acl):
id = int_id(_id)
for entry in _acl[1]:
if entry[0] <= id <= entry[1]:
return _acl[0]
return not _acl[0]
def acl_build(_acl):
if not _acl:
return(True, set((ID_MIN, ID_MAX)))
acl = set()
sections = _acl.split(':')
if sections[0] == 'PERMIT':
action = True
else:
action = False
for entry in sections[1].split(','):
if entry == 'ALL':
acl.add((ID_MIN, ID_MAX))
break
elif '-' in entry:
start,end = entry.split('-')
start,end = int(start), int(end)
if (ID_MIN <= start <= ID_MAX) or (ID_MIN <= end <= ID_MAX):
acl.add((start, end))
else:
pass #logger message here
else:
id = int(entry)
if (ID_MIN <= id <= ID_MAX) or (ID_MIN <= id <= ID_MAX):
acl.add((id, id))
else:
pass #logger message here
return (action, acl)
if __name__ == '__main__':
from time import time
from pprint import pprint
ACL = {
'SUB': {
'K0USY': 'PERMIT:1-5,3120101,3120124'
},
'TGID': {
'GLOBAL': 'DENY:ALL',
'K0USY': 'PERMIT:1-5,3120,31201'
}
}
for acl in ACL:
if 'GLOBAL' not in ACL[acl]:
ACL[acl].update({'GLOBAL':'PERMIT:ALL'})
for acltype in ACL[acl]:
ACL[acl][acltype] = acl_build(ACL[acl][acltype])
pprint(ACL)
print
print(acl_check('\x00\x00\x01', ACL['TGID']['GLOBAL']))
print(acl_check('\x00\x00\x01', ACL['TGID']['K0USY']))

View File

@ -37,6 +37,7 @@ import sys
from bitarray import bitarray
from time import time
from importlib import import_module
from types import ModuleType
# Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import DatagramProtocol
@ -47,6 +48,7 @@ from twisted.internet import task
from hblink import HBSYSTEM, systems, int_id, hblink_handler
from dmr_utils.utils import hex_str_3, int_id, get_alias
from dmr_utils import decode, bptc, const
from acl import acl_check, acl_build
import hb_config
import hb_log
import hb_const
@ -63,6 +65,19 @@ __status__ = 'pre-alpha'
# Module gobal varaibles
# Import rules -- at this point, just ACLs
def import_rules(_rules):
try:
rules_file = import_module(_rules)
logger.info('Rules file found and bridges imported')
return rules_file
except ImportError:
logger.info('Rules file not found. Initializing defaults')
rules_file = ModuleType('rules_file')
rules_file.ACL = {'SID':{}, 'TGID':{}}
return rules_file
class bridgeallSYSTEM(HBSYSTEM):
def __init__(self, _name, _config, _logger):
@ -125,14 +140,79 @@ class bridgeallSYSTEM(HBSYSTEM):
if _call_type == 'group':
# Check for GLOBAL Subscriber ID ACL Match
if acl_check(_rf_src, ACL['SID']['GLOBAL']) == False:
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS GLOBAL ACL*** SID: %s HBP, Peer %s', self._system, int_id(_rf_src), int_id(_radio_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]) == False:
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS SYSTEM ACL*** SID: %s HBP, Peer %s', self._system, int_id(_rf_src), int_id(_radio_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']) == False:
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS GLOBAL ACL*** TGID: %s HBP, Peer %s', self._system, int_id(_dst_id), int_id(_radio_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]) == False:
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY INGRESS SYSTEM ACL*** TGID: %s HBP, Peer %s', self._system, int_id(_dst_id), int_id(_radio_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)
# Mark status variables for use later
self.STATUS[_slot]['RX_RFS'] = _rf_src
self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq
self.STATUS[_slot]['RX_TGID'] = _dst_id
self.STATUS[_slot]['RX_TIME'] = pkt_time
self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
for _target in self._CONFIG['SYSTEMS']:
if _target != self._system:
_target_status = systems[_target].STATUS
_target_system = self._CONFIG['SYSTEMS'][_target]
# Check for GLOBAL Subscriber ID ACL Match
if acl_check(_rf_src, ACL['SID']['GLOBAL']) == False:
if (_stream_id != _target_status[_slot]['TX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS GLOBAL ACL*** SID: %s HBP, Peer %s', _target, int_id(_rf_src), int_id(_radio_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]) == False:
if (_stream_id != _target_status[_slot]['TX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS SYSTEM ACL*** SID: %s HBP, Peer %s', _target, int_id(_rf_src), int_id(_radio_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']) == False:
if (_stream_id != _target_status[_slot]['TX_STREAM_ID']):
self._logger.warning('(%s) Group Voice Call ***REJECTED BY EGRESS GLOBAL ACL*** TGID: %s HBP, Peer %s', _target, int_id(_dst_id), int_id(_radio_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]) == 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))
_target_status[_slot]['TX_STREAM_ID'] = _stream_id
return
systems[_target].send_system(_data)
#self._logger.debug('(%s) Packet routed to system: %s', self._system, _target)
@ -142,13 +222,7 @@ class bridgeallSYSTEM(HBSYSTEM):
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)
# Mark status variables for use later
self.STATUS[_slot]['RX_RFS'] = _rf_src
self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq
self.STATUS[_slot]['RX_TGID'] = _dst_id
self.STATUS[_slot]['RX_TIME'] = pkt_time
self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
#************************************************
@ -156,7 +230,6 @@ class bridgeallSYSTEM(HBSYSTEM):
#************************************************
if __name__ == '__main__':
import argparse
import sys
import os
@ -219,6 +292,30 @@ if __name__ == '__main__':
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Import rules file
rules_file = import_rules('hb_bridge_all_rules')
# Create ACLs
ACL = rules_file.ACL
for acl_type in ACL:
if acl_type != 'SID' and acl_type != 'TGID':
sys.exit(('TERMINATE: SID or TGID stanzas not in ACL!!! Exiting to save you grief later'))
if 'GLOBAL' not in ACL[acl_type]:
ACL[acl_type].update({'GLOBAL':'PERMIT:ALL'})
for system_acl in ACL[acl_type]:
if system_acl not in CONFIG['SYSTEMS'] and system_acl != 'GLOBAL':
sys.exit(('TERMINATE: {} ACL configured for system {} that does not exist!!! Exiting to save you grief later'.format(acl_type, system_acl)))
ACL[acl_type][system_acl] = acl_build(ACL[acl_type][system_acl])
for system in CONFIG['SYSTEMS']:
for acl_type in ACL:
if system not in ACL[acl_type]:
logger.warning('No SID ACL for system %s - initializing \'PERMIT:ALL\'', system)
ACL[acl_type].update({system: acl_build('PERMIT:ALL')})
# HBlink instance creation
logger.info('HBlink \'hb_bridge_all.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')

View File

@ -0,0 +1,40 @@
# ACL Entries
#
# The 'action' May be PERMIT|DENY
# Each entry may be a single radio id, a hypenated range (e.g. 1-2999), or the string 'ALL'.
# if "ALL" is used, you may not include any other ranges or individual IDs.
# Format:
# ACL = 'action:id|start-end|,id|start-end,....'
#
# Sections exist for both TGIDs and Subscriber IDs.
# Sections exist for glboal actions, and per-system actions.
# ***FIRST MATCH EXITS***
# SID - Subscriber ID section.
# TGID - Talkgroup ID section.
#
# "GLOBAL" affects ALL systems
# "SYSTEM NAME" affects the system in quetion
# ACLs are applied both ingress AND egress
# If you omit GLOBAL or SYSTEM level ACLs, they will be initilzied
# automatically as "PERMIT:ALL"
#
# EXAMPLE:
# ACL = {
# 'SID': {
# 'K0USY': 'PERMIT:1-5,3120101,3120124'
# },
# 'TGID': {
# 'GLOBAL': 'PERMIT:ALL',
# 'K0USY': 'DENY:1-5,3120,31201'
# }
# }
ACL = {
'SID': {
'GLOBAL': 'PERMIT:ALL'
},
'TGID': {
'GLOBAL': 'PERMIT:ALL'
}
}