OK-DMR / Hytera_Homebrew_Bridge

[BETA] protocol translator between Hytera repeaters 8.x and 9.x and MMDVM / Homebrew servers (hblink3, freedmr, ...)
GNU Affero General Public License v3.0
27 stars 24 forks source link

Via LTE no connection (double-NAT issue) #17

Open do1kbl opened 1 year ago

do1kbl commented 1 year ago

Hello, in the Lan it work fine. But if I install it on a Computer make the port forwarding correct the hytera repeater wont connect. on DMR+ and Brandmeister I can connect. maybe we need dynamic ports? Logs: INFO - 2023-08-12 10:48:42,239 - BridgeSettings - Hytera Repeater is expected to connect at 192.168.1.106 and ports [MASTER PORT: 50000] [DMR PORT: 50001] [RDAC PORT: 50002] ERROR - 2023-08-12 10:48:59,245 - MMDVMProtocol - PARAM: homebrew.repeater_dmr_id CURRENT_VALUE: 0 MESSAGE: Value might have not been configured and was not obtained in Hytera repeater configuration process (either P2P, RDAC or SNMP) ERROR - 2023-08-12 10:48:59,245 - MMDVMProtocol - PARAM: homebrew.callsign CURRENT_VALUE: None MESSAGE: Value might have not been configured and was not obtained in Hyt era repeater configuration process (either P2P, RDAC or SNMP) INFO - 2023-08-12 10:48:59,245 - MMDVMProtocol - Sending Login Request DEBUG - 2023-08-12 10:48:59,246 - HyteraP2PProtocol - RDAC Accept for 46.114.174.112.50428 INFO - 2023-08-12 10:48:59,247 - MMDVMProtocol - Not sending packet, waiting for Hytera repeater to connect first DEBUG - 2023-08-12 10:48:59,248 - HyteraP2PProtocol - DMR Accept for 46.114.174.112.60789 DEBUG - 2023-08-12 10:48:59,251 - HyteraP2PProtocol - RDAC Accept for 46.114.174.112.50428 DEBUG - 2023-08-12 10:48:59,252 - HyteraP2PProtocol - DMR Accept for 46.114.174.112.60789 DEBUG - 2023-08-12 10:49:02,212 - HyteraP2PProtocol - RDAC Accept for 46.114.174.112.50428

You its other port 60789 and 50428.

maybe its possible to solve it

do1kbl commented 1 year ago

I chage on the file hytera_protocols.py from line 148 I read the incomming Port and overwrite the ports from the settings.ini file

from

def datagram_received(self, data: bytes, address: Tuple[str, int]) -> None:
        packet_type = self.command_get_type(data)
        is_command = self.packet_is_command(data)
        if is_command:
            if packet_type not in self.KNOWN_PACKET_TYPES:
                if not self.packet_is_ack(data):
                    self.log_error("Received %s bytes from %s" % (len(data), address))
                    self.log_error(data.hex())
                    self.log_error("Unknown packet of type:%s received" % packet_type)
            if packet_type == self.PACKET_TYPE_REQUEST_REGISTRATION:
                self.handle_registration(data, address)
            elif packet_type == self.PACKET_TYPE_REQUEST_RDAC_STARTUP:
                self.handle_rdac_request(data, address)
            elif packet_type == self.PACKET_TYPE_REQUEST_DMR_STARTUP:
                self.handle_dmr_request(data, address)

into:

def datagram_received(self, data: bytes, address: Tuple[str, int]) -> None:
        packet_type = self.command_get_type(data)
        is_command = self.packet_is_command(data)
        #self.log_debug("P2P port?: %s.%s" % address)
        if is_command:
            if packet_type not in self.KNOWN_PACKET_TYPES:
                if not self.packet_is_ack(data):
                    self.log_error("Received %s bytes from %s" % (len(data), address))
                    self.log_error(data.hex())
                    self.log_error("Unknown packet of type:%s received" % packet_type)
            if packet_type == self.PACKET_TYPE_REQUEST_REGISTRATION:
                self.handle_registration(data, address)
                self.log_debug("P2P port?: %s.%s" % address)
                print("Wechsel P2P Port von %s in %s" % (self.settings.p2p_port, address[1]))
                self.settings.p2p_port = address[1]
            elif packet_type == self.PACKET_TYPE_REQUEST_RDAC_STARTUP:
                self.handle_rdac_request(data, address)
                print("Wechsel RDAC Port von %s in %s" % (self.settings.rdac_port, address[1]))
                self.settings.rdac_port = address[1]
            elif packet_type == self.PACKET_TYPE_REQUEST_DMR_STARTUP:
                self.handle_dmr_request(data, address)
                print("Wechsel DMR Port von %s in %s" % (self.settings.dmr_port, address[1]))
                self.settings.dmr_port = address[1]

after this the repeater will connect the data will read from the repeater (Frequency, call, radio id) but after this I received not Heartbeat (HyteraMmdvmTranslator - HYTER->HHB Received IPSC Heartbeat, not translating)

And no voice data will send to eachother

if someone speak on mmdvm site I see the Data in the consol window but the repeater do nothing. if someone speal on the hytera repeater I see nothing in the log

the log says only: DEBUG - 2023-08-17 00:09:48,588 - MMDVMProtocol - HHB->MMDVM Unsupported common_log_format for data TypeRepeaterPing DEBUG - 2023-08-17 00:09:48,605 - MMDVMProtocol - Master PONG received DEBUG - 2023-08-17 00:09:53,594 - MMDVMProtocol - Sending PING DEBUG - 2023-08-17 00:09:53,596 - MMDVMProtocol - HHB->MMDVM Unsupported common_log_format for data TypeRepeaterPing DEBUG - 2023-08-17 00:09:53,613 - MMDVMProtocol - Master PONG received

smarek commented 1 year ago

This is LAN with NAT issue

VT0z2y8m483XFR_Yw6H1GzCFKGS7GMWN4USkfqPQi6c8D_ZplLYhhOXpxddlY4L7zQcsQ0Y2WxOQIRlIJBPtScWqGR67E9P98jDKPbaO0Dx0lUQ47UeTqXTKagLCfwKf38yRl89QnbBD5_oWkfP16cVoVCEPD_2PZuawL5L78VehOBRsbHK4vyR16GdPNrpR30y7Qljpma3mk5US-Q46wvvgj5q8Hjxss_y78byO_Pzu0000

We need to separate Incoming Address (ip, port) from Outgoing Address (ip, port)

Incoming transmission will look like the NAT (router) is the Repeater, and HHB will try to respond to NAT IP instead of Repeater IP


plantuml: VT0z2y8m483XFR_Yw6H1GzCFKGS7GMWN4USkfqPQi6c8D_ZplLYhhOXpxddlY4L7zQcsQ0Y2WxOQIRlIJBPtScWqGR67E9P98jDKPbaO0Dx0lUQ47UeTqXTKagLCfwKf38yRl89QnbBD5_oWkfP16cVoVCEPD_2PZuawL5L78VehOBRsbHK4vyR16GdPNrpR30y7Qljpma3mk5US-Q46wvvgj5q8Hjxss_y78byO_Pzu0000

smarek commented 1 year ago

@do1kbl how is this configured/handled in kurt's gw_hytera_mmdvm ?

do1kbl commented 1 year ago

I fix it quick and dity I will sent u the file later, but maybe you find a better way but its working.

smarek commented 1 year ago

"the fix" i received, cannot be solved like this

> git diff --no-index hytera_protocols_Original.py hytera_protocols.py
diff --git a/hytera_protocols_Original.py b/hytera_protocols.py
index a063a4e..e4a4c23 100644
--- a/hytera_protocols_Original.py
+++ b/hytera_protocols.py
@@ -92,7 +92,7 @@ class HyteraP2PProtocol(CustomBridgeDatagramProtocol):
         self.log_debug("RDAC Accept for %s.%s" % address)

         # redirect repeater to correct RDAC port
-        data = self.get_redirect_packet(data, self.settings.rdac_port)
+        data = self.get_redirect_packet(data, 62007) #self.settings.rdac_port)
         self.transport.sendto(data, response_address)

     @staticmethod
@@ -123,8 +123,9 @@ class HyteraP2PProtocol(CustomBridgeDatagramProtocol):

         self.transport.sendto(data, response_address)
         self.log_debug("DMR Accept for %s.%s" % address)
+        self.settings.dmr_port = address[1]

-        data = self.get_redirect_packet(data, self.settings.dmr_port)
+        data = self.get_redirect_packet(data, 62006) #self.settings.dmr_port)
         self.transport.sendto(data, response_address)

     def handle_ping(self, data: bytes, address: tuple) -> None:
@@ -156,10 +157,18 @@ class HyteraP2PProtocol(CustomBridgeDatagramProtocol):
                     self.log_error("Unknown packet of type:%s received" % packet_type)
             if packet_type == self.PACKET_TYPE_REQUEST_REGISTRATION:
                 self.handle_registration(data, address)
+                self.log_debug("P2P port?: %s.%s" % address)
+                print("Wechsel P2P Port von %s in %s" % (self.settings.p2p_port, address[1]))
+                self.settings.p2p_port = address[1]
             elif packet_type == self.PACKET_TYPE_REQUEST_RDAC_STARTUP:
                 self.handle_rdac_request(data, address)
+                print("Wechsel RDAC Port von %s in %s" % (self.settings.rdac_port, address[1]))
+                self.settings.rdac_port = address[1]
             elif packet_type == self.PACKET_TYPE_REQUEST_DMR_STARTUP:
                 self.handle_dmr_request(data, address)
+                print("Wechsel DMR Port von %s in %s:%s" % (self.settings.dmr_port, address[0], address[1]))
+                self.settings.hytera_repeater_ip = address[0]
+                #self.settings.dmr_port = address[1]
         elif self.packet_is_ping(data):
             self.handle_ping(data, address)
         else:
@@ -541,6 +550,9 @@ class HyteraDMRProtocol(CustomBridgeDatagramProtocol):

     def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:
         try:
+            #print(addr)
+            self.settings.hytera_repeater_ip = addr[0]
+            self.settings.dmr_port = addr[1]
             self.queue_incoming.put_nowait(parse_hytera_data(data))
         except EOFError as e:
             self.log_error(f"Cannot parse IPSC DMR packet {hexlify(data)} from {addr}")
do1kbl commented 1 year ago

yes but this 6200x ports what is fix in the code need to be from the config but not overwritten by the program

smarek commented 1 year ago

@do1kbl how would you expect 2 or more repeaters over NAT to be routed? We need unique DMR/RDAC incoming port because we cannot distinguish them from incoming data

smarek commented 1 year ago

@do1kbl are we solving HHB behind NAT or Repeater behind NAT ?

smarek commented 1 year ago

Ok, to conclude here

@do1kbl has double-NAT setup

[HHB] <-> [NAT A] <-> Internet <-> [NAT B] <-> [Hytera Repeater]

This can be solved by 2 ways 1) UDP hole punching (requires STUN server in the internet, https://github.com/pradt2/always-online-stun ) 2) Configure both NATs static port-forwarding

[Untested] With static port-forwarding:

From previous testing Hytera Repeater is sensitive about the IP+Port data come from, otherwise it's just rejected

I will solve the "single-NAT scenarios" (both "HHB in the LAN" and "RPT in the LAN"), nothing more

smarek commented 1 year ago

Scenarios

1) Repeater in LAN, HHB in internet/extranet

repeater_behind_nat

2) Repeater in internet/extranet, HHB in LAN

repeater_in_the_internet

smarek commented 1 year ago

Testing results:

test script for NAT traversal

# Run as "test_nat.py <LISTEN_IP> <LISTEN_PORT OR 0 FOR DYNAMIC> <TARGET_IP> <TARGET_PORT>

import sys
import asyncio

from asyncio import DatagramProtocol
from asyncio import transports
from typing import Any, Union

listen_ip: str = sys.argv[1]
listen_port: int = int(sys.argv[2])
target_ip: str = sys.argv[3]
target_port: int = int(sys.argv[4])

print(f"Listen {listen_ip}:{listen_port} \nTarget {target_ip}:{target_port}")

class TestProtocol(DatagramProtocol):
    def datagram_received(self, data: bytes, addr: tuple[Union[str, Any], int]) -> None:
        print(f"datagram_received from {addr} data {data.hex()}")
        self.transport.sendto(bytes([0x44, 0x55, 0x66]), addr)

    def connection_made(self, transport: transports.DatagramTransport) -> None:
        print(f"connection_made {transport}")
        self.transport: transports.DatagramTransport = transport
        transport.sendto(bytes([0x11, 0x22, 0x33]), (target_ip, target_port))

async def main() -> None:
    proto = TestProtocol()
    _transport, _protocol = await asyncio.get_running_loop().create_datagram_endpoint(
        lambda: proto, local_addr=(listen_ip, listen_port)
    )
    await asyncio.sleep(60)

asyncio.run(main())