Add full TURN relay support for reliable calls from any network
Problem: Calls via FQDN work "sometimes" because STUN-only mode fails behind strict NAT (cellular, Proton VPN, hotel WiFi, corporate firewalls). STUN tells clients their public IP, but can't relay media when direct UDP paths are blocked. TURN relays media as a fallback. Changes: coturn (docker-compose.yml): - Upgraded from STUN-only to full STUN+TURN relay - Uses long-term credential mechanism (--lt-cred-mech) - Credentials shared between coturn and Asterisk automatically - Relay port range 49152-49252 (configurable, ~50 concurrent relayed calls) - Always-on (removed --profile stun gate) - Conditional --external-ip (only set when PUBLIC_IP is provided) Entrypoint (docker/entrypoint.sh): - Auto-detects public IP (ifconfig.me → icanhazip.com → api.ipify.org) - Auto-generates TURN password on first startup (saved to config) - Configures rtp.conf with icesupport + stunaddr + turnaddr + credentials - Updates pjsip.conf external_*_address if public IP changes - Always enables ICE, STUN, and TURN for Docker deployments Main script (easy-asterisk-v0.10.0.sh): - Added TURN_ENABLED, TURN_SERVER, TURN_USERNAME, TURN_PASSWORD to load_config/save_config - repair_core_configs: rtp.conf now includes turnaddr/turnusername/turnpassword when TURN is enabled - Bash device creation: Docker mode defaults to FQDN (TLS) for all new devices - Python device creation: reads TURN_ENABLED, auto-selects FQDN in Docker - Main menu shows TURN status .env.example: - Comprehensive documentation for every setting - DOMAIN_NAME is the only required setting - Port forwarding requirements clearly listed - TURN credentials and relay port range documented The result: `docker compose up -d` gives you a fully working PBX where any SIP client on any network can connect reliably via FQDN:5061. https://claude.ai/code/session_01Vm6NLaQuzM4VosAotqS1q8
This commit is contained in:
+107
-53
@@ -283,6 +283,10 @@ load_config() {
|
||||
WEB_ADMIN_AUTH_DISABLED="${WEB_ADMIN_AUTH_DISABLED:-false}"
|
||||
VPN_ICE_ENABLED="${VPN_ICE_ENABLED:-n}"
|
||||
CUSTOM_STUN_SERVER="${CUSTOM_STUN_SERVER:-}"
|
||||
TURN_ENABLED="${TURN_ENABLED:-n}"
|
||||
TURN_SERVER="${TURN_SERVER:-}"
|
||||
TURN_USERNAME="${TURN_USERNAME:-}"
|
||||
TURN_PASSWORD="${TURN_PASSWORD:-}"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -322,6 +326,10 @@ WEB_ADMIN_PORT="$WEB_ADMIN_PORT"
|
||||
WEB_ADMIN_AUTH_DISABLED="$WEB_ADMIN_AUTH_DISABLED"
|
||||
VPN_ICE_ENABLED="$VPN_ICE_ENABLED"
|
||||
CUSTOM_STUN_SERVER="$CUSTOM_STUN_SERVER"
|
||||
TURN_ENABLED="$TURN_ENABLED"
|
||||
TURN_SERVER="$TURN_SERVER"
|
||||
TURN_USERNAME="$TURN_USERNAME"
|
||||
TURN_PASSWORD="$TURN_PASSWORD"
|
||||
EOF
|
||||
chmod 644 "$CONFIG_FILE"
|
||||
|
||||
@@ -736,46 +744,79 @@ add_device_menu() {
|
||||
local display_transport="UDP"
|
||||
local display_encryption="None"
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e " HOW WILL THIS DEVICE CONNECT?"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo -e " 1) ${GREEN}LAN/VPN${NC} - Same network or VPN tunnel (UDP)"
|
||||
if [[ "$ENABLE_TLS" == "y" && -n "$DOMAIN_NAME" ]]; then
|
||||
echo -e " 2) ${CYAN}FQDN${NC} - Internet or cross-VLAN via ${DOMAIN_NAME} (TLS)"
|
||||
else
|
||||
echo -e " 2) ${YELLOW}FQDN${NC} - Not configured (run 'Setup Internet Access' first)"
|
||||
fi
|
||||
echo ""
|
||||
read -p " Select [1]: " conn_choice
|
||||
conn_choice="${conn_choice:-1}"
|
||||
# In Docker with FQDN: default to FQDN mode for all devices
|
||||
if is_docker && [[ -n "$DOMAIN_NAME" ]]; then
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e " HOW WILL THIS DEVICE CONNECT?"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo -e " 1) ${CYAN}FQDN (recommended)${NC} - Via ${DOMAIN_NAME} (TLS) - works from any network"
|
||||
echo -e " 2) ${GREEN}LAN only${NC} - Same local network (UDP)"
|
||||
echo ""
|
||||
read -p " Select [1]: " conn_choice
|
||||
conn_choice="${conn_choice:-1}"
|
||||
|
||||
if [[ "$conn_choice" == "1" ]]; then
|
||||
# LAN/VPN - UDP, no encryption (explicit transport prevents TLS fallback)
|
||||
transport_block="transport=transport-udp"
|
||||
encryption_block="media_encryption=no"
|
||||
display_server="$(hostname -I | awk '{print $1}')"
|
||||
display_port="5060"
|
||||
display_transport="UDP"
|
||||
display_encryption="None"
|
||||
# Enable ICE for VPN devices if VPN ICE mode is active
|
||||
if [[ "$VPN_ICE_ENABLED" == "y" ]]; then
|
||||
if [[ "$conn_choice" == "2" ]]; then
|
||||
transport_block="transport=transport-udp"
|
||||
encryption_block="media_encryption=no"
|
||||
display_server="$(hostname -I | awk '{print $1}')"
|
||||
display_port="5060"
|
||||
display_transport="UDP"
|
||||
display_encryption="None"
|
||||
ice_block="ice_support=yes"
|
||||
else
|
||||
conn_type="fqdn"
|
||||
transport_block="transport=transport-tls"
|
||||
encryption_block="media_encryption=sdes"
|
||||
ice_block="ice_support=yes"
|
||||
display_server="$DOMAIN_NAME"
|
||||
display_port="5061"
|
||||
display_transport="TLS"
|
||||
display_encryption="SRTP (SDES)"
|
||||
fi
|
||||
elif [[ "$conn_choice" == "2" ]]; then
|
||||
if [[ "$ENABLE_TLS" != "y" || -z "$DOMAIN_NAME" ]]; then
|
||||
print_error "FQDN access not configured. Run 'Setup Internet Access' first."
|
||||
return
|
||||
else
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e " HOW WILL THIS DEVICE CONNECT?"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo -e " 1) ${GREEN}LAN/VPN${NC} - Same network or VPN tunnel (UDP)"
|
||||
if [[ "$ENABLE_TLS" == "y" && -n "$DOMAIN_NAME" ]]; then
|
||||
echo -e " 2) ${CYAN}FQDN${NC} - Internet or cross-VLAN via ${DOMAIN_NAME} (TLS)"
|
||||
else
|
||||
echo -e " 2) ${YELLOW}FQDN${NC} - Not configured (run 'Setup Internet Access' first)"
|
||||
fi
|
||||
echo ""
|
||||
read -p " Select [1]: " conn_choice
|
||||
conn_choice="${conn_choice:-1}"
|
||||
|
||||
if [[ "$conn_choice" == "1" ]]; then
|
||||
# LAN/VPN - UDP, no encryption (explicit transport prevents TLS fallback)
|
||||
transport_block="transport=transport-udp"
|
||||
encryption_block="media_encryption=no"
|
||||
display_server="$(hostname -I | awk '{print $1}')"
|
||||
display_port="5060"
|
||||
display_transport="UDP"
|
||||
display_encryption="None"
|
||||
# Enable ICE for VPN devices if VPN ICE mode is active
|
||||
if [[ "$VPN_ICE_ENABLED" == "y" ]]; then
|
||||
ice_block="ice_support=yes"
|
||||
fi
|
||||
elif [[ "$conn_choice" == "2" ]]; then
|
||||
if [[ "$ENABLE_TLS" != "y" || -z "$DOMAIN_NAME" ]]; then
|
||||
print_error "FQDN access not configured. Run 'Setup Internet Access' first."
|
||||
return
|
||||
fi
|
||||
conn_type="fqdn"
|
||||
transport_block="transport=transport-tls"
|
||||
encryption_block="media_encryption=sdes"
|
||||
ice_block="ice_support=yes"
|
||||
display_server="$DOMAIN_NAME"
|
||||
display_port="5061"
|
||||
display_transport="TLS"
|
||||
display_encryption="SRTP (SDES)"
|
||||
fi
|
||||
conn_type="fqdn"
|
||||
transport_block="transport=transport-tls"
|
||||
encryption_block="media_encryption=sdes"
|
||||
ice_block="ice_support=yes"
|
||||
display_server="$DOMAIN_NAME"
|
||||
display_port="5061"
|
||||
display_transport="TLS"
|
||||
display_encryption="SRTP (SDES)"
|
||||
fi
|
||||
|
||||
backup_config "/etc/asterisk/pjsip.conf"
|
||||
@@ -2989,30 +3030,31 @@ transport=config,pjsip.conf,criteria=type=transport
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ICE and STUN configuration
|
||||
# ICE / STUN / TURN configuration
|
||||
# Enabled for: FQDN/internet mode OR VPN with ICE enabled
|
||||
load_config
|
||||
local ice_stun_config=""
|
||||
if [[ -n "$DOMAIN_NAME" ]]; then
|
||||
# FQDN mode: always enable ICE
|
||||
local stun_addr="${CUSTOM_STUN_SERVER:-stun.l.google.com:19302}"
|
||||
ice_stun_config="icesupport=yes
|
||||
stunaddr=${stun_addr}"
|
||||
elif [[ "$VPN_ICE_ENABLED" == "y" ]]; then
|
||||
# VPN mode with ICE: use custom or self-hosted STUN
|
||||
local stun_addr="${CUSTOM_STUN_SERVER:-stun.l.google.com:19302}"
|
||||
ice_stun_config="icesupport=yes
|
||||
local ice_config=""
|
||||
if [[ -n "$DOMAIN_NAME" ]] || [[ "$VPN_ICE_ENABLED" == "y" ]] || [[ "$TURN_ENABLED" == "y" ]]; then
|
||||
local stun_addr="${TURN_SERVER:-${CUSTOM_STUN_SERVER:-stun.l.google.com:19302}}"
|
||||
ice_config="icesupport=yes
|
||||
stunaddr=${stun_addr}"
|
||||
# Add TURN relay if configured (required for calls through strict NAT/VPN)
|
||||
if [[ "$TURN_ENABLED" == "y" && -n "$TURN_SERVER" && -n "$TURN_USERNAME" && -n "$TURN_PASSWORD" ]]; then
|
||||
ice_config="${ice_config}
|
||||
turnaddr=${TURN_SERVER}
|
||||
turnusername=${TURN_USERNAME}
|
||||
turnpassword=${TURN_PASSWORD}"
|
||||
fi
|
||||
else
|
||||
ice_stun_config="# icesupport disabled - LAN only mode"
|
||||
ice_config="# icesupport disabled - LAN only mode"
|
||||
fi
|
||||
|
||||
cat > /etc/asterisk/rtp.conf << EOF
|
||||
[general]
|
||||
rtpstart=10000
|
||||
rtpend=20000
|
||||
rtpstart=${RTP_START:-10000}
|
||||
rtpend=${RTP_END:-20000}
|
||||
strictrtp=yes
|
||||
${ice_stun_config}
|
||||
${ice_config}
|
||||
EOF
|
||||
|
||||
cat > /etc/asterisk/logger.conf << EOF
|
||||
@@ -3935,7 +3977,11 @@ show_main_menu() {
|
||||
echo -e " Web Admin: ${YELLOW}Stopped${NC}"
|
||||
fi
|
||||
[[ -n "$DOMAIN_NAME" ]] && echo -e " Domain: ${DOMAIN_NAME}"
|
||||
[[ "$VPN_ICE_ENABLED" == "y" ]] && echo -e " VPN ICE: ${GREEN}Enabled${NC} (${CUSTOM_STUN_SERVER:-auto})"
|
||||
if [[ "$TURN_ENABLED" == "y" ]]; then
|
||||
echo -e " TURN: ${GREEN}Enabled${NC} (${TURN_SERVER:-auto})"
|
||||
elif [[ "$VPN_ICE_ENABLED" == "y" ]]; then
|
||||
echo -e " STUN/ICE: ${GREEN}Enabled${NC} (${CUSTOM_STUN_SERVER:-auto})"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
declare -A menu_map
|
||||
@@ -4618,11 +4664,19 @@ def add_device(name, category, extension, conn_type='lan', auto_answer=None):
|
||||
# Determine transport and encryption
|
||||
# Check if VPN ICE mode is enabled (for third-party VPNs)
|
||||
vpn_ice = 'n'
|
||||
turn_enabled = 'n'
|
||||
is_container = os.path.exists('/.dockerenv')
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, 'r') as cf:
|
||||
for cline in cf:
|
||||
if cline.startswith('VPN_ICE_ENABLED='):
|
||||
vpn_ice = cline.strip().split('=', 1)[1].strip('"')
|
||||
elif cline.startswith('TURN_ENABLED='):
|
||||
turn_enabled = cline.strip().split('=', 1)[1].strip('"')
|
||||
|
||||
# In Docker: always use FQDN mode for web-created devices
|
||||
if is_container and conn_type == 'lan':
|
||||
conn_type = 'fqdn'
|
||||
|
||||
if conn_type == 'fqdn':
|
||||
transport = 'transport=transport-tls'
|
||||
@@ -4631,7 +4685,7 @@ def add_device(name, category, extension, conn_type='lan', auto_answer=None):
|
||||
else:
|
||||
transport = 'transport=transport-udp'
|
||||
encryption = 'media_encryption=no'
|
||||
ice = 'ice_support=yes' if vpn_ice == 'y' else ''
|
||||
ice = 'ice_support=yes' if (vpn_ice == 'y' or turn_enabled == 'y') else ''
|
||||
|
||||
aa_tag = ''
|
||||
if auto_answer == 'yes':
|
||||
|
||||
Reference in New Issue
Block a user