From 24c201ffc2f82e83bd20484b5e9aef4a45750a8e Mon Sep 17 00:00:00 2001 From: Andy Taylor Date: Wed, 19 Jun 2019 07:32:44 +0000 Subject: [PATCH 1/4] Add basic XLX support --- config.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++- hblink-SAMPLE.cfg | 32 +++++++++++++++++++++++++++++++ hblink.py | 7 +++++++ install.sh | 6 ++++++ requirements.txt | 1 - rules_SAMPLE.py | 2 ++ 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100755 install.sh diff --git a/config.py b/config.py index 5de0d69..3efe39f 100755 --- a/config.py +++ b/config.py @@ -198,7 +198,53 @@ def build_config(_config_file): 'LAST_PING_TX_TIME': 0, 'LAST_PING_ACK_TIME': 0, }}) - + + if config.get(section, 'MODE') == 'XLXPEER': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'LOOSE': config.getboolean(section, 'LOOSE'), + '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': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), + 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), + 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), + 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), + 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), + 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), + 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), + 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), + 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), + 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), + 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), + 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), + 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), + 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'XLXMODULE': config.getint(section, 'XLXMODULE'), + 'OPTIONS': '', + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') + }}) + CONFIG['SYSTEMS'][section].update({'XLXSTATS': { + 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES + 'CONNECTED': None, + 'PINGS_SENT': 0, + 'PINGS_ACKD': 0, + 'NUM_OUTSTANDING': 0, + 'PING_OUTSTANDING': False, + 'LAST_PING_TX_TIME': 0, + 'LAST_PING_ACK_TIME': 0, + }}) + elif config.get(section, 'MODE') == 'MASTER': CONFIG['SYSTEMS'].update({section: { 'MODE': config.get(section, 'MODE'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 6611016..a743373 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -207,3 +207,35 @@ USE_ACL: True SUB_ACL: DENY:1 TGID_TS1_ACL: PERMIT:ALL TGID_TS2_ACL: PERMIT:ALL + +[XLX-1] +MODE: XLXPEER +ENABLED: True +LOOSE: False +EXPORT_AMBE: False +IP: +PORT: 54002 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 62030 +PASSPHRASE: passw0rd +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +XLXMODULE: 4004 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index 2693981..e22bdb6 100755 --- a/hblink.py +++ b/hblink.py @@ -224,6 +224,13 @@ class HBSYSTEM(DatagramProtocol): self.datagramReceived = self.peer_datagramReceived self.dereg = self.peer_dereg + elif self._config['MODE'] == 'XLXPEER': + self._stats = self._config['XLXSTATS'] + self.send_system = self.send_master + self.maintenance_loop = self.peer_maintenance_loop + self.datagramReceived = self.peer_datagramReceived + self.dereg = self.peer_dereg + def startProtocol(self): # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds self._system_maintenance = task.LoopingCall(self.maintenance_loop) diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..c47b9db --- /dev/null +++ b/install.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +# Install the required support programs +apt-get install python3 python3-pip -y +pip3 install -r requirements.txt + diff --git a/requirements.txt b/requirements.txt index 7bb55eb..730b1c4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -python>=3.5.0 bitstring>=3.1.5 bitarray>=0.8.1 Twisted>=16.3.0 diff --git a/rules_SAMPLE.py b/rules_SAMPLE.py index 3781e57..a3c3701 100755 --- a/rules_SAMPLE.py +++ b/rules_SAMPLE.py @@ -15,7 +15,9 @@ configuration file. * SYSTEM - The name of the sytem as listed in the main hblink configuration file (e.g. hblink.cfg) This MUST be the exact same name as in the main config file!!! * TS - Timeslot used for matching traffic to this confernce bridge + XLX connections should *ALWAYS* use TS 2 only. * TGID - Talkgroup ID used for matching traffic to this conference bridge + XLX connections should *ALWAYS* use TG 9 only. * ON and OFF are LISTS of Talkgroup IDs used to trigger this system off and on. Even if you only want one (as shown in the ON example), it has to be in list format. None can be handled with an empty list, such as " 'ON': [] ". From aa7fea86bab59f1eea2d829997bd2d2da8112452 Mon Sep 17 00:00:00 2001 From: Andy Taylor Date: Wed, 19 Jun 2019 12:15:21 +0000 Subject: [PATCH 2/4] Add packet generation for XLX master module selection --- hblink.py | 26 ++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 27 insertions(+) diff --git a/hblink.py b/hblink.py index e22bdb6..42d2c2b 100755 --- a/hblink.py +++ b/hblink.py @@ -290,6 +290,28 @@ class HBSYSTEM(DatagramProtocol): # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) + def send_xlxmaster(self, radio, xlx, mastersock): + radio3 = int.from_bytes(radio, 'big').to_bytes(3, 'big') + radio4 = int.from_bytes(radio, 'big').to_bytes(4, 'big') + xlx3 = xlx.to_bytes(3, 'big') + streamid = bytearray.fromhex('6df88f36') + for packetnr in range(5): + if packetnr < 3: + # First 3 packets, voice start, stream type e1 + strmtype = 225 + payload = bytearray.fromhex('4f2e00b501ae3a001c40a0c1cc7dff57d75df5d5065026f82880bd616f13f185890000') + else: + # Last 2 packets, voice end, stream type e2 + strmtype = 226 + payload = bytearray.fromhex('4f410061011e3a781c30a061ccbdff57d75df5d2534425c02fe0b1216713e885ba0000') + packetnr1 = packetnr.to_bytes(1, 'big') + strmtype1 = strmtype.to_bytes(1, 'big') + _packet = b''.join([DMRD, packetnr1, radio3, xlx3, radio4, strmtype1, streamid, payload]) + self.transport.write(_packet, mastersock) + # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! + #logger.info('(%s) XLX Module Change Packet: %s', self._system, ahex(_packet)) + return + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass @@ -638,6 +660,10 @@ class HBSYSTEM(DatagramProtocol): self._stats['CONNECTION'] = 'YES' self._stats['CONNECTED'] = time() logger.info('(%s) Connection to Master Completed', self._system) + # If we are an XLX, send the XLX module request here. + if self._config['MODE'] == 'XLXPEER': + self.send_xlxmaster(self._config['RADIO_ID'], self._config['XLXMODULE'], self._config['MASTER_SOCKADDR']) + logger.info('(%s) Sending XLX Module request', self._system) else: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) diff --git a/requirements.txt b/requirements.txt index 730b1c4..3d17f35 100755 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ bitstring>=3.1.5 bitarray>=0.8.1 Twisted>=16.3.0 dmr_utils3>=0.1.19 +configparser>=3.0.0 From bce4e0177534cd0ba1ec123161dddbfc89a1ec2d Mon Sep 17 00:00:00 2001 From: Andy Taylor Date: Wed, 19 Jun 2019 14:49:52 +0000 Subject: [PATCH 3/4] Randomisation of the Stream ID --- hblink.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hblink.py b/hblink.py index 42d2c2b..f1f440a 100755 --- a/hblink.py +++ b/hblink.py @@ -33,7 +33,7 @@ from binascii import a2b_hex as bhex from random import randint from hashlib import sha256, sha1 from hmac import new as hmac_new, compare_digest -from time import time +from time import time, sleep from collections import deque # Twisted is pretty important, so I keep it separate @@ -294,7 +294,9 @@ class HBSYSTEM(DatagramProtocol): radio3 = int.from_bytes(radio, 'big').to_bytes(3, 'big') radio4 = int.from_bytes(radio, 'big').to_bytes(4, 'big') xlx3 = xlx.to_bytes(3, 'big') - streamid = bytearray.fromhex('6df88f36') + streamid = randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big') + # Wait for .5 secs for the XLX to log us in + sleep(.500) for packetnr in range(5): if packetnr < 3: # First 3 packets, voice start, stream type e1 @@ -307,9 +309,10 @@ class HBSYSTEM(DatagramProtocol): packetnr1 = packetnr.to_bytes(1, 'big') strmtype1 = strmtype.to_bytes(1, 'big') _packet = b''.join([DMRD, packetnr1, radio3, xlx3, radio4, strmtype1, streamid, payload]) + sleep(.100) self.transport.write(_packet, mastersock) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - #logger.info('(%s) XLX Module Change Packet: %s', self._system, ahex(_packet)) + #logger.debug('(%s) XLX Module Change Packet: %s', self._system, ahex(_packet)) return def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From 02c605dab75494659f43354d4e70a1a629d8c1fe Mon Sep 17 00:00:00 2001 From: Andy Taylor Date: Wed, 19 Jun 2019 15:03:29 +0000 Subject: [PATCH 4/4] Removed timers, add disconnect/reconnect to make the conntion more reliable --- hblink.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hblink.py b/hblink.py index f1f440a..9a61fdd 100755 --- a/hblink.py +++ b/hblink.py @@ -33,7 +33,7 @@ from binascii import a2b_hex as bhex from random import randint from hashlib import sha256, sha1 from hmac import new as hmac_new, compare_digest -from time import time, sleep +from time import time from collections import deque # Twisted is pretty important, so I keep it separate @@ -296,7 +296,6 @@ class HBSYSTEM(DatagramProtocol): xlx3 = xlx.to_bytes(3, 'big') streamid = randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big') # Wait for .5 secs for the XLX to log us in - sleep(.500) for packetnr in range(5): if packetnr < 3: # First 3 packets, voice start, stream type e1 @@ -309,7 +308,6 @@ class HBSYSTEM(DatagramProtocol): packetnr1 = packetnr.to_bytes(1, 'big') strmtype1 = strmtype.to_bytes(1, 'big') _packet = b''.join([DMRD, packetnr1, radio3, xlx3, radio4, strmtype1, streamid, payload]) - sleep(.100) self.transport.write(_packet, mastersock) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! #logger.debug('(%s) XLX Module Change Packet: %s', self._system, ahex(_packet)) @@ -665,6 +663,7 @@ class HBSYSTEM(DatagramProtocol): logger.info('(%s) Connection to Master Completed', self._system) # If we are an XLX, send the XLX module request here. if self._config['MODE'] == 'XLXPEER': + self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR']) self.send_xlxmaster(self._config['RADIO_ID'], self._config['XLXMODULE'], self._config['MASTER_SOCKADDR']) logger.info('(%s) Sending XLX Module request', self._system) else: