From 4b9864671efa19323f4cb460367c355c11388dbb Mon Sep 17 00:00:00 2001 From: "Dan Srebnick (K2IE)" Date: Sat, 24 Oct 2020 20:53:48 -0400 Subject: [PATCH 1/3] Restore XLXPEER functionality --- config.py | 49 +++++++++++++++++++++++++++++++++++++++++++++-- hblink-SAMPLE.cfg | 39 ++++++++++++++++++++++++++++++++----- hblink.py | 48 +++++++++++++++++++++++++++++++++------------- 3 files changed, 116 insertions(+), 20 deletions(-) diff --git a/config.py b/config.py index e76fbf4..f86628c 100755 --- a/config.py +++ b/config.py @@ -182,7 +182,7 @@ def build_config(_config_file): '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'), - 'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]), + 'OPTIONS': bytes(config.get(section, 'OPTIONS'), 'utf-8'), 'USE_ACL': config.getboolean(section, 'USE_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), @@ -199,6 +199,52 @@ def build_config(_config_file): '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'), @@ -228,7 +274,6 @@ def build_config(_config_file): 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), - 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), 'USE_ACL': config.getboolean(section, 'USE_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_ACL'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 2a50767..a3b616c 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -120,9 +120,7 @@ STALE_DAYS: 7 # # ACLs: # OpenBridge does not 'register', so registration ACL is meaningless. -# Proper OpenBridge passes all traffic on TS1. -# HBlink can extend OPB to use both slots for unit calls only. -# Setting "BOTH_SLOTS" True ONLY affects unit traffic! +# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. # Otherwise ACLs work as described in the global stanza [OBP-1] MODE: OPENBRIDGE @@ -133,7 +131,6 @@ NETWORK_ID: 3129100 PASSPHRASE: password TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 -BOTH_SLOTS: True USE_ACL: True SUB_ACL: DENY:1 TGID_ACL: PERMIT:ALL @@ -209,4 +206,36 @@ OPTIONS: USE_ACL: True SUB_ACL: DENY:1 TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL \ No newline at end of file +TGID_TS2_ACL: PERMIT:ALL + +[XLX-1] +MODE: XLXPEER +ENABLED: True +LOOSE: True +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 f8ac097..7331b42 100755 --- a/hblink.py +++ b/hblink.py @@ -223,6 +223,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) @@ -282,6 +289,29 @@ 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 = 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 + 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.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): pass @@ -489,18 +519,6 @@ class HBSYSTEM(DatagramProtocol): self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id)) - elif _command == RPTO: - _peer_id = _data[4:8] - if _peer_id in self._peers \ - and self._peers[_peer_id]['CONNECTION'] == 'YES' \ - and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: - logger.info('(%s) Peer %s (%s) has send options: %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), _data[8:]) - self.transport.write(b''.join([RPTACK, _peer_id]), _sockaddr) - - elif _command == DMRA: - _peer_id = _data[4:8] - logger.info('(%s) Recieved DMR Talker Alias from peer %s, subscriber %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_rf_src)) - else: logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) @@ -630,7 +648,11 @@ 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'], 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: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) From 951f39f3db75aa4677a41b61350880adfbe85c05 Mon Sep 17 00:00:00 2001 From: "Dan Srebnick (K2IE)" Date: Sat, 24 Oct 2020 21:14:06 -0400 Subject: [PATCH 2/3] Re-add needed code removed when diffing to restore XLX --- config.py | 5 +++-- hblink-SAMPLE.cfg | 5 ++++- hblink.py | 13 +++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index f86628c..d59d4d6 100755 --- a/config.py +++ b/config.py @@ -182,8 +182,8 @@ def build_config(_config_file): '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'), - 'OPTIONS': bytes(config.get(section, 'OPTIONS'), 'utf-8'), - 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]), + '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') @@ -274,6 +274,7 @@ def build_config(_config_file): 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), + 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), 'USE_ACL': config.getboolean(section, 'USE_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_ACL'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index a3b616c..c2e789d 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -120,7 +120,9 @@ STALE_DAYS: 7 # # ACLs: # OpenBridge does not 'register', so registration ACL is meaningless. -# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. +# Proper OpenBridge passes all traffic on TS1. +# HBlink can extend OPB to use both slots for unit calls only. +# Setting "BOTH_SLOTS" True ONLY affects unit traffic! # Otherwise ACLs work as described in the global stanza [OBP-1] MODE: OPENBRIDGE @@ -131,6 +133,7 @@ NETWORK_ID: 3129100 PASSPHRASE: password TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 +BOTH_SLOTS: True USE_ACL: True SUB_ACL: DENY:1 TGID_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index 7331b42..7896396 100755 --- a/hblink.py +++ b/hblink.py @@ -519,6 +519,18 @@ class HBSYSTEM(DatagramProtocol): self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id)) + elif _command == RPTO: + _peer_id = _data[4:8] + if _peer_id in self._peers \ + and self._peers[_peer_id]['CONNECTION'] == 'YES' \ + and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: + logger.info('(%s) Peer %s (%s) has send options: %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), _data[8:]) + self.transport.write(b''.join([RPTACK, _peer_id]), _sockaddr) + + elif _command == DMRA: + _peer_id = _data[4:8] + logger.info('(%s) Recieved DMR Talker Alias from peer %s, subscriber %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_rf_src)) + else: logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) @@ -648,6 +660,7 @@ 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'], int(4000), self._config['MASTER_SOCKADDR']) From 948a2a7bbc0570f3cf3ccc6ce13c35513f4fcd95 Mon Sep 17 00:00:00 2001 From: "Dan Srebnick (K2IE)" Date: Sat, 24 Oct 2020 21:20:22 -0400 Subject: [PATCH 3/3] Cosmetics --- config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index d59d4d6..7600642 100755 --- a/config.py +++ b/config.py @@ -182,8 +182,8 @@ def build_config(_config_file): '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'), - 'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]), - 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]), + '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') @@ -274,7 +274,7 @@ def build_config(_config_file): 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), - 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), + 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), 'USE_ACL': config.getboolean(section, 'USE_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_ACL'),