2021-01-04 16:42:48 -05:00
|
|
|
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
|
2021-06-30 20:36:04 -04:00
|
|
|
import ipaddress
|
2021-01-04 16:42:48 -05:00
|
|
|
|
2021-05-24 17:32:54 -04:00
|
|
|
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
|
|
|
|
__author__ = 'Simon Adlem - G7RZU'
|
|
|
|
__copyright__ = 'Copyright (c) Simon Adlem, G7RZU 2020,2021'
|
|
|
|
__credits__ = 'Jon Lee, G4TSN; Norman Williams, M6NBP'
|
|
|
|
__license__ = 'GNU GPLv3'
|
|
|
|
__maintainer__ = 'Simon Adlem G7RZU'
|
|
|
|
__email__ = 'simon@gb7fr.org.uk'
|
|
|
|
|
2021-06-30 20:36:04 -04:00
|
|
|
def IsIPv4Address(ip):
|
|
|
|
try:
|
|
|
|
ipaddress.IPv4Address(ip)
|
|
|
|
return True
|
|
|
|
except ValueError as errorCode:
|
|
|
|
pass
|
|
|
|
return False
|
|
|
|
|
|
|
|
def IsIPv6Address(ip):
|
|
|
|
try:
|
|
|
|
ipaddress.IPv6Address(ip)
|
|
|
|
return True
|
|
|
|
except ValueError as errorCode:
|
|
|
|
pass
|
|
|
|
|
2021-01-04 16:42:48 -05:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2021-01-05 12:18:02 -05:00
|
|
|
def reaper(self,_peer_id):
|
2021-01-04 16:42:48 -05:00
|
|
|
if self.debug:
|
|
|
|
print("dead",_peer_id)
|
2021-06-30 20:36:04 -04:00
|
|
|
self.transport.write(b'RPTCL'+_peer_id, (Master,self.peerTrack[_peer_id]['dport']))
|
2021-01-04 16:42:48 -05:00
|
|
|
self.connTrack[self.peerTrack[_peer_id]['dport']] = False
|
|
|
|
del self.peerTrack[_peer_id]
|
|
|
|
|
|
|
|
|
|
|
|
def datagramReceived(self, data, addr):
|
|
|
|
|
|
|
|
# HomeBrew Protocol Commands
|
|
|
|
DMRD = b'DMRD'
|
2021-01-22 16:16:16 -05:00
|
|
|
DMRA = b'DMRA'
|
2021-01-04 16:42:48 -05:00
|
|
|
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'
|
|
|
|
|
2021-06-30 20:36:04 -04:00
|
|
|
_peer_id = False
|
|
|
|
|
2021-01-04 16:42:48 -05:00
|
|
|
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]
|
2021-05-08 11:47:11 -04:00
|
|
|
self.peerTrack[_peer_id]['timer'].cancel()
|
2021-01-05 20:38:09 -05:00
|
|
|
self.reaper(_peer_id)
|
|
|
|
return
|
2021-01-04 16:42:48 -05:00
|
|
|
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)
|
2021-01-05 12:18:02 -05:00
|
|
|
if _peer_id and _peer_id in self.peerTrack:
|
2021-01-04 16:42:48 -05:00
|
|
|
self.transport.write(data,(self.peerTrack[_peer_id]['shost'],self.peerTrack[_peer_id]['sport']))
|
|
|
|
#self.peerTrack[_peer_id]['timer'].reset()
|
|
|
|
return
|
2021-01-05 12:18:02 -05:00
|
|
|
|
2021-01-04 16:42:48 -05:00
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
_command = data[:4]
|
|
|
|
|
|
|
|
if _command == DMRD: # DMRData -- encapsulated DMR data frame
|
|
|
|
_peer_id = data[11:15]
|
2021-01-22 08:31:42 -05:00
|
|
|
elif _command == DMRA: # DMRAlias -- Talker Alias information
|
|
|
|
_peer_id = _data[4:8]
|
2021-01-04 16:42:48 -05:00
|
|
|
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
|
2021-06-30 20:36:04 -04:00
|
|
|
self.transport.write(data, (Master,_dport))
|
2021-01-04 16:42:48 -05:00
|
|
|
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:
|
2021-01-05 13:37:49 -05:00
|
|
|
_dport = random.randint(1,(self.numPorts - 1))
|
2021-01-04 16:42:48 -05:00
|
|
|
_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
|
2021-01-05 12:18:02 -05:00
|
|
|
self.peerTrack[_peer_id]['timer'] = ResettableTimer(self.timeout,self.reaper,[_peer_id])
|
2021-01-04 16:42:48 -05:00
|
|
|
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
|
2021-06-30 20:36:04 -04:00
|
|
|
# '' = all IPv4, '::' = all IPv4 and IPv6 (Dual Stack)
|
|
|
|
ListenIP = ''
|
2021-03-01 13:33:28 -05:00
|
|
|
DestportStart = 54000
|
|
|
|
DestPortEnd = 54100
|
2021-01-04 16:42:48 -05:00
|
|
|
Timeout = 30
|
2021-06-30 20:36:04 -04:00
|
|
|
Stats = False
|
2021-03-07 07:23:30 -05:00
|
|
|
Debug = False
|
2021-01-04 16:42:48 -05:00
|
|
|
BlackList = [1234567]
|
|
|
|
|
|
|
|
#*******************
|
|
|
|
|
|
|
|
|
|
|
|
CONNTRACK = {}
|
|
|
|
|
2021-01-05 13:37:49 -05:00
|
|
|
for port in range(DestportStart,DestPortEnd+1,1):
|
2021-01-04 16:42:48 -05:00
|
|
|
CONNTRACK[port] = False
|
2021-01-05 13:37:49 -05:00
|
|
|
|
2021-06-30 20:36:04 -04:00
|
|
|
#If we are listening IPv6 and Master is an IPv4 IPv4Address
|
|
|
|
#IPv6ify the address.
|
|
|
|
if ListenIP == '::' and IsIPv4Address(Master):
|
|
|
|
Master = '::ffff:' + Master
|
2021-01-04 16:42:48 -05:00
|
|
|
|
2021-06-30 20:36:04 -04:00
|
|
|
reactor.listenUDP(ListenPort,Proxy(Master,ListenPort,CONNTRACK,BlackList,Timeout,Debug,DestportStart,DestPortEnd),interface=ListenIP)
|
2021-01-04 16:42:48 -05:00
|
|
|
|
|
|
|
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()
|
|
|
|
|