From cd5e989d2f393d7722551640ed3536f3e291da9d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 12:31:49 +0000 Subject: [PATCH] Fix VPN mobile connectivity and WiFi-to-cellular audio handoff - Add local_net entries to PJSIP transports in LAN/VPN mode when VLAN/VPN subnets are configured, so Asterisk recognizes VPN traffic as local and mobile devices no longer appear offline on VPN - Auto-detect VPN interfaces (Tailscale, WireGuard, OpenVPN) and suggest their subnets in the VLAN/VPN configuration menu - Add rtp_keepalive=15, rtp_timeout=120, and rtp_timeout_hold=120 to mobile device endpoints to maintain NAT mappings during WiFi-to-cellular transitions (fixes one-way audio) - Reduce qualify_frequency from 60s to 30s for faster re-registration detection when mobile devices change networks - Update README with VPN troubleshooting and one-way audio guidance including client-side fixes for Sipnetic and Linphone https://claude.ai/code/session_01Vm6NLaQuzM4VosAotqS1q8 --- README.md | 64 ++++++++++++++++++++++++ easy-asterisk-readme.md | 64 ++++++++++++++++++++++++ easy-asterisk-v0.10.0.sh | 102 +++++++++++++++++++++++++++++++++------ 3 files changed, 215 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c554d84..69721c7 100644 --- a/README.md +++ b/README.md @@ -599,6 +599,70 @@ nc -v pbx.yourhouse.com 5061 If this fails, your port forwarding isn't set up correctly on your router. +### "Mobile devices on VPN can't reach Asterisk" (VPN Issues) + +When mobile devices connect through a VPN (Tailscale, WireGuard, etc.), Asterisk needs to know about the VPN subnet. Without this, VPN-connected devices appear offline. + +**Fix:** + +1. Run the installer: `sudo ./easy-asterisk-v0.10.0.sh` +2. Go to: **Server Settings > Configure VLAN/VPN Subnets** +3. Answer "y" when asked about VLANs/VPNs +4. Add your VPN subnet(s): + - **Tailscale**: `100.64.0.0/10` + - **WireGuard**: Usually `10.x.x.x/24` (check your WireGuard config) + - **OpenVPN**: Check your VPN config for the tunnel subnet +5. The script will auto-detect VPN interfaces on the server and suggest subnets + +**Important:** The Asterisk server itself must also be on the VPN. If using Tailscale, install Tailscale on the server too. Mobile devices should connect to the server's **VPN IP** (e.g., `100.x.x.x` for Tailscale), not its LAN IP. + +**Verify VPN connectivity:** +```bash +# On the mobile device (or from another VPN device), ping the server's VPN IP +ping 100.x.x.x + +# Test SIP port through VPN +nc -u -v 100.x.x.x 5060 +``` + +### "One-way audio when switching from WiFi to mobile data" + +This is a known issue with SIP clients on mobile devices. When the phone switches networks (WiFi to cellular or vice versa), the phone's IP address changes but the active audio stream may not update properly. + +**What happens:** +- The phone switches to mobile data and gets a new IP +- SIP signaling may update, but the audio (RTP) stream still uses the old path +- Result: the caller can't be heard by the receiving person + +**Server-side fixes (already applied for mobile devices in v0.10.0):** +- `rtp_symmetric=yes` - Asterisk sends audio back to wherever it receives audio from +- `rtp_keepalive=15` - Asterisk sends periodic keepalive packets to maintain NAT mappings +- `rtp_timeout=120` - Detects dead audio streams after 120 seconds +- `qualify_frequency=30` - Checks device availability every 30 seconds + +**Client-side fixes (on your phone):** + +For **Sipnetic**: +- Settings > Network > Enable "ICE" (if available) +- Settings > Network > Enable "STUN" (if available) +- Settings > Network > Keep-alive interval: 15-30 seconds +- Make sure "Background mode" is enabled + +For **Linphone**: +- Settings > Network > Enable ICE +- Settings > Network > STUN server: `stun.l.google.com:19302` +- Settings > Network > Enable TURN (if behind strict NAT) + +For **any SIP app**: +- Disable WiFi sleep / battery optimization for the app +- Enable "Keep WiFi on during sleep" in Android settings +- After switching networks, hang up and redial - this forces a clean reconnection + +**If the problem persists:** +- Consider using FQDN mode with TLS/SRTP instead of LAN/VPN mode +- FQDN mode enables ICE (Interactive Connectivity Establishment) which handles network changes better +- Alternatively, keep your phone on one network type (WiFi or mobile data) during calls + ### "My IP changed and FQDN stopped working" See [Dynamic IP Handling](#dynamic-ip-handling) section. You need to set up DDNS. diff --git a/easy-asterisk-readme.md b/easy-asterisk-readme.md index c554d84..69721c7 100644 --- a/easy-asterisk-readme.md +++ b/easy-asterisk-readme.md @@ -599,6 +599,70 @@ nc -v pbx.yourhouse.com 5061 If this fails, your port forwarding isn't set up correctly on your router. +### "Mobile devices on VPN can't reach Asterisk" (VPN Issues) + +When mobile devices connect through a VPN (Tailscale, WireGuard, etc.), Asterisk needs to know about the VPN subnet. Without this, VPN-connected devices appear offline. + +**Fix:** + +1. Run the installer: `sudo ./easy-asterisk-v0.10.0.sh` +2. Go to: **Server Settings > Configure VLAN/VPN Subnets** +3. Answer "y" when asked about VLANs/VPNs +4. Add your VPN subnet(s): + - **Tailscale**: `100.64.0.0/10` + - **WireGuard**: Usually `10.x.x.x/24` (check your WireGuard config) + - **OpenVPN**: Check your VPN config for the tunnel subnet +5. The script will auto-detect VPN interfaces on the server and suggest subnets + +**Important:** The Asterisk server itself must also be on the VPN. If using Tailscale, install Tailscale on the server too. Mobile devices should connect to the server's **VPN IP** (e.g., `100.x.x.x` for Tailscale), not its LAN IP. + +**Verify VPN connectivity:** +```bash +# On the mobile device (or from another VPN device), ping the server's VPN IP +ping 100.x.x.x + +# Test SIP port through VPN +nc -u -v 100.x.x.x 5060 +``` + +### "One-way audio when switching from WiFi to mobile data" + +This is a known issue with SIP clients on mobile devices. When the phone switches networks (WiFi to cellular or vice versa), the phone's IP address changes but the active audio stream may not update properly. + +**What happens:** +- The phone switches to mobile data and gets a new IP +- SIP signaling may update, but the audio (RTP) stream still uses the old path +- Result: the caller can't be heard by the receiving person + +**Server-side fixes (already applied for mobile devices in v0.10.0):** +- `rtp_symmetric=yes` - Asterisk sends audio back to wherever it receives audio from +- `rtp_keepalive=15` - Asterisk sends periodic keepalive packets to maintain NAT mappings +- `rtp_timeout=120` - Detects dead audio streams after 120 seconds +- `qualify_frequency=30` - Checks device availability every 30 seconds + +**Client-side fixes (on your phone):** + +For **Sipnetic**: +- Settings > Network > Enable "ICE" (if available) +- Settings > Network > Enable "STUN" (if available) +- Settings > Network > Keep-alive interval: 15-30 seconds +- Make sure "Background mode" is enabled + +For **Linphone**: +- Settings > Network > Enable ICE +- Settings > Network > STUN server: `stun.l.google.com:19302` +- Settings > Network > Enable TURN (if behind strict NAT) + +For **any SIP app**: +- Disable WiFi sleep / battery optimization for the app +- Enable "Keep WiFi on during sleep" in Android settings +- After switching networks, hang up and redial - this forces a clean reconnection + +**If the problem persists:** +- Consider using FQDN mode with TLS/SRTP instead of LAN/VPN mode +- FQDN mode enables ICE (Interactive Connectivity Establishment) which handles network changes better +- Alternatively, keep your phone on one network type (WiFi or mobile data) during calls + ### "My IP changed and FQDN stopped working" See [Dynamic IP Handling](#dynamic-ip-handling) section. You need to set up DDNS. diff --git a/easy-asterisk-v0.10.0.sh b/easy-asterisk-v0.10.0.sh index e885257..773a8ce 100644 --- a/easy-asterisk-v0.10.0.sh +++ b/easy-asterisk-v0.10.0.sh @@ -11,12 +11,15 @@ # - FIXED: Extension renaming now preserves AA tags correctly # - FIXED: LAN/VPN devices now explicitly use UDP transport (prevents TLS fallback) # - FIXED: LAN devices now have media_encryption=no to prevent SRTP issues +# - FIXED: VPN subnets now included as local_net in LAN mode (fixes VPN mobile offline) +# - FIXED: One-way audio on WiFi-to-mobile-data handoff (rtp_keepalive + timers) # - ADDED: Web Admin interface for browser-based client management # - View device status (online/offline) in real-time # - Add/delete devices via web interface # - View rooms and categories # - HTTP Basic authentication with SHA256 password hashing # - Access at http://server:8080/clients +# - ADDED: VPN subnet auto-detection (Tailscale, WireGuard, OpenVPN) # - IMPROVED: Device deletion uses awk for reliable multi-section removal # - IMPROVED: Device renaming uses awk to handle all edge cases # @@ -649,6 +652,15 @@ add_device_menu() { backup_config "/etc/asterisk/pjsip.conf" + # Mobile devices benefit from keepalive to maintain NAT mappings + # during WiFi/mobile data transitions + local keepalive_block="" + if [[ "$cat_id" == "mobile" ]]; then + keepalive_block="rtp_keepalive=15 +rtp_timeout=120 +rtp_timeout_hold=120" + fi + cat >> /etc/asterisk/pjsip.conf << EOF ; === Device: $name ($cat_id) $override_tag === @@ -666,6 +678,7 @@ direct_media=no rtp_symmetric=yes force_rport=yes rewrite_contact=yes +${keepalive_block} ${ice_block} auth=${ext} aors=${ext} @@ -681,7 +694,7 @@ password=${pass} type=aor max_contacts=5 remove_existing=yes -qualify_frequency=60 +qualify_frequency=30 EOF chown -R asterisk:asterisk /etc/asterisk 2>/dev/null || true @@ -1494,35 +1507,81 @@ verify_cidr_config() { } configure_vlan_subnets() { - print_header "VLAN Configuration" + print_header "VLAN / VPN Subnet Configuration" load_config - echo "VLAN Support for Asterisk Easy" + echo "Additional Subnet Support for Easy Asterisk" echo "================================================" echo "" - echo "If your network uses VLANs (Virtual LANs), you need to" - echo "tell Asterisk about all the local subnets to prevent" - echo "calls from dropping after 30 seconds." + echo "If your network uses VLANs or VPNs, you need to tell" + echo "Asterisk about all the local subnets so that:" + echo " - Calls don't drop after 30 seconds (VLAN issue)" + echo " - VPN-connected mobile devices can register" + echo " - Audio works correctly for VPN users" echo "" echo "Example subnets:" echo " 192.168.1.0/24 - Main network" echo " 192.168.10.0/24 - IoT VLAN" - echo " 192.168.20.0/24 - Guest VLAN" - echo " 10.0.0.0/8 - Large private network" + echo " 100.64.0.0/10 - Tailscale VPN" + echo " 10.0.0.0/8 - WireGuard/OpenVPN" echo "" - read -p "Does your network use VLANs? (y/n) [${HAS_VLANS}]: " has_vlans + # Auto-detect VPN interfaces and their subnets + local detected_vpn_subnets="" + local vpn_info="" + while IFS= read -r line; do + local iface=$(echo "$line" | awk '{print $2}' | tr -d ':') + local addr=$(echo "$line" | awk '{print $4}') + if [[ -n "$addr" && -n "$iface" ]]; then + case "$iface" in + tailscale*|ts*) + vpn_info="${vpn_info} Detected: ${iface} -> ${addr} (Tailscale)\n" + detected_vpn_subnets="${detected_vpn_subnets} 100.64.0.0/10" + ;; + wg*) + vpn_info="${vpn_info} Detected: ${iface} -> ${addr} (WireGuard)\n" + detected_vpn_subnets="${detected_vpn_subnets} ${addr}" + ;; + tun*|tap*) + vpn_info="${vpn_info} Detected: ${iface} -> ${addr} (OpenVPN/VPN tunnel)\n" + detected_vpn_subnets="${detected_vpn_subnets} ${addr}" + ;; + nordlynx*|proton*) + vpn_info="${vpn_info} Detected: ${iface} -> ${addr} (VPN)\n" + detected_vpn_subnets="${detected_vpn_subnets} ${addr}" + ;; + esac + fi + done < <(ip -o -f inet addr show 2>/dev/null | grep -vE 'lo |docker|br-|veth') + detected_vpn_subnets=$(echo "$detected_vpn_subnets" | xargs -n1 2>/dev/null | sort -u | xargs 2>/dev/null) + + if [[ -n "$vpn_info" ]]; then + echo -e "${GREEN}VPN interfaces detected on this server:${NC}" + echo -e "$vpn_info" + echo " Suggested VPN subnets: ${detected_vpn_subnets}" + echo "" + echo " NOTE: If mobile devices connect via VPN (e.g., Tailscale on phones)," + echo " you MUST add the VPN subnet here for them to reach Asterisk." + echo "" + fi + + read -p "Does your network use VLANs or VPNs? (y/n) [${HAS_VLANS}]: " has_vlans has_vlans=${has_vlans:-$HAS_VLANS} if [[ "$has_vlans" =~ ^[Yy] ]]; then HAS_VLANS="y" echo "" - echo "Current VLAN Subnets: ${VLAN_SUBNETS:-none}" + echo "Current Subnets: ${VLAN_SUBNETS:-none}" + if [[ -n "$detected_vpn_subnets" ]]; then + echo "Detected VPN Subnets: ${detected_vpn_subnets}" + fi echo "" - echo "Enter VLAN subnets in CIDR notation, separated by spaces." - echo "Example: 192.168.1.0/24 192.168.10.0/24 192.168.20.0/24" + echo "Enter ALL additional subnets (VLAN + VPN) in CIDR notation, separated by spaces." + echo "Example: 192.168.10.0/24 100.64.0.0/10" echo "" - read -p "VLAN Subnets: " vlan_input + local default_subnets="${VLAN_SUBNETS:-$detected_vpn_subnets}" + read -p "Subnets [${default_subnets}]: " vlan_input + vlan_input="${vlan_input:-$default_subnets}" if [[ -n "$vlan_input" ]]; then VLAN_SUBNETS="$vlan_input" @@ -2841,10 +2900,17 @@ local_net=${vlan_subnet}" local nat_settings="" if [[ -n "$public_ip" && -n "$DOMAIN_NAME" ]]; then + # FQDN mode: full NAT settings with external addresses nat_settings="external_media_address=$public_ip external_signaling_address=$public_ip ${all_local_nets}" print_info "NAT: Public IP=$public_ip, Server IP=$server_ip" + elif [[ "$HAS_VLANS" == "y" && -n "$VLAN_SUBNETS" ]]; then + # LAN/VPN mode with VLAN/VPN subnets: include local_net entries + # so Asterisk recognizes VPN traffic as local (prevents VPN devices + # appearing offline and fixes media routing for VPN-connected mobiles) + nat_settings="${all_local_nets}" + print_info "LAN mode with additional subnets: $VLAN_SUBNETS" fi cat > "$conf_file" << EOF @@ -4312,6 +4378,11 @@ def add_device(name, category, extension, conn_type='lan', auto_answer=None): elif auto_answer == 'no': aa_tag = '[AA:no] ' + # Mobile devices get keepalive settings for NAT traversal + keepalive = '' + if category == 'mobile': + keepalive = 'rtp_keepalive=15\nrtp_timeout=120\nrtp_timeout_hold=120' + device_config = f''' ; === Device: {name} ({category}) {aa_tag}=== [{extension}] @@ -4328,6 +4399,7 @@ direct_media=no rtp_symmetric=yes force_rport=yes rewrite_contact=yes +{keepalive} {ice} auth={extension} aors={extension} @@ -4343,7 +4415,7 @@ password={password} type=aor max_contacts=5 remove_existing=yes -qualify_frequency=60 +qualify_frequency=30 ''' with open(PJSIP_CONF, 'a') as f: @@ -5878,7 +5950,7 @@ submenu_server() { echo " 6) Verify CIDR/NAT config" echo " 7) Watch Live Logs" echo " 8) Router Doctor" - echo " 9) Configure VLAN Subnets" + echo " 9) Configure VLAN/VPN Subnets" echo " 10) Provisioning Manager" echo " 11) Web Admin (Client Management)" echo " 0) Back"