Merge pull request #26 from outis1one/claude/asterisk-vpn-mobile-issue-Nt958
Claude/asterisk vpn mobile issue nt958
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
*.md
|
||||
LICENSE
|
||||
@@ -0,0 +1,92 @@
|
||||
# ================================================================
|
||||
# Easy Asterisk - Environment Configuration
|
||||
#
|
||||
# Setup:
|
||||
# 1. cp .env.example .env
|
||||
# 2. Set DOMAIN_NAME (the only required setting)
|
||||
# 3. docker compose up -d
|
||||
# 4. docker exec -it easy-asterisk easy-asterisk
|
||||
#
|
||||
# Port forwarding required on your router:
|
||||
# 5061/tcp → SIP TLS signaling
|
||||
# 3478/udp → STUN/TURN (NAT traversal + media relay)
|
||||
# 3478/tcp → TURN TCP fallback (for restrictive networks)
|
||||
# 10000-20000/udp → RTP media (or your custom range below)
|
||||
#
|
||||
# How it works:
|
||||
# - All SIP clients connect to DOMAIN_NAME:5061 (TLS)
|
||||
# - coturn handles NAT traversal (STUN) and media relay (TURN)
|
||||
# - Works from any network: LAN, cellular, Proton VPN, hotel WiFi
|
||||
# - TURN credentials are auto-generated if TURN_PASSWORD is empty
|
||||
# ================================================================
|
||||
|
||||
# ── Domain Name (REQUIRED) ────────────────────────────────────
|
||||
# The FQDN that points to this server's public IP.
|
||||
# This is what SIP clients use to connect.
|
||||
# Example: asterisk.yourdomain.com
|
||||
DOMAIN_NAME=
|
||||
|
||||
# ── Public IP ─────────────────────────────────────────────────
|
||||
# Your server's public IP address.
|
||||
# Leave empty to auto-detect (uses ifconfig.me).
|
||||
# Set manually if auto-detection fails (e.g., behind double NAT).
|
||||
PUBLIC_IP=
|
||||
|
||||
# ── TLS ───────────────────────────────────────────────────────
|
||||
# Always "y" for remote access. Self-signed certs are auto-generated.
|
||||
# For trusted certs (no client warnings), mount your Let's Encrypt
|
||||
# certs into /etc/asterisk/certs/ via docker compose volumes.
|
||||
ENABLE_TLS=y
|
||||
|
||||
# ── Local Network ─────────────────────────────────────────────
|
||||
# Your LAN CIDR. Auto-detected if empty.
|
||||
# Example: 192.168.1.0/24
|
||||
LOCAL_CIDR=
|
||||
|
||||
# ── Additional Subnets (optional) ─────────────────────────────
|
||||
# Only needed for site-to-site VPNs or VLANs where the server
|
||||
# has a direct route to client IPs (e.g., WireGuard, Tailscale).
|
||||
#
|
||||
# NOT needed for client-side VPNs (Proton, NordVPN, etc.)
|
||||
# - Those clients appear with random public IPs
|
||||
# - TURN handles media relay for them automatically
|
||||
#
|
||||
# Examples:
|
||||
# WireGuard: VLAN_SUBNETS=10.8.0.0/24
|
||||
# Tailscale: VLAN_SUBNETS=100.64.0.0/10
|
||||
# Multiple: VLAN_SUBNETS=10.8.0.0/24 10.10.0.0/24
|
||||
HAS_VLANS=n
|
||||
VLAN_SUBNETS=
|
||||
|
||||
# ── TURN/STUN Credentials ────────────────────────────────────
|
||||
# Used by coturn for TURN relay authentication.
|
||||
# If TURN_PASSWORD is empty, a random password is generated on
|
||||
# first startup and saved to /etc/easy-asterisk/config.
|
||||
#
|
||||
# These credentials are shared between coturn and Asterisk.
|
||||
# SIP clients do NOT need these - only the server uses them.
|
||||
TURN_USERNAME=easyasterisk
|
||||
TURN_PASSWORD=
|
||||
|
||||
# ── TURN Relay Port Range ─────────────────────────────────────
|
||||
# Ports coturn uses for media relay. Forward this range on your router.
|
||||
# Default is 100 ports (enough for ~50 simultaneous relayed calls).
|
||||
# Most calls use direct paths; TURN relay is the fallback.
|
||||
TURN_RELAY_MIN=49152
|
||||
TURN_RELAY_MAX=49252
|
||||
|
||||
# ── RTP Port Range ────────────────────────────────────────────
|
||||
# Asterisk's own RTP media ports. Forward this range on your router.
|
||||
# Default: 10000-20000 (10,000 ports)
|
||||
# For constrained environments: 10000-10200
|
||||
RTP_START=10000
|
||||
RTP_END=20000
|
||||
|
||||
# ── Web Admin ─────────────────────────────────────────────────
|
||||
# HTTP management interface. Access via browser at:
|
||||
# http://your-server:8080/clients
|
||||
#
|
||||
# For HTTPS: put this behind Caddy or nginx reverse proxy,
|
||||
# then set WEB_ADMIN_AUTH_DISABLED=true (let the proxy handle auth).
|
||||
WEB_ADMIN_PORT=8080
|
||||
WEB_ADMIN_AUTH_DISABLED=false
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# ================================================================
|
||||
# Easy Asterisk - Docker Container
|
||||
# Asterisk PBX with web admin and optional STUN support
|
||||
#
|
||||
# Usage:
|
||||
# docker compose up -d # Asterisk only
|
||||
# docker compose --profile stun up -d # Asterisk + self-hosted STUN
|
||||
# docker exec -it easy-asterisk easy-asterisk # Interactive management
|
||||
# docker exec -it easy-asterisk vpn-diagnostics # VPN diagnostics
|
||||
# docker exec -it easy-asterisk dns-whitelist # DNS whitelist check
|
||||
# ================================================================
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
# Install Asterisk and all dependencies (matches install_asterisk_packages)
|
||||
RUN echo "exit 101" > /usr/sbin/policy-rc.d && chmod +x /usr/sbin/policy-rc.d && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
asterisk \
|
||||
asterisk-core-sounds-en-gsm \
|
||||
asterisk-modules \
|
||||
openssl \
|
||||
curl \
|
||||
tcpdump \
|
||||
sngrep \
|
||||
python3 \
|
||||
iproute2 \
|
||||
net-tools \
|
||||
dnsutils \
|
||||
iputils-ping \
|
||||
procps \
|
||||
lsof \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -f /usr/sbin/policy-rc.d \
|
||||
&& ldconfig \
|
||||
&& update-ca-certificates 2>/dev/null || true
|
||||
|
||||
# Create required directories
|
||||
RUN mkdir -p \
|
||||
/etc/easy-asterisk \
|
||||
/etc/asterisk/certs \
|
||||
/var/lib/asterisk/static-http \
|
||||
/var/log/asterisk \
|
||||
/var/spool/asterisk \
|
||||
/var/run/asterisk \
|
||||
&& chown -R asterisk:asterisk \
|
||||
/etc/asterisk \
|
||||
/var/lib/asterisk \
|
||||
/var/log/asterisk \
|
||||
/var/spool/asterisk \
|
||||
/var/run/asterisk
|
||||
|
||||
# Docker detection marker (used by is_docker() in the script)
|
||||
RUN touch /.dockerenv
|
||||
|
||||
# Copy the main management script
|
||||
COPY easy-asterisk-v0.10.0.sh /usr/local/bin/easy-asterisk
|
||||
RUN chmod +x /usr/local/bin/easy-asterisk
|
||||
|
||||
# Copy diagnostic and utility scripts
|
||||
COPY scripts/vpn-diagnostics.sh /usr/local/bin/vpn-diagnostics
|
||||
COPY scripts/dns-whitelist.sh /usr/local/bin/dns-whitelist
|
||||
RUN chmod +x /usr/local/bin/vpn-diagnostics /usr/local/bin/dns-whitelist
|
||||
|
||||
# Copy entrypoint
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# SIP signaling
|
||||
EXPOSE 5060/udp
|
||||
EXPOSE 5060/tcp
|
||||
EXPOSE 5061/tcp
|
||||
|
||||
# Web admin + provisioning
|
||||
EXPOSE 8080/tcp
|
||||
EXPOSE 8088/tcp
|
||||
EXPOSE 8089/tcp
|
||||
|
||||
# STUN (if running coturn in same container)
|
||||
EXPOSE 3478/udp
|
||||
|
||||
# RTP media range (use --network host in production for full range)
|
||||
# Docker port-mapping 10000 ports is impractical; host networking recommended
|
||||
EXPOSE 10000-10100/udp
|
||||
|
||||
# Persistent data
|
||||
VOLUME ["/etc/asterisk", "/etc/easy-asterisk", "/var/log/asterisk"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD asterisk -rx "core show version" >/dev/null 2>&1 || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# ================================================================
|
||||
# Easy Asterisk - Docker Compose
|
||||
#
|
||||
# Usage:
|
||||
# docker compose up -d # Start everything
|
||||
# docker exec -it easy-asterisk easy-asterisk # Interactive management
|
||||
# docker exec -it easy-asterisk vpn-diagnostics # VPN diagnostics
|
||||
#
|
||||
# All clients connect via FQDN (TLS) regardless of their network.
|
||||
# coturn provides STUN (NAT detection) + TURN (media relay) so calls
|
||||
# work even behind strict firewalls, cellular NAT, or VPNs like Proton.
|
||||
# ================================================================
|
||||
|
||||
services:
|
||||
|
||||
# ── Asterisk PBX ───────────────────────────────────────────
|
||||
asterisk:
|
||||
build: .
|
||||
container_name: easy-asterisk
|
||||
# Host networking required for:
|
||||
# - RTP media ports (10000-20000 UDP) - too many to map individually
|
||||
# - Proper NAT detection and SIP Contact headers
|
||||
# - Direct access to coturn on localhost
|
||||
network_mode: host
|
||||
depends_on:
|
||||
coturn:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- asterisk-config:/etc/asterisk
|
||||
- easy-asterisk-config:/etc/easy-asterisk
|
||||
- asterisk-logs:/var/log/asterisk
|
||||
- asterisk-spool:/var/spool/asterisk
|
||||
- asterisk-lib:/var/lib/asterisk
|
||||
environment:
|
||||
# ── Domain (REQUIRED for remote access) ──
|
||||
# Your FQDN that points to this server's public IP
|
||||
- DOMAIN_NAME=${DOMAIN_NAME:?Set DOMAIN_NAME in .env}
|
||||
- ENABLE_TLS=${ENABLE_TLS:-y}
|
||||
|
||||
# ── Public IP ──
|
||||
# Auto-detected if empty. Set manually if detection fails.
|
||||
- PUBLIC_IP=${PUBLIC_IP:-}
|
||||
|
||||
# ── Local Network ──
|
||||
- LOCAL_CIDR=${LOCAL_CIDR:-}
|
||||
|
||||
# ── Additional Subnets ──
|
||||
# Space-separated CIDRs for VLANs, site-to-site VPNs, etc.
|
||||
# NOT needed for client-side VPNs (Proton, NordVPN) - TURN handles those
|
||||
- HAS_VLANS=${HAS_VLANS:-n}
|
||||
- VLAN_SUBNETS=${VLAN_SUBNETS:-}
|
||||
|
||||
# ── TURN/STUN Server ──
|
||||
# Points to the coturn service (auto-configured)
|
||||
- TURN_ENABLED=y
|
||||
- TURN_SERVER=${DOMAIN_NAME:?}:3478
|
||||
- TURN_USERNAME=${TURN_USERNAME:-easyasterisk}
|
||||
- TURN_PASSWORD=${TURN_PASSWORD:-}
|
||||
|
||||
# ── RTP Port Range ──
|
||||
- RTP_START=${RTP_START:-10000}
|
||||
- RTP_END=${RTP_END:-20000}
|
||||
|
||||
# ── Web Admin ──
|
||||
- WEB_ADMIN_PORT=${WEB_ADMIN_PORT:-8080}
|
||||
- WEB_ADMIN_AUTH_DISABLED=${WEB_ADMIN_AUTH_DISABLED:-false}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "asterisk", "-rx", "core show version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# ── TURN/STUN Relay Server (coturn) ──────────────────────────
|
||||
# Provides:
|
||||
# STUN - Tells clients their public IP (NAT detection)
|
||||
# TURN - Relays media when direct UDP paths are blocked
|
||||
# (corporate firewalls, cellular NAT, Proton VPN, etc.)
|
||||
#
|
||||
# Without TURN, calls work "sometimes" - with TURN, they always work.
|
||||
coturn:
|
||||
image: coturn/coturn:latest
|
||||
container_name: easy-asterisk-coturn
|
||||
network_mode: host
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
# Build coturn arguments
|
||||
ARGS="-n --listening-port=3478 --fingerprint --lt-cred-mech"
|
||||
ARGS="$$ARGS --user=${TURN_USERNAME:-easyasterisk}:${TURN_PASSWORD:-changeme}"
|
||||
ARGS="$$ARGS --realm=${DOMAIN_NAME:-localhost}"
|
||||
ARGS="$$ARGS --min-port=${TURN_RELAY_MIN:-49152}"
|
||||
ARGS="$$ARGS --max-port=${TURN_RELAY_MAX:-49252}"
|
||||
# Only add external-ip if PUBLIC_IP is set
|
||||
if [ -n "${PUBLIC_IP:-}" ]; then
|
||||
ARGS="$$ARGS --external-ip=${PUBLIC_IP}"
|
||||
fi
|
||||
ARGS="$$ARGS --no-tls --no-dtls --no-cli"
|
||||
ARGS="$$ARGS --no-multicast-peers --no-loopback-peers"
|
||||
ARGS="$$ARGS --log-file=stdout"
|
||||
exec turnserver $$ARGS
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ss -uln | grep -q ':3478'"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
asterisk-config:
|
||||
easy-asterisk-config:
|
||||
asterisk-logs:
|
||||
asterisk-spool:
|
||||
asterisk-lib:
|
||||
@@ -0,0 +1,327 @@
|
||||
#!/bin/bash
|
||||
# ================================================================
|
||||
# Easy Asterisk Docker Entrypoint
|
||||
#
|
||||
# Fully automated:
|
||||
# - Detects public IP
|
||||
# - Generates TURN credentials if not provided
|
||||
# - Configures Asterisk with FQDN, TLS, ICE, STUN, TURN
|
||||
# - Starts web admin + Asterisk
|
||||
# ================================================================
|
||||
|
||||
set -e
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${GREEN}[entrypoint]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[entrypoint]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[entrypoint]${NC} $1"; }
|
||||
|
||||
CONFIG_DIR="/etc/easy-asterisk"
|
||||
CONFIG_FILE="${CONFIG_DIR}/config"
|
||||
WEB_ADMIN_SCRIPT="/usr/local/bin/easy-asterisk-webadmin"
|
||||
|
||||
# ── Helper: generate random password ─────────────────────────
|
||||
gen_password() {
|
||||
openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 24
|
||||
}
|
||||
|
||||
# ── 1. Ensure asterisk user exists ───────────────────────────
|
||||
if ! id asterisk >/dev/null 2>&1; then
|
||||
useradd -r -s /bin/false -d /var/lib/asterisk asterisk 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ── 2. Detect public IP ──────────────────────────────────────
|
||||
PUBLIC_IP="${PUBLIC_IP:-}"
|
||||
if [[ -z "$PUBLIC_IP" ]]; then
|
||||
log_info "Auto-detecting public IP..."
|
||||
PUBLIC_IP=$(curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null || true)
|
||||
if [[ -z "$PUBLIC_IP" ]]; then
|
||||
PUBLIC_IP=$(curl -s -4 --connect-timeout 5 icanhazip.com 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -z "$PUBLIC_IP" ]]; then
|
||||
PUBLIC_IP=$(curl -s -4 --connect-timeout 5 api.ipify.org 2>/dev/null || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$PUBLIC_IP" ]]; then
|
||||
log_info "Public IP: ${PUBLIC_IP}"
|
||||
else
|
||||
log_warn "Could not detect public IP. Set PUBLIC_IP in .env"
|
||||
fi
|
||||
|
||||
# ── 3. Generate TURN password if not provided ─────────────────
|
||||
TURN_USERNAME="${TURN_USERNAME:-easyasterisk}"
|
||||
if [[ -z "${TURN_PASSWORD:-}" ]] || [[ "${TURN_PASSWORD}" == "changeme" ]]; then
|
||||
# Check if we already generated one previously
|
||||
if [[ -f "$CONFIG_FILE" ]] && grep -q "^TURN_PASSWORD=" "$CONFIG_FILE"; then
|
||||
TURN_PASSWORD=$(grep "^TURN_PASSWORD=" "$CONFIG_FILE" | cut -d'"' -f2)
|
||||
fi
|
||||
if [[ -z "${TURN_PASSWORD:-}" ]] || [[ "${TURN_PASSWORD}" == "changeme" ]]; then
|
||||
TURN_PASSWORD=$(gen_password)
|
||||
log_info "Generated TURN password (saved to config)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 4. Detect local network ──────────────────────────────────
|
||||
local_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
raw_cidr=$(ip -o -f inet addr show 2>/dev/null | awk '/scope global/ {print $4}' | head -1)
|
||||
default_cidr="$raw_cidr"
|
||||
if [[ "$raw_cidr" =~ \.([0-9]+)/([0-9]+)$ ]]; then
|
||||
default_cidr="${raw_cidr%.*}.0/${BASH_REMATCH[2]}"
|
||||
fi
|
||||
|
||||
# ── 5. Generate self-signed certs if missing ──────────────────
|
||||
if [[ ! -f /etc/asterisk/certs/server.crt ]]; then
|
||||
log_info "Generating self-signed TLS certificate..."
|
||||
mkdir -p /etc/asterisk/certs
|
||||
# Use DOMAIN_NAME as CN if available
|
||||
cn="${DOMAIN_NAME:-asterisk-local}"
|
||||
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
||||
-keyout /etc/asterisk/certs/server.key \
|
||||
-out /etc/asterisk/certs/server.crt \
|
||||
-subj "/CN=${cn}" 2>/dev/null
|
||||
chown asterisk:asterisk /etc/asterisk/certs/server.*
|
||||
chmod 644 /etc/asterisk/certs/server.crt
|
||||
chmod 600 /etc/asterisk/certs/server.key
|
||||
fi
|
||||
|
||||
# ── 6. Write config file ─────────────────────────────────────
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# Determine TURN/STUN server address
|
||||
turn_server="${TURN_SERVER:-${DOMAIN_NAME:-$local_ip}:3478}"
|
||||
|
||||
cat > "$CONFIG_FILE" << EOF
|
||||
# Easy Asterisk Configuration (Docker) - $(date)
|
||||
KIOSK_USER=""
|
||||
KIOSK_UID=""
|
||||
KIOSK_EXTENSION=""
|
||||
KIOSK_NAME=""
|
||||
SIP_PASSWORD=""
|
||||
ASTERISK_HOST="${DOMAIN_NAME:-$local_ip}"
|
||||
DOMAIN_NAME="${DOMAIN_NAME:-}"
|
||||
ENABLE_TLS="${ENABLE_TLS:-y}"
|
||||
HAS_VLANS="${HAS_VLANS:-n}"
|
||||
VLAN_SUBNETS="${VLAN_SUBNETS:-}"
|
||||
CERT_PATH=""
|
||||
KEY_PATH=""
|
||||
INSTALLED_SERVER="y"
|
||||
INSTALLED_CLIENT="n"
|
||||
CURRENT_PUBLIC_IP="${PUBLIC_IP}"
|
||||
PTT_DEVICE=""
|
||||
PTT_KEYCODE=""
|
||||
LOCAL_CIDR="${LOCAL_CIDR:-$default_cidr}"
|
||||
WEB_ADMIN_PORT="${WEB_ADMIN_PORT:-8080}"
|
||||
WEB_ADMIN_AUTH_DISABLED="${WEB_ADMIN_AUTH_DISABLED:-false}"
|
||||
VPN_ICE_ENABLED="y"
|
||||
CUSTOM_STUN_SERVER="${turn_server}"
|
||||
TURN_ENABLED="y"
|
||||
TURN_SERVER="${turn_server}"
|
||||
TURN_USERNAME="${TURN_USERNAME}"
|
||||
TURN_PASSWORD="${TURN_PASSWORD}"
|
||||
EOF
|
||||
chmod 644 "$CONFIG_FILE"
|
||||
|
||||
# ── 7. Initialize categories & rooms if missing ──────────────
|
||||
CATEGORIES_FILE="${CONFIG_DIR}/categories.conf"
|
||||
if [[ ! -f "$CATEGORIES_FILE" ]]; then
|
||||
log_info "Creating default device categories..."
|
||||
cat > "$CATEGORIES_FILE" << 'EOF'
|
||||
kiosks|Kiosks|yes|Fixed wall-mount tablets & intercoms
|
||||
mobile|Mobile|no|Phones & tablets (ring normally)
|
||||
custom|Custom|no|Custom configuration
|
||||
EOF
|
||||
fi
|
||||
|
||||
ROOMS_FILE="${CONFIG_DIR}/rooms.conf"
|
||||
if [[ ! -f "$ROOMS_FILE" ]]; then
|
||||
cat > "$ROOMS_FILE" << 'EOF'
|
||||
# ext|name|members|timeout|type
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ── 8. Generate Asterisk configs ─────────────────────────────
|
||||
|
||||
# Build local_net entries
|
||||
all_local_nets="local_net=${LOCAL_CIDR:-$default_cidr}"
|
||||
if [[ "${HAS_VLANS:-n}" == "y" && -n "${VLAN_SUBNETS:-}" ]]; then
|
||||
for subnet in $VLAN_SUBNETS; do
|
||||
all_local_nets="${all_local_nets}
|
||||
local_net=${subnet}"
|
||||
done
|
||||
fi
|
||||
|
||||
# NAT settings - always include external addresses for FQDN mode
|
||||
nat_settings=""
|
||||
if [[ -n "$PUBLIC_IP" ]]; then
|
||||
nat_settings="external_media_address=${PUBLIC_IP}
|
||||
external_signaling_address=${PUBLIC_IP}
|
||||
${all_local_nets}"
|
||||
else
|
||||
nat_settings="${all_local_nets}"
|
||||
fi
|
||||
|
||||
# ── pjsip.conf (only if empty/missing - preserves existing devices) ──
|
||||
if [[ ! -f /etc/asterisk/pjsip.conf ]] || [[ ! -s /etc/asterisk/pjsip.conf ]]; then
|
||||
log_info "Generating PJSIP configuration..."
|
||||
cat > /etc/asterisk/pjsip.conf << EOF
|
||||
; Easy Asterisk (Docker) - FQDN: ${DOMAIN_NAME:-none}
|
||||
[global]
|
||||
type=global
|
||||
user_agent=EasyAsterisk
|
||||
|
||||
[transport-udp]
|
||||
type=transport
|
||||
protocol=udp
|
||||
bind=0.0.0.0:5060
|
||||
; Server IP: ${local_ip} | Public IP: ${PUBLIC_IP:-unknown}
|
||||
${nat_settings}
|
||||
|
||||
[transport-tcp]
|
||||
type=transport
|
||||
protocol=tcp
|
||||
bind=0.0.0.0:5060
|
||||
; Server IP: ${local_ip} | Public IP: ${PUBLIC_IP:-unknown}
|
||||
${nat_settings}
|
||||
|
||||
[transport-tls]
|
||||
type=transport
|
||||
protocol=tls
|
||||
bind=0.0.0.0:5061
|
||||
; Server IP: ${local_ip} | Public IP: ${PUBLIC_IP:-unknown}
|
||||
cert_file=/etc/asterisk/certs/server.crt
|
||||
priv_key_file=/etc/asterisk/certs/server.key
|
||||
ca_list_file=/etc/ssl/certs/ca-certificates.crt
|
||||
method=tlsv1_2
|
||||
${nat_settings}
|
||||
|
||||
EOF
|
||||
chown asterisk:asterisk /etc/asterisk/pjsip.conf
|
||||
else
|
||||
# Update NAT settings in existing pjsip.conf transports if public IP changed
|
||||
if [[ -n "$PUBLIC_IP" ]]; then
|
||||
current_ext=$(grep "^external_media_address=" /etc/asterisk/pjsip.conf 2>/dev/null | head -1 | cut -d= -f2)
|
||||
if [[ "$current_ext" != "$PUBLIC_IP" && -n "$current_ext" ]]; then
|
||||
log_info "Updating public IP in pjsip.conf: ${current_ext} -> ${PUBLIC_IP}"
|
||||
sed -i "s|external_media_address=.*|external_media_address=${PUBLIC_IP}|g" /etc/asterisk/pjsip.conf
|
||||
sed -i "s|external_signaling_address=.*|external_signaling_address=${PUBLIC_IP}|g" /etc/asterisk/pjsip.conf
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── rtp.conf (always regenerated - includes TURN credentials) ──
|
||||
log_info "Configuring RTP with ICE + STUN + TURN..."
|
||||
cat > /etc/asterisk/rtp.conf << EOF
|
||||
[general]
|
||||
rtpstart=${RTP_START:-10000}
|
||||
rtpend=${RTP_END:-20000}
|
||||
strictrtp=yes
|
||||
icesupport=yes
|
||||
stunaddr=${turn_server}
|
||||
turnaddr=${turn_server}
|
||||
turnusername=${TURN_USERNAME}
|
||||
turnpassword=${TURN_PASSWORD}
|
||||
EOF
|
||||
chown asterisk:asterisk /etc/asterisk/rtp.conf
|
||||
|
||||
# ── extensions.conf (only if missing) ──
|
||||
if [[ ! -f /etc/asterisk/extensions.conf ]] || [[ ! -s /etc/asterisk/extensions.conf ]]; then
|
||||
log_info "Generating dialplan..."
|
||||
cat > /etc/asterisk/extensions.conf << 'EOF'
|
||||
[general]
|
||||
static=yes
|
||||
writeprotect=no
|
||||
[default]
|
||||
exten => _X.,1,Hangup()
|
||||
[intercom]
|
||||
EOF
|
||||
chown asterisk:asterisk /etc/asterisk/extensions.conf
|
||||
fi
|
||||
|
||||
# ── Other core configs (only if missing) ──
|
||||
if [[ ! -f /etc/asterisk/asterisk.conf ]]; then
|
||||
cat > /etc/asterisk/asterisk.conf << 'EOF'
|
||||
[directories]
|
||||
[options]
|
||||
runuser = asterisk
|
||||
rungroup = asterisk
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [[ ! -f /etc/asterisk/logger.conf ]]; then
|
||||
cat > /etc/asterisk/logger.conf << 'EOF'
|
||||
[general]
|
||||
[logfiles]
|
||||
console => notice,warning,error
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [[ ! -f /etc/asterisk/modules.conf ]]; then
|
||||
cat > /etc/asterisk/modules.conf << 'EOF'
|
||||
[modules]
|
||||
autoload=yes
|
||||
noload => chan_sip.so
|
||||
noload => chan_iax2.so
|
||||
load => res_pjsip.so
|
||||
load => res_pjsip_session.so
|
||||
load => res_pjsip_logger.so
|
||||
load => chan_pjsip.so
|
||||
load => codec_ulaw.so
|
||||
load => codec_alaw.so
|
||||
load => codec_g722.so
|
||||
load => codec_opus.so
|
||||
load => res_rtp_asterisk.so
|
||||
load => app_dial.so
|
||||
load => app_page.so
|
||||
load => pbx_config.so
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ── 9. Fix permissions ───────────────────────────────────────
|
||||
chown -R asterisk:asterisk /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk 2>/dev/null || true
|
||||
|
||||
# ── 10. Start Web Admin in background ─────────────────────────
|
||||
# The web admin script is generated by the 'easy-asterisk' management tool.
|
||||
# On first run: docker exec -it easy-asterisk easy-asterisk → Web Admin menu → Start
|
||||
if [[ -f "$WEB_ADMIN_SCRIPT" ]]; then
|
||||
log_info "Starting Web Admin on port ${WEB_ADMIN_PORT:-8080}..."
|
||||
WEBADMIN_PORT="${WEB_ADMIN_PORT:-8080}" \
|
||||
WEBADMIN_AUTH_DISABLED="${WEB_ADMIN_AUTH_DISABLED:-false}" \
|
||||
python3 "$WEB_ADMIN_SCRIPT" &
|
||||
fi
|
||||
|
||||
# ── 11. Signal handling for clean shutdown ────────────────────
|
||||
cleanup() {
|
||||
log_info "Shutting down..."
|
||||
pkill -f "easy-asterisk-webadmin" 2>/dev/null || true
|
||||
asterisk -rx "core stop now" 2>/dev/null || true
|
||||
exit 0
|
||||
}
|
||||
trap cleanup SIGTERM SIGINT
|
||||
|
||||
# ── 12. Start Asterisk ───────────────────────────────────────
|
||||
log_info "Starting Asterisk PBX..."
|
||||
echo ""
|
||||
echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Easy Asterisk (Docker)${NC}"
|
||||
echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e " FQDN: ${GREEN}${DOMAIN_NAME:-not set}${NC}"
|
||||
echo -e " Public IP: ${GREEN}${PUBLIC_IP:-unknown}${NC}"
|
||||
echo -e " TURN/STUN: ${GREEN}${turn_server}${NC}"
|
||||
echo -e " TLS: ${GREEN}Enabled (port 5061)${NC}"
|
||||
echo -e " ICE: ${GREEN}Enabled${NC}"
|
||||
echo -e "${CYAN}──────────────────────────────────────────────────────────────${NC}"
|
||||
echo -e " SIP clients connect to: ${GREEN}${DOMAIN_NAME:-$local_ip}:5061${NC} (TLS)"
|
||||
echo -e " Web Admin: ${GREEN}http://${local_ip}:${WEB_ADMIN_PORT:-8080}/clients${NC}"
|
||||
echo -e "${CYAN}──────────────────────────────────────────────────────────────${NC}"
|
||||
echo -e " Management: docker exec -it easy-asterisk easy-asterisk"
|
||||
echo -e " Diagnostics: docker exec -it easy-asterisk vpn-diagnostics"
|
||||
echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
|
||||
exec asterisk -f -U asterisk -G asterisk
|
||||
@@ -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.
|
||||
|
||||
+768
-213
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,280 @@
|
||||
#!/bin/bash
|
||||
# ================================================================
|
||||
# DNS Whitelist Checker for Easy Asterisk
|
||||
#
|
||||
# Checks which domains need to be whitelisted when DNS filtering
|
||||
# is active on the server, caller, or receiver networks.
|
||||
#
|
||||
# Usage: dns-whitelist [--check] [--sipnetic] [--linphone]
|
||||
# ================================================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
CONFIG_FILE="/etc/easy-asterisk/config"
|
||||
CHECK_MODE=false
|
||||
SHOW_SIPNETIC=false
|
||||
SHOW_LINPHONE=false
|
||||
SHOW_ALL=true
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--check) CHECK_MODE=true; shift ;;
|
||||
--sipnetic) SHOW_SIPNETIC=true; SHOW_ALL=false; shift ;;
|
||||
--linphone) SHOW_LINPHONE=true; SHOW_ALL=false; shift ;;
|
||||
--help|-h)
|
||||
echo "Usage: dns-whitelist [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --check Test reachability of each domain"
|
||||
echo " --sipnetic Show Sipnetic-specific domains"
|
||||
echo " --linphone Show Linphone-specific domains"
|
||||
echo " --help Show this help"
|
||||
exit 0
|
||||
;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN} $1${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
check_dns() {
|
||||
local domain="$1"
|
||||
local port="$2"
|
||||
local proto="${3:-tcp}"
|
||||
|
||||
if $CHECK_MODE; then
|
||||
# DNS resolution test
|
||||
if nslookup "$domain" >/dev/null 2>&1; then
|
||||
echo -e " ${GREEN}✓ DNS resolves${NC}"
|
||||
else
|
||||
echo -e " ${RED}✗ DNS BLOCKED - add to whitelist${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Connectivity test
|
||||
if [[ "$proto" == "udp" ]]; then
|
||||
# UDP - just check DNS resolution (can't reliably test UDP connectivity)
|
||||
echo -e " ${CYAN}→ UDP port ${port} (cannot test remotely)${NC}"
|
||||
else
|
||||
if curl -s --connect-timeout 5 "https://${domain}" >/dev/null 2>&1 || \
|
||||
curl -s --connect-timeout 5 "http://${domain}" >/dev/null 2>&1; then
|
||||
echo -e " ${GREEN}✓ Reachable${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}! Connection failed (may be expected)${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Load config if available
|
||||
source "$CONFIG_FILE" 2>/dev/null || true
|
||||
|
||||
print_header "DNS Whitelist for Easy Asterisk"
|
||||
|
||||
echo -e "${BOLD}Your Setup:${NC}"
|
||||
if [[ -n "$DOMAIN_NAME" ]]; then
|
||||
echo -e " Mode: FQDN/Internet (${DOMAIN_NAME})"
|
||||
else
|
||||
echo -e " Mode: LAN/VPN (no domain configured)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# SECTION 1: ASTERISK SERVER DOMAINS
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
if $SHOW_ALL; then
|
||||
echo -e "${BOLD}━━━ 1. ASTERISK SERVER (whitelist on server's DNS filter) ━━━${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required for LAN/VPN mode:${NC}"
|
||||
echo -e " ${GREEN}None${NC} - Asterisk needs no internet after installation"
|
||||
echo -e " SIP operates over direct IP connections, no DNS involved"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required for FQDN/Internet mode only:${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e " ${CYAN}ifconfig.me${NC} (HTTPS 443)"
|
||||
echo -e " Purpose: Auto-detect public IP for NAT settings"
|
||||
echo -e " When: Only during config regeneration"
|
||||
check_dns "ifconfig.me" "443"
|
||||
echo ""
|
||||
|
||||
echo -e " ${CYAN}icanhazip.com${NC} (HTTPS 443)"
|
||||
echo -e " Purpose: Fallback public IP detection"
|
||||
check_dns "icanhazip.com" "443"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required if ICE/STUN enabled:${NC}"
|
||||
echo ""
|
||||
|
||||
# Check what STUN server is configured
|
||||
stun_server=""
|
||||
if [[ -f /etc/asterisk/rtp.conf ]]; then
|
||||
stun_server=$(grep "^stunaddr=" /etc/asterisk/rtp.conf 2>/dev/null | cut -d= -f2)
|
||||
fi
|
||||
|
||||
if [[ -n "$stun_server" ]]; then
|
||||
stun_host=$(echo "$stun_server" | cut -d: -f1)
|
||||
stun_port=$(echo "$stun_server" | cut -d: -f2)
|
||||
stun_port="${stun_port:-3478}"
|
||||
echo -e " ${CYAN}${stun_host}${NC} (UDP ${stun_port})"
|
||||
echo -e " Purpose: STUN NAT discovery"
|
||||
echo -e " ${YELLOW}Tip: Use self-hosted coturn to avoid this dependency${NC}"
|
||||
check_dns "$stun_host" "$stun_port" "udp"
|
||||
else
|
||||
echo -e " ${GREEN}No external STUN server configured${NC}"
|
||||
echo -e " To use self-hosted: docker compose --profile stun up -d"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required for package updates only:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}archive.ubuntu.com${NC} / ${CYAN}security.ubuntu.com${NC} (HTTPS 443)"
|
||||
echo -e " Purpose: apt package updates"
|
||||
echo -e " When: Only during install/update (not runtime)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required for TLS certificates:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}acme-v02.api.letsencrypt.org${NC} (HTTPS 443)"
|
||||
echo -e " Purpose: Let's Encrypt certificate issuance"
|
||||
echo -e " When: Only if using Let's Encrypt / Certbot / Caddy"
|
||||
if $CHECK_MODE; then
|
||||
check_dns "acme-v02.api.letsencrypt.org" "443"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# SECTION 2: SIPNETIC (Mobile Client) DOMAINS
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
if $SHOW_ALL || $SHOW_SIPNETIC; then
|
||||
echo -e "${BOLD}━━━ 2. SIPNETIC CLIENT (whitelist on caller/receiver DNS) ━━━${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required for SIP calls:${NC}"
|
||||
echo -e " ${GREEN}None${NC} - Configure Sipnetic with the server's IP address directly"
|
||||
echo -e " SIP registration and calls use IP:port, not DNS"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Sipnetic app domains (for app functionality):${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}onesip.io${NC} / ${CYAN}api.onesip.io${NC}"
|
||||
echo -e " Purpose: Sipnetic account/licensing (free tier works offline)"
|
||||
echo -e " Required: Only for initial setup or account sync"
|
||||
if $CHECK_MODE; then
|
||||
check_dns "onesip.io" "443"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo -e " ${CYAN}play.google.com${NC} / ${CYAN}apps.apple.com${NC}"
|
||||
echo -e " Purpose: App updates"
|
||||
echo -e " Required: Only for installing/updating the app"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}If STUN configured in Sipnetic:${NC}"
|
||||
echo ""
|
||||
echo -e " The STUN server domain configured in Sipnetic's settings"
|
||||
echo -e " needs to resolve on the mobile device's network."
|
||||
echo ""
|
||||
echo -e " ${YELLOW}Recommendation: Use the Asterisk server's VPN IP as STUN${NC}"
|
||||
echo -e " ${YELLOW}server (if running self-hosted coturn), avoiding DNS entirely.${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Sipnetic Configuration for DNS-Filtered Networks:${NC}"
|
||||
echo ""
|
||||
echo -e " Server: ${CYAN}<server-vpn-ip>${NC} (not a hostname)"
|
||||
echo -e " Port: ${CYAN}5060${NC} (UDP, LAN/VPN mode)"
|
||||
echo -e " Transport: ${CYAN}UDP${NC}"
|
||||
echo -e " STUN: ${CYAN}<server-vpn-ip>:3478${NC} (if self-hosted coturn)"
|
||||
echo -e " or leave blank if VPN provides direct routing"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# SECTION 3: LINPHONE (Mobile Client) DOMAINS
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
if $SHOW_ALL || $SHOW_LINPHONE; then
|
||||
echo -e "${BOLD}━━━ 3. LINPHONE CLIENT (whitelist on caller/receiver DNS) ━━━${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Required for SIP calls:${NC}"
|
||||
echo -e " ${GREEN}None${NC} - Same as Sipnetic, configure with server IP directly"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}Linphone app domains:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}linphone.org${NC} / ${CYAN}sip.linphone.org${NC}"
|
||||
echo -e " Purpose: Default Linphone SIP proxy (NOT needed for Easy Asterisk)"
|
||||
echo -e " Required: ${GREEN}No${NC} - We use our own Asterisk server"
|
||||
echo ""
|
||||
echo -e " ${CYAN}subscribe.linphone.org${NC}"
|
||||
echo -e " Purpose: Push notifications (may be needed for background calls)"
|
||||
echo -e " Required: Only if you need calls to ring when app is backgrounded"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}For remote provisioning:${NC}"
|
||||
echo ""
|
||||
echo -e " If using Easy Asterisk's HTTP provisioning:"
|
||||
echo -e " The phone must reach ${CYAN}http://<server-ip>:8088/static/linphone.xml${NC}"
|
||||
echo -e " This is an IP address, so no DNS whitelist needed."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# SECTION 4: SUMMARY
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
if $SHOW_ALL; then
|
||||
print_header "Quick Reference - Minimum DNS Whitelist"
|
||||
|
||||
echo -e "${BOLD}For LAN/VPN mode (no internet calling):${NC}"
|
||||
echo ""
|
||||
echo -e " Server DNS filter: ${GREEN}No domains needed${NC}"
|
||||
echo -e " Client DNS filter: ${GREEN}No domains needed${NC}"
|
||||
echo -e " (Configure everything by IP address)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}For LAN/VPN + self-hosted STUN (coturn):${NC}"
|
||||
echo ""
|
||||
echo -e " Server DNS filter: ${GREEN}No domains needed${NC}"
|
||||
echo -e " Client DNS filter: ${GREEN}No domains needed${NC}"
|
||||
echo -e " (STUN server reached by VPN IP, not hostname)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}For LAN/VPN + Google STUN:${NC}"
|
||||
echo ""
|
||||
echo -e " Server DNS filter: ${YELLOW}stun.l.google.com${NC}"
|
||||
echo -e " Client DNS filter: ${YELLOW}stun.l.google.com${NC} (if also set in Sipnetic)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}For FQDN/Internet mode:${NC}"
|
||||
echo ""
|
||||
echo -e " Server DNS filter: ${YELLOW}ifconfig.me, icanhazip.com, stun.l.google.com${NC}"
|
||||
echo -e " ${YELLOW}acme-v02.api.letsencrypt.org${NC} (if using LE certs)"
|
||||
echo -e " Client DNS filter: ${YELLOW}Your domain name (${DOMAIN_NAME:-yourdomain.com})${NC}"
|
||||
echo ""
|
||||
|
||||
print_header "Recommendation for DNS-Filtered Environments"
|
||||
|
||||
echo -e " ${GREEN}Use LAN/VPN mode + self-hosted coturn (STUN-only)${NC}"
|
||||
echo -e " ${GREEN}= Zero external DNS dependencies${NC}"
|
||||
echo ""
|
||||
echo -e " Setup: docker compose --profile stun up -d"
|
||||
echo -e " Then configure STUN as your server's VPN IP:3478"
|
||||
echo -e " No hostnames, no DNS, everything by IP."
|
||||
echo ""
|
||||
fi
|
||||
@@ -0,0 +1,306 @@
|
||||
#!/bin/bash
|
||||
# ================================================================
|
||||
# VPN Diagnostics for Easy Asterisk
|
||||
#
|
||||
# Tests whether your third-party VPN setup needs STUN/TURN
|
||||
# and validates connectivity between Asterisk and VPN clients.
|
||||
#
|
||||
# Usage: vpn-diagnostics [--auto] [--client-ip <ip>]
|
||||
# ================================================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
CONFIG_FILE="/etc/easy-asterisk/config"
|
||||
RESULTS=()
|
||||
WARNINGS=()
|
||||
CLIENT_IP=""
|
||||
AUTO_MODE=false
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--auto) AUTO_MODE=true; shift ;;
|
||||
--client-ip) CLIENT_IP="$2"; shift 2 ;;
|
||||
--help|-h)
|
||||
echo "Usage: vpn-diagnostics [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --auto Non-interactive mode"
|
||||
echo " --client-ip <ip> Test connectivity to specific VPN client"
|
||||
echo " --help Show this help"
|
||||
exit 0
|
||||
;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN} $1${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
pass() { echo -e " ${GREEN}✓${NC} $1"; RESULTS+=("PASS: $1"); }
|
||||
fail() { echo -e " ${RED}✗${NC} $1"; RESULTS+=("FAIL: $1"); }
|
||||
warn() { echo -e " ${YELLOW}!${NC} $1"; WARNINGS+=("$1"); }
|
||||
info() { echo -e " ${CYAN}→${NC} $1"; }
|
||||
|
||||
# ── Test 1: Detect network interfaces ────────────────────────
|
||||
print_header "VPN Diagnostics for Easy Asterisk"
|
||||
|
||||
echo -e "${BOLD}1. Network Interface Detection${NC}"
|
||||
echo ""
|
||||
|
||||
# Detect primary LAN interface
|
||||
primary_ip=$(hostname -I | awk '{print $1}')
|
||||
info "Primary IP: ${primary_ip}"
|
||||
|
||||
# Detect VPN interfaces (tun, tap, wg, tailscale, utun, ppp)
|
||||
vpn_found=false
|
||||
vpn_ips=()
|
||||
vpn_ifaces=()
|
||||
|
||||
while IFS= read -r line; do
|
||||
iface=$(echo "$line" | awk '{print $2}' | tr -d ':')
|
||||
ip_addr=$(echo "$line" | awk '{print $4}' | cut -d'/' -f1)
|
||||
|
||||
# Check for VPN interface patterns
|
||||
if [[ "$iface" =~ ^(tun|tap|wg|tailscale|utun|ppp|nordlynx|proton|mullvad) ]] || \
|
||||
[[ "$ip_addr" =~ ^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|100\.64\.|100\.96\.|100\.100\.) ]]; then
|
||||
vpn_found=true
|
||||
vpn_ips+=("$ip_addr")
|
||||
vpn_ifaces+=("$iface")
|
||||
pass "VPN interface detected: ${iface} (${ip_addr})"
|
||||
fi
|
||||
done < <(ip -o -f inet addr show scope global 2>/dev/null)
|
||||
|
||||
if ! $vpn_found; then
|
||||
warn "No VPN interface detected on server"
|
||||
info "If your VPN runs on the router (not this server), that's expected"
|
||||
info "The VPN subnet should be added via VLAN/VPN subnet configuration"
|
||||
fi
|
||||
|
||||
# ── Test 2: Check Asterisk PJSIP transport configuration ─────
|
||||
echo ""
|
||||
echo -e "${BOLD}2. Asterisk Transport Configuration${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ -f /etc/asterisk/pjsip.conf ]]; then
|
||||
# Check local_net entries
|
||||
local_nets=$(grep "^local_net=" /etc/asterisk/pjsip.conf 2>/dev/null | sort -u)
|
||||
if [[ -n "$local_nets" ]]; then
|
||||
while IFS= read -r net; do
|
||||
info "Transport local_net: ${net#local_net=}"
|
||||
done <<< "$local_nets"
|
||||
|
||||
# Check if VPN subnets are included
|
||||
for vpn_ip in "${vpn_ips[@]}"; do
|
||||
vpn_subnet=$(echo "$vpn_ip" | sed 's/\.[0-9]*$/.0\/24/')
|
||||
if echo "$local_nets" | grep -q "$vpn_subnet"; then
|
||||
pass "VPN subnet ${vpn_subnet} included in transport"
|
||||
else
|
||||
fail "VPN subnet ${vpn_subnet} NOT in transport local_net"
|
||||
warn "Add via: Server Settings → Configure VLAN/VPN Subnets"
|
||||
fi
|
||||
done
|
||||
else
|
||||
warn "No local_net entries found in transport (basic LAN mode)"
|
||||
fi
|
||||
|
||||
# Check transport types
|
||||
if grep -q "transport=transport-udp" /etc/asterisk/pjsip.conf; then
|
||||
pass "UDP transport configured for LAN/VPN devices"
|
||||
fi
|
||||
if grep -q "transport=transport-tls" /etc/asterisk/pjsip.conf; then
|
||||
pass "TLS transport configured for FQDN devices"
|
||||
fi
|
||||
else
|
||||
fail "pjsip.conf not found"
|
||||
fi
|
||||
|
||||
# ── Test 3: Check RTP and ICE/STUN configuration ─────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}3. RTP / ICE / STUN Configuration${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ -f /etc/asterisk/rtp.conf ]]; then
|
||||
rtp_start=$(grep "^rtpstart=" /etc/asterisk/rtp.conf | cut -d= -f2)
|
||||
rtp_end=$(grep "^rtpend=" /etc/asterisk/rtp.conf | cut -d= -f2)
|
||||
info "RTP port range: ${rtp_start:-10000}-${rtp_end:-20000}"
|
||||
|
||||
if grep -q "^icesupport=yes" /etc/asterisk/rtp.conf; then
|
||||
pass "ICE support enabled"
|
||||
stun_addr=$(grep "^stunaddr=" /etc/asterisk/rtp.conf | cut -d= -f2)
|
||||
if [[ -n "$stun_addr" ]]; then
|
||||
info "STUN server: ${stun_addr}"
|
||||
|
||||
# Test STUN server reachability
|
||||
stun_host=$(echo "$stun_addr" | cut -d: -f1)
|
||||
stun_port=$(echo "$stun_addr" | cut -d: -f2)
|
||||
stun_port="${stun_port:-3478}"
|
||||
|
||||
if command -v nslookup &>/dev/null && nslookup "$stun_host" >/dev/null 2>&1; then
|
||||
pass "STUN server DNS resolves: ${stun_host}"
|
||||
else
|
||||
fail "Cannot resolve STUN server: ${stun_host}"
|
||||
warn "Add ${stun_host} to DNS whitelist"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
info "ICE support disabled (standard for LAN/VPN mode)"
|
||||
warn "If audio fails over VPN, enable ICE via: Server Settings → VPN STUN/ICE"
|
||||
fi
|
||||
else
|
||||
warn "rtp.conf not found"
|
||||
fi
|
||||
|
||||
# ── Test 4: Check endpoint ICE settings ───────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}4. Per-Device ICE Configuration${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ -f /etc/asterisk/pjsip.conf ]]; then
|
||||
device_count=$(grep -c "^; === Device:" /etc/asterisk/pjsip.conf 2>/dev/null || echo 0)
|
||||
ice_device_count=$(grep -c "^ice_support=yes" /etc/asterisk/pjsip.conf 2>/dev/null || echo 0)
|
||||
info "Total devices: ${device_count}"
|
||||
info "Devices with ICE: ${ice_device_count}"
|
||||
|
||||
if [[ "$device_count" -gt 0 && "$ice_device_count" -eq 0 ]]; then
|
||||
warn "No devices have ICE enabled"
|
||||
info "For third-party VPNs with NAT, enable ICE via VPN STUN/ICE menu"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Test 5: VPN client connectivity ──────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}5. VPN Client Connectivity${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ -z "$CLIENT_IP" ]] && ! $AUTO_MODE; then
|
||||
echo " Enter a VPN client IP to test connectivity (or press Enter to skip):"
|
||||
read -p " Client VPN IP: " CLIENT_IP
|
||||
fi
|
||||
|
||||
if [[ -n "$CLIENT_IP" ]]; then
|
||||
# Ping test
|
||||
if ping -c 2 -W 3 "$CLIENT_IP" >/dev/null 2>&1; then
|
||||
pass "Ping to ${CLIENT_IP} succeeded"
|
||||
else
|
||||
fail "Ping to ${CLIENT_IP} failed"
|
||||
warn "VPN routing issue - client may not be reachable"
|
||||
fi
|
||||
|
||||
# SIP port test (UDP 5060)
|
||||
if command -v nc &>/dev/null; then
|
||||
if nc -z -u -w 3 "$CLIENT_IP" 5060 2>/dev/null; then
|
||||
pass "UDP 5060 reachable on ${CLIENT_IP}"
|
||||
else
|
||||
info "UDP 5060 probe inconclusive (normal for filtered VPNs)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
info "Skipping client connectivity test (no IP provided)"
|
||||
fi
|
||||
|
||||
# ── Test 6: NAT type detection ───────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}6. NAT Type Analysis${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if server is behind NAT
|
||||
if [[ -n "$primary_ip" ]]; then
|
||||
public_ip=$(curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null || echo "")
|
||||
if [[ -n "$public_ip" ]]; then
|
||||
if [[ "$primary_ip" == "$public_ip" ]]; then
|
||||
pass "Server has public IP (no NAT)"
|
||||
else
|
||||
info "Server behind NAT: ${primary_ip} → ${public_ip}"
|
||||
info "This is normal for VPN setups where traffic stays on VPN"
|
||||
fi
|
||||
else
|
||||
info "Cannot detect public IP (DNS filtering or no internet)"
|
||||
info "Not needed for LAN/VPN mode"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Test 7: Asterisk registration status ─────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}7. Asterisk Registration Status${NC}"
|
||||
echo ""
|
||||
|
||||
if command -v asterisk &>/dev/null; then
|
||||
reg_output=$(asterisk -rx "pjsip show endpoints" 2>/dev/null || echo "")
|
||||
if [[ -n "$reg_output" ]]; then
|
||||
online_count=$(echo "$reg_output" | grep -c "Avail" 2>/dev/null || echo 0)
|
||||
offline_count=$(echo "$reg_output" | grep -c "Unavail" 2>/dev/null || echo 0)
|
||||
info "Endpoints online: ${online_count}"
|
||||
info "Endpoints offline: ${offline_count}"
|
||||
|
||||
if [[ "$offline_count" -gt 0 ]]; then
|
||||
warn "Some endpoints are offline - check VPN connectivity"
|
||||
echo "$reg_output" | grep "Unavail" | while IFS= read -r line; do
|
||||
info " Offline: $line"
|
||||
done
|
||||
fi
|
||||
else
|
||||
info "Asterisk not running or no endpoints configured"
|
||||
fi
|
||||
else
|
||||
info "Asterisk CLI not available"
|
||||
fi
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────
|
||||
print_header "Diagnostic Summary"
|
||||
|
||||
fail_count=0
|
||||
pass_count=0
|
||||
for result in "${RESULTS[@]}"; do
|
||||
if [[ "$result" == FAIL* ]]; then
|
||||
((fail_count++))
|
||||
elif [[ "$result" == PASS* ]]; then
|
||||
((pass_count++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " Passed: ${GREEN}${pass_count}${NC}"
|
||||
echo -e " Failed: ${RED}${fail_count}${NC}"
|
||||
echo -e " Warnings: ${YELLOW}${#WARNINGS[@]}${NC}"
|
||||
|
||||
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${BOLD}Recommendations:${NC}"
|
||||
for w in "${WARNINGS[@]}"; do
|
||||
echo -e " ${YELLOW}→${NC} $w"
|
||||
done
|
||||
fi
|
||||
|
||||
# ── STUN Recommendation ─────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Do you need STUN?${NC}"
|
||||
echo ""
|
||||
|
||||
if $vpn_found; then
|
||||
echo -e " VPN detected on this server."
|
||||
echo -e " ${GREEN}If your VPN provides direct routing (both sides get VPN IPs),${NC}"
|
||||
echo -e " ${GREEN}STUN is likely NOT needed.${NC}"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}If audio works one-way or not at all, enable STUN:${NC}"
|
||||
echo -e " 1. docker compose --profile stun up -d (self-hosted STUN)"
|
||||
echo -e " 2. Or via easy-asterisk: Server Settings → VPN STUN/ICE"
|
||||
else
|
||||
echo -e " No VPN interface found on server."
|
||||
echo -e " ${YELLOW}If VPN runs on router/firewall:${NC}"
|
||||
echo -e " - Add VPN subnet via: Server Settings → VLAN/VPN Subnets"
|
||||
echo -e " - If audio still fails, enable STUN for NAT traversal"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
Reference in New Issue
Block a user