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
This commit is contained in:
Claude
2026-02-21 12:31:49 +00:00
parent 18b38bff26
commit cd5e989d2f
3 changed files with 215 additions and 15 deletions
+64
View File
@@ -599,6 +599,70 @@ nc -v pbx.yourhouse.com 5061
If this fails, your port forwarding isn't set up correctly on your router. 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" ### "My IP changed and FQDN stopped working"
See [Dynamic IP Handling](#dynamic-ip-handling) section. You need to set up DDNS. See [Dynamic IP Handling](#dynamic-ip-handling) section. You need to set up DDNS.
+64
View File
@@ -599,6 +599,70 @@ nc -v pbx.yourhouse.com 5061
If this fails, your port forwarding isn't set up correctly on your router. 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" ### "My IP changed and FQDN stopped working"
See [Dynamic IP Handling](#dynamic-ip-handling) section. You need to set up DDNS. See [Dynamic IP Handling](#dynamic-ip-handling) section. You need to set up DDNS.
+87 -15
View File
@@ -11,12 +11,15 @@
# - FIXED: Extension renaming now preserves AA tags correctly # - FIXED: Extension renaming now preserves AA tags correctly
# - FIXED: LAN/VPN devices now explicitly use UDP transport (prevents TLS fallback) # - 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: 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 # - ADDED: Web Admin interface for browser-based client management
# - View device status (online/offline) in real-time # - View device status (online/offline) in real-time
# - Add/delete devices via web interface # - Add/delete devices via web interface
# - View rooms and categories # - View rooms and categories
# - HTTP Basic authentication with SHA256 password hashing # - HTTP Basic authentication with SHA256 password hashing
# - Access at http://server:8080/clients # - 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 deletion uses awk for reliable multi-section removal
# - IMPROVED: Device renaming uses awk to handle all edge cases # - IMPROVED: Device renaming uses awk to handle all edge cases
# #
@@ -649,6 +652,15 @@ add_device_menu() {
backup_config "/etc/asterisk/pjsip.conf" 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 cat >> /etc/asterisk/pjsip.conf << EOF
; === Device: $name ($cat_id) $override_tag === ; === Device: $name ($cat_id) $override_tag ===
@@ -666,6 +678,7 @@ direct_media=no
rtp_symmetric=yes rtp_symmetric=yes
force_rport=yes force_rport=yes
rewrite_contact=yes rewrite_contact=yes
${keepalive_block}
${ice_block} ${ice_block}
auth=${ext} auth=${ext}
aors=${ext} aors=${ext}
@@ -681,7 +694,7 @@ password=${pass}
type=aor type=aor
max_contacts=5 max_contacts=5
remove_existing=yes remove_existing=yes
qualify_frequency=60 qualify_frequency=30
EOF EOF
chown -R asterisk:asterisk /etc/asterisk 2>/dev/null || true chown -R asterisk:asterisk /etc/asterisk 2>/dev/null || true
@@ -1494,35 +1507,81 @@ verify_cidr_config() {
} }
configure_vlan_subnets() { configure_vlan_subnets() {
print_header "VLAN Configuration" print_header "VLAN / VPN Subnet Configuration"
load_config load_config
echo "VLAN Support for Asterisk Easy" echo "Additional Subnet Support for Easy Asterisk"
echo "================================================" echo "================================================"
echo "" echo ""
echo "If your network uses VLANs (Virtual LANs), you need to" echo "If your network uses VLANs or VPNs, you need to tell"
echo "tell Asterisk about all the local subnets to prevent" echo "Asterisk about all the local subnets so that:"
echo "calls from dropping after 30 seconds." 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 ""
echo "Example subnets:" echo "Example subnets:"
echo " 192.168.1.0/24 - Main network" echo " 192.168.1.0/24 - Main network"
echo " 192.168.10.0/24 - IoT VLAN" echo " 192.168.10.0/24 - IoT VLAN"
echo " 192.168.20.0/24 - Guest VLAN" echo " 100.64.0.0/10 - Tailscale VPN"
echo " 10.0.0.0/8 - Large private network" echo " 10.0.0.0/8 - WireGuard/OpenVPN"
echo "" 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} has_vlans=${has_vlans:-$HAS_VLANS}
if [[ "$has_vlans" =~ ^[Yy] ]]; then if [[ "$has_vlans" =~ ^[Yy] ]]; then
HAS_VLANS="y" HAS_VLANS="y"
echo "" 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 ""
echo "Enter VLAN subnets in CIDR notation, separated by spaces." echo "Enter ALL additional subnets (VLAN + VPN) in CIDR notation, separated by spaces."
echo "Example: 192.168.1.0/24 192.168.10.0/24 192.168.20.0/24" echo "Example: 192.168.10.0/24 100.64.0.0/10"
echo "" 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 if [[ -n "$vlan_input" ]]; then
VLAN_SUBNETS="$vlan_input" VLAN_SUBNETS="$vlan_input"
@@ -2841,10 +2900,17 @@ local_net=${vlan_subnet}"
local nat_settings="" local nat_settings=""
if [[ -n "$public_ip" && -n "$DOMAIN_NAME" ]]; then if [[ -n "$public_ip" && -n "$DOMAIN_NAME" ]]; then
# FQDN mode: full NAT settings with external addresses
nat_settings="external_media_address=$public_ip nat_settings="external_media_address=$public_ip
external_signaling_address=$public_ip external_signaling_address=$public_ip
${all_local_nets}" ${all_local_nets}"
print_info "NAT: Public IP=$public_ip, Server IP=$server_ip" 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 fi
cat > "$conf_file" << EOF cat > "$conf_file" << EOF
@@ -4312,6 +4378,11 @@ def add_device(name, category, extension, conn_type='lan', auto_answer=None):
elif auto_answer == 'no': elif auto_answer == 'no':
aa_tag = '[AA: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_config = f'''
; === Device: {name} ({category}) {aa_tag}=== ; === Device: {name} ({category}) {aa_tag}===
[{extension}] [{extension}]
@@ -4328,6 +4399,7 @@ direct_media=no
rtp_symmetric=yes rtp_symmetric=yes
force_rport=yes force_rport=yes
rewrite_contact=yes rewrite_contact=yes
{keepalive}
{ice} {ice}
auth={extension} auth={extension}
aors={extension} aors={extension}
@@ -4343,7 +4415,7 @@ password={password}
type=aor type=aor
max_contacts=5 max_contacts=5
remove_existing=yes remove_existing=yes
qualify_frequency=60 qualify_frequency=30
''' '''
with open(PJSIP_CONF, 'a') as f: with open(PJSIP_CONF, 'a') as f:
@@ -5878,7 +5950,7 @@ submenu_server() {
echo " 6) Verify CIDR/NAT config" echo " 6) Verify CIDR/NAT config"
echo " 7) Watch Live Logs" echo " 7) Watch Live Logs"
echo " 8) Router Doctor" echo " 8) Router Doctor"
echo " 9) Configure VLAN Subnets" echo " 9) Configure VLAN/VPN Subnets"
echo " 10) Provisioning Manager" echo " 10) Provisioning Manager"
echo " 11) Web Admin (Client Management)" echo " 11) Web Admin (Client Management)"
echo " 0) Back" echo " 0) Back"