10 KiB
ADN Systems DMR Peer Server
Overview
ADN Systems DMR Peer Server is a fork of FreeDMR, implementing a Digital Mobile Radio (DMR) network server. Launched in April 2024 by international amateur radio enthusiasts, it operates on an Open Bridge Protocol (OBP) fostering a decentralized network architecture. The system handles DMR voice and data communication, acting as a conference bridge/reflector that routes traffic between connected systems (repeaters, hotspots, peers) based on configurable bridge rules.
User Preferences
Preferred communication style: Simple, everyday language.
System Architecture
Core Protocol Implementation
Problem: Need to implement HomeBrew Repeater Protocol (HBP) and Open Bridge Protocol for DMR communication Solution: Python-based protocol handlers using Twisted framework for asynchronous networking
hblink.pyserves as the core protocol engine implementing HBSYSTEM and OPENBRIDGE classes- Supports both peer and master/server modes for network topology flexibility
- Uses DatagramProtocol for UDP-based DMR packet handling
- Implements custom authentication using SHA256/BLAKE2b hashing with HMAC for security
Rationale: Twisted provides battle-tested async I/O suitable for handling multiple simultaneous connections with low latency requirements critical for voice communication.
Bridge/Routing Architecture
Problem: Route voice traffic between multiple DMR systems based on talkgroups and timeslots
Solution: Conference bridge pattern implemented in bridge_master.py and bridge.py
- Traffic routing based on rules defined in
rules.pyconfiguration - Systems can be dynamically activated/deactivated on conference bridges
- Supports timeout-based automatic disconnection
- Static routing option available for permanent connections
Alternatives Considered: End-to-end routing was rejected in favor of conference bridge approach for better scalability and simpler management.
Pros: Flexible routing, easy management, scalable Cons: Systems must individually join bridges (not transparent end-to-end)
Proxy Architecture
Problem: Allow multiple hotspots/repeaters to connect through a single public IP address
Solution: UDP proxy implementation in hotspot_proxy_v2.py and hotspot_proxy_self_service.py
- Dynamic port allocation for incoming connections
- Connection tracking with timeout-based cleanup
- Blacklist support for access control
- Self-service variant includes database-backed client management
Rationale: Many amateur radio operators are behind NAT/firewalls; proxy enables connectivity without port forwarding.
Voice Synthesis System
Problem: Generate voice announcements for system events (linking, unlinking, status) Solution: AMBE (Advanced Multi-Band Excitation) voice codec integration
- Pre-recorded indexed voice files in multiple languages (en_GB, es_ES, fr_FR, etc.)
read_ambe.pyhandles reading/parsing of AMBE audio filesmk_voice.pygenerates HBP-compliant voice packets from AMBE dataplayback.pyandplay_ambe.pyhandle voice injection into streams
Rationale: Provides accessible feedback to users without requiring external TTS systems, maintains compatibility with DMR audio codecs.
Individual Password Authentication
Problem: Need individual password authentication per Radio ID (indicativo) for enhanced security Solution: JSON-based password storage with automatic reload
- Password file:
data/user_passwords.jsonstores individual passwords by Radio ID - Web dashboard:
dashboard.pyprovides admin interface for password management - Auto-reload: Passwords are reloaded every 10 seconds without server restart
- Fallback: Global passphrase used if no individual password is configured
- Mandatory authentication: Individual or global password required (no null passphrase allowed)
Suffix Support for Radio IDs:
- Radio IDs of 9 digits (ej: 214501601-214501699) can use the password of their 7-digit base ID (ej: 2145016)
- Useful for users with multiple hotspots/devices using suffix extensions
- Priority: Exact ID match first, then base ID match
Authentication Priority:
- Individual password for exact Radio ID (if configured)
- Individual password for base ID (7 digits, for 9-digit IDs with suffix)
- Global passphrase (if configured)
- Reject connection (no valid credentials)
Dashboard Credentials: Stored in config/dashboard_credentials.json file (not uploaded to GitHub)
- Supports multiple administrators with the following format:
{
"admins": [
{"user": "admin", "password": "admin123"},
{"user": "operador1", "password": "clave456"}
]
}
- Also supports legacy single-admin format for backwards compatibility
Dashboard Web con Acceso Dual:
- Pagina principal (
/): Selector entre acceso usuario y administrador - Acceso Usuario (
/user/login): Login con Radio ID y contrasena para cambiar su propia contrasena - Acceso Administrador (
/admin/login): Login para gestionar todas las contrasenas - Los usuarios pueden cambiar su contrasena sin necesitar al administrador
Busqueda por Indicativo (Callsign):
- Descarga automatica diaria de la base de datos de RadioID.net (
data/user.csv) - Formato CSV con campos: RADIO_ID, CALLSIGN, FIRST_NAME, LAST_NAME, CITY, STATE, COUNTRY
- Funcion de busqueda en el panel de administracion para encontrar Radio IDs por indicativo
- Muestra toda la informacion del registro: nombre, ciudad, estado, pais
- Soporta busqueda parcial (ej: "EA4" muestra todos los indicativos que empiezan por EA4)
- Limita resultados a 20 para mejor rendimiento
- Boton "Usar ID" para autorellenar el formulario de agregar contrasena
Servidor de Seguridad Centralizado (Rama: descentralizada)
Problema: Gestionar credenciales de forma centralizada desde un servidor remoto Solucion: Descarga automatica de archivos de seguridad desde servidor central
Configuracion en adn.cfg seccion [GLOBAL]:
URL_SECURITY: IP o DNS del servidor central de seguridadPORT_SECURITY: Puerto del servidor centralPASS_SECURITY: Contrasena de autenticacion del administradorUSERS_PASS: Nombre del archivo de contrasenas (default: user_passwords.json)HASH_ENCRYPT: Nombre del archivo de clave de encriptacion (default: encryption_key.secret)
Comportamiento de Descarga:
encryption_key.secret: Se descarga SOLO al arrancar el servidor, sobrescribe el existenteuser_passwords.json: Se descarga cada 5 minutos, compara contenido antes de actualizar
Sintaxis de Descarga (curl):
curl -L "http://URL_SECURITY:PORT_SECURITY/descargar?pass=PASS_SECURITY&encryption_key.secret"
curl -L "http://URL_SECURITY:PORT_SECURITY/descargar?pass=PASS_SECURITY&user_passwords.json"
Seguridad y Tolerancia a Fallos:
- Si la descarga falla, se conservan los archivos locales existentes
- No se permite contrasena nula (ALLOW_NULL_PASSPHRASE eliminado)
- Autenticacion obligatoria: contrasena individual o global requerida
Archivos Descargados:
config/encryption_key.secret: Clave de encriptacion para descifrar contrasenasdata/user_passwords.json: Archivo JSON con contrasenas encriptadas por Radio ID
Configuration Management
Problem: Complex multi-system configuration with ACLs, bridges, and network parameters
Solution: INI-based configuration parsed by config.py
- Centralized configuration in
config/adn.cfg - ACL (Access Control List) processing for registration and talkgroup filtering
- Support for both whitelist and blacklist approaches
- Language preferences and multi-language support via
languages.py
Monitoring and Reporting
Problem: Need visibility into system operation, connections, and traffic Solution: Network-based reporting protocol
report_receiver.pyandreport_sql.pyconsume events from the core server- Pickle-based serialization for config/bridge state transmission
- Opcode-based protocol defined in
reporting_const.py - SQL integration option for persistent event logging
Rationale: Separates monitoring concerns from core routing logic, enables multiple monitoring tools.
Asterisk Integration
Problem: Integration with Asterisk PBX for advanced features (app_rpt)
Solution: Asterisk Manager Interface (AMI) client in AMI.py
- Sends RPT (repeater) commands to Asterisk
- Uses Twisted LineReceiver for line-based protocol handling
- Supports node-specific command routing
API Layer
Problem: External systems need programmatic access to server functions Solution: XML-RPC and SOAP API implementation
API.pyimplements Spyne-based SOAP service (FD_API)- Peer validation and authentication endpoints
api_client.pyprovides example XML-RPC client- Key-based authentication for peer systems
External Dependencies
Network Protocols
- Twisted (>= 16.3.0) - Asynchronous networking framework for all protocol handling
- dmr_utils3 (>= 0.1.19) - DMR protocol utilities for packet encoding/decoding, BPTC, Golay error correction
Database Systems
- MySQL/MariaDB - Via twisted.enterprise.adbapi and MySQLdb
- Used by
proxy_db.pyfor self-service proxy client management - Stores client registrations, authentication, and connection tracking
- Optional for report_sql.py event logging
- Used by
Remote Object Communication
- Pyro5 - Python Remote Objects for inter-process communication
- Used by proxy services for distributed architecture
- Enables separation of proxy components across processes/hosts
Third-Party Services
- radioid.net API - DMR ID database lookups
peer_ids.json,subscriber_ids.json,talkgroup_ids.jsondownloaded from external sources- Cached locally with staleness checking via
utils.py::try_download() - Provides callsign/name resolution for DMR IDs
Supporting Libraries
- bitarray/bitstring - Binary data manipulation for DMR packet construction
- configparser - INI file parsing for hblink.cfg
- setproctitle - Process naming for easier system monitoring
- resettabletimer - Timeout management for connection tracking
- hashlib/hmac - Cryptographic functions for authentication
Language/Voice Assets
- Pre-recorded AMBE voice files in Audio/ directory
- Multiple language support (en_GB, es_ES, fr_FR, de_DE, dk_DK, it_IT, no_NO, pl_PL, se_SE, pt_PT, cy_GB, el_GR, th_TH, CW)
- Voice file indexing via i8n_voice_map.py