diff --git a/docker-configs/Dockerfile-hdstack b/docker-configs/Dockerfile-hdstack new file mode 100644 index 0000000..f3f96c7 --- /dev/null +++ b/docker-configs/Dockerfile-hdstack @@ -0,0 +1,17 @@ +FROM python:3.7-alpine + +COPY entrypoint-proxy /entrypoint + +RUN adduser -D -u 54000 radio && \ + apk update && \ + apk add git gcc musl-dev && \ + cd /opt && \ + git clone https://github.com/hacknix/freedmr && \ + cd /opt/freedmr && \ + pip install --no-cache-dir -r requirements.txt && \ + apk del git gcc musl-dev && \ + chown -R radio: /opt/freedmr + +USER radio + +ENTRYPOINT [ "/entrypoint" ] diff --git a/docker-configs/entrypoint-hdstack b/docker-configs/entrypoint-hdstack new file mode 100755 index 0000000..837948f --- /dev/null +++ b/docker-configs/entrypoint-hdstack @@ -0,0 +1,7 @@ +#!/bin/sh + +cd /opt/freedmr +python /opt/freedmr/hdstack/hotspot_proxy_v2.py & +python /opt/freedmr/bridge_master.py -c freedmr.cfg -r rules.py & +python /opt/freedmr/bridge_master.py -c hdstack/stack1.cfg -r rules.py & +python /opt/freedmr/bridge_master.py -c hdstack/stack2.cfg -r rules.py & diff --git a/hdstack/freebridge.cfg b/hdstack/freebridge.cfg new file mode 100644 index 0000000..9acf606 --- /dev/null +++ b/hdstack/freebridge.cfg @@ -0,0 +1,86 @@ +[GLOBAL] +PATH: ./ +PING_TIME: 10 +MAX_MISSED: 3 +USE_ACL: True +REG_ACL: PERMIT:ALL +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL +GEN_STAT_BRIDGES: False +ALLOW_NULL_PASSPHRASE: True +ANNOUNCEMENT_LANGUAGE: en_GB + +[REPORTS] +REPORT: True +REPORT_INTERVAL: 60 +REPORT_PORT: 4321 +REPORT_CLIENTS: * + +[LOGGER] +LOG_FILE: freedmr.log +LOG_HANDLERS: file-timed +LOG_LEVEL: INFO +LOG_NAME: FreeDMR + +[ALIASES] +TRY_DOWNLOAD: False +PATH: ./ +PEER_FILE: peer_ids.json +SUBSCRIBER_FILE: subscriber_ids.json +TGID_FILE: talkgroup_ids.json +PEER_URL: https://www.radioid.net/static/rptrs.json +SUBSCRIBER_URL: https://www.radioid.net/static/users.json +STALE_DAYS: 7 + +[MYSQL] +USE_MYSQL: False +USER: hblink +PASS: mypassword +DB: hblink +SERVER: 127.0.0.1 +PORT: 3306 +TABLE: repeaters + +[OBP-HDSTACK1] +MODE: OPENBRIDGE +ENABLED: True +IP: 127.0.0.1 +PORT: 70001 +NETWORK_ID: 3 +PASSPHRASE: internal +TARGET_IP: 127.0.0.1 +TARGET_PORT: 70001 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL +RELAX_CHECKS: True + +[OBP-HDSTACK2] +MODE: OPENBRIDGE +ENABLED: True +IP: 127.0.0.1 +PORT: 70002 +NETWORK_ID: 4 +PASSPHRASE: internal +TARGET_IP: 127.0.0.1 +TARGET_PORT: 70002 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL +RELAX_CHECKS: True + + +[OBP-FREEDMR] +MODE: OPENBRIDGE +ENABLED: False +IP: +PORT: +NETWORK_ID: +PASSPHRASE: +TARGET_IP: +TARGET_PORT: +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL +RELAX_CHECKS: True diff --git a/hdstack/hdstack1.cfg b/hdstack/hdstack1.cfg new file mode 100644 index 0000000..3111d67 --- /dev/null +++ b/hdstack/hdstack1.cfg @@ -0,0 +1,80 @@ +[GLOBAL] +PATH: ./ +PING_TIME: 10 +MAX_MISSED: 3 +USE_ACL: True +REG_ACL: PERMIT:ALL +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL +GEN_STAT_BRIDGES: False +ALLOW_NULL_PASSPHRASE: True +ANNOUNCEMENT_LANGUAGE: en_GB + +[REPORTS] +REPORT: True +REPORT_INTERVAL: 60 +REPORT_PORT: 4322 +REPORT_CLIENTS: * + +[LOGGER] +LOG_FILE: hdstack1.log +LOG_HANDLERS: file-timed +LOG_LEVEL: INFO +LOG_NAME: FreeDMR-hdstack1 + +[ALIASES] +TRY_DOWNLOAD: False +PATH: ./ +PEER_FILE: peer_ids.json +SUBSCRIBER_FILE: subscriber_ids.json +TGID_FILE: talkgroup_ids.json +PEER_URL: https://www.radioid.net/static/rptrs.json +SUBSCRIBER_URL: https://www.radioid.net/static/users.json +STALE_DAYS: 7 + +[MYSQL] +USE_MYSQL: False +USER: hblink +PASS: mypassword +DB: hblink +SERVER: 127.0.0.1 +PORT: 3306 +TABLE: repeaters + +[OBP-MAIN] +MODE: OPENBRIDGE +ENABLED: True +IP: 127.0.0.1 +PORT: 70001 +NETWORK_ID: 1 +PASSPHRASE: internal +TARGET_IP: 127.0.0.1 +TARGET_PORT: 70001 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL +RELAX_CHECKS: True + +[SYSTEM] +MODE: MASTER +ENABLED: True +REPEAT: True +MAX_PEERS: 1 +EXPORT_AMBE: False +IP: 127.0.0.1 +PORT: 54000 +PASSPHRASE: +GROUP_HANGTIME: 5 +USE_ACL: True +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL +DEFAULT_UA_TIMER: 999 +SINGLE_MODE: True +VOICE_IDENT: True +TS1_STATIC: +TS2_STATIC: +DEFAULT_REFLECTOR: 0 +GENERATOR: 150 diff --git a/hdstack/hdstack2.cfg b/hdstack/hdstack2.cfg new file mode 100644 index 0000000..6bda71c --- /dev/null +++ b/hdstack/hdstack2.cfg @@ -0,0 +1,80 @@ +[GLOBAL] +PATH: ./ +PING_TIME: 10 +MAX_MISSED: 3 +USE_ACL: True +REG_ACL: PERMIT:ALL +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL +GEN_STAT_BRIDGES: False +ALLOW_NULL_PASSPHRASE: True +ANNOUNCEMENT_LANGUAGE: en_GB + +[REPORTS] +REPORT: True +REPORT_INTERVAL: 60 +REPORT_PORT: 4323 +REPORT_CLIENTS: * + +[LOGGER] +LOG_FILE: hdstack2.log +LOG_HANDLERS: file-timed +LOG_LEVEL: INFO +LOG_NAME: FreeDMR-hdstack2 + +[ALIASES] +TRY_DOWNLOAD: False +PATH: ./ +PEER_FILE: peer_ids.json +SUBSCRIBER_FILE: subscriber_ids.json +TGID_FILE: talkgroup_ids.json +PEER_URL: https://www.radioid.net/static/rptrs.json +SUBSCRIBER_URL: https://www.radioid.net/static/users.json +STALE_DAYS: 7 + +[MYSQL] +USE_MYSQL: False +USER: hblink +PASS: mypassword +DB: hblink +SERVER: 127.0.0.1 +PORT: 3306 +TABLE: repeaters + +[OBP-MAIN] +MODE: OPENBRIDGE +ENABLED: True +IP: 127.0.0.1 +PORT: 70002 +NETWORK_ID: 2 +PASSPHRASE: internal +TARGET_IP: 127.0.0.1 +TARGET_PORT: 70002 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL +RELAX_CHECKS: True + +[SYSTEM] +MODE: MASTER +ENABLED: True +REPEAT: True +MAX_PEERS: 1 +EXPORT_AMBE: False +IP: 127.0.0.1 +PORT: 54150 +PASSPHRASE: +GROUP_HANGTIME: 5 +USE_ACL: True +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL +DEFAULT_UA_TIMER: 999 +SINGLE_MODE: True +VOICE_IDENT: True +TS1_STATIC: +TS2_STATIC: +DEFAULT_REFLECTOR: 0 +GENERATOR: 150 diff --git a/hdstack/hotspot_proxy_v2.py b/hdstack/hotspot_proxy_v2.py new file mode 100644 index 0000000..1e3948f --- /dev/null +++ b/hdstack/hotspot_proxy_v2.py @@ -0,0 +1,195 @@ +from twisted.internet.protocol import DatagramProtocol +from twisted.internet import reactor, task +from time import time +from resettabletimer import ResettableTimer +from dmr_utils3.utils import int_id +import random + +class Proxy(DatagramProtocol): + + def __init__(self,Master,ListenPort,connTrack,blackList,Timeout,Debug,DestportStart,DestPortEnd): + self.master = Master + self.connTrack = connTrack + self.peerTrack = {} + self.timeout = Timeout + self.debug = Debug + self.blackList = blackList + self.destPortStart = DestportStart + self.destPortEnd = DestPortEnd + self.numPorts = DestPortEnd - DestportStart + + + def reaper(self,_peer_id): + if self.debug: + print("dead",_peer_id) + self.transport.write(b'RPTCL'+_peer_id, ('127.0.0.1',self.peerTrack[_peer_id]['dport'])) + self.connTrack[self.peerTrack[_peer_id]['dport']] = False + del self.peerTrack[_peer_id] + + + def datagramReceived(self, data, addr): + + # HomeBrew Protocol Commands + DMRD = b'DMRD' + DMRA = b'DMRA' + MSTCL = b'MSTCL' + MSTNAK = b'MSTNAK' + MSTPONG = b'MSTPONG' + MSTN = b'MSTN' + MSTP = b'MSTP' + MSTC = b'MSTC' + RPTL = b'RPTL' + RPTPING = b'RPTPING' + RPTCL = b'RPTCL' + RPTL = b'RPTL' + RPTACK = b'RPTACK' + RPTK = b'RPTK' + RPTC = b'RPTC' + RPTP = b'RPTP' + RPTA = b'RPTA' + RPTO = b'RPTO' + + host,port = addr + + nowtime = time() + + Debug = self.debug + + #If the packet comes from the master + if host == self.master: + _command = data[:4] + + if _command == DMRD: + _peer_id = data[11:15] + elif _command == RPTA: + if data[6:10] in self.peerTrack: + _peer_id = data[6:10] + else: + _peer_id = self.connTrack[port] + elif _command == MSTN: + _peer_id = data[6:10] + self.peerTrack[_peer_id]['timer'].cancel() + self.reaper(_peer_id) + return + elif _command == MSTP: + _peer_id = data[7:11] + elif _command == MSTC: + _peer_id = data[5:9] + self.peerTrack[_peer_id]['timer'].cancel() + self.reaper(_peer_id) + return + + # _peer_id = self.connTrack[port] + if self.debug: + print(data) + if _peer_id and _peer_id in self.peerTrack: + self.transport.write(data,(self.peerTrack[_peer_id]['shost'],self.peerTrack[_peer_id]['sport'])) + #self.peerTrack[_peer_id]['timer'].reset() + return + + + + else: + _command = data[:4] + + if _command == DMRD: # DMRData -- encapsulated DMR data frame + _peer_id = data[11:15] + elif _command == DMRA: # DMRAlias -- Talker Alias information + _peer_id = _data[4:8] + elif _command == RPTL: # RPTLogin -- a repeater wants to login + _peer_id = data[4:8] + elif _command == RPTK: # Repeater has answered our login challenge + _peer_id = data[4:8] + elif _command == RPTC: # Repeater is sending it's configuraiton OR disconnecting + if data[:5] == RPTCL: # Disconnect command + _peer_id = data[5:9] + else: + _peer_id = data[4:8] # Configure Command + elif _command == RPTO: # options + _peer_id = data[4:8] + elif _command == RPTP: # RPTPing -- peer is pinging us + _peer_id = data[7:11] + else: + return + + if _peer_id in self.peerTrack: + _dport = self.peerTrack[_peer_id]['dport'] + self.peerTrack[_peer_id]['sport'] = port + self.peerTrack[_peer_id]['shost'] = host + self.transport.write(data, ('127.0.0.1',_dport)) + self.peerTrack[_peer_id]['timer'].reset() + if self.debug: + print(data) + return + else: + + if int_id(_peer_id) in self.blackList: + return + #for _dport in self.connTrack: + while True: + _dport = random.randint(1,(self.numPorts - 1)) + _dport = _dport + self.destPortStart + if not self.connTrack[_dport]: + break + self.connTrack[_dport] = _peer_id + self.peerTrack[_peer_id] = {} + self.peerTrack[_peer_id]['dport'] = _dport + self.peerTrack[_peer_id]['sport'] = port + self.peerTrack[_peer_id]['shost'] = host + self.peerTrack[_peer_id]['timer'] = ResettableTimer(self.timeout,self.reaper,[_peer_id]) + self.peerTrack[_peer_id]['timer'].start() + self.transport.write(data, (self.master,_dport)) + if self.debug: + print(data) + return + + +if __name__ == '__main__': + +#*** CONFIG HERE *** + + Master = "127.0.0.1" + ListenPort = 62031 + DestportStart = 54000 + DestPortEnd = 54300 + Timeout = 30 + Stats = False + Debug = False + BlackList = [1234567] + +#******************* + + + CONNTRACK = {} + + for port in range(DestportStart,DestPortEnd+1,1): + CONNTRACK[port] = False + + + reactor.listenUDP(ListenPort,Proxy(Master,ListenPort,CONNTRACK,BlackList,Timeout,Debug,DestportStart,DestPortEnd)) + + def loopingErrHandle(failure): + print('(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error innowtimed loop.\n {}'.format(failure)) + reactor.stop() + + def stats(): + count = 0 + nowtime = time() + for port in CONNTRACK: + if CONNTRACK[port]: + count = count+1 + + totalPorts = DestPortEnd - DestportStart + freePorts = totalPorts - count + + print("{} ports out of {} in use ({} free)".format(count,totalPorts,freePorts)) + + + + if Stats == True: + stats_task = task.LoopingCall(stats) + statsa = stats_task.start(30) + statsa.addErrback(loopingErrHandle) + + reactor.run() + diff --git a/hotspot_proxy_v2.py b/hotspot_proxy_v2.py index 76957c9..4b8bcf0 100644 --- a/hotspot_proxy_v2.py +++ b/hotspot_proxy_v2.py @@ -153,8 +153,8 @@ if __name__ == '__main__': DestportStart = 54000 DestPortEnd = 54100 Timeout = 30 - Stats = True - Debug = True + Stats = False + Debug = False BlackList = [1234567] #*******************