diff --git a/services/asterisk.sh b/services/asterisk.sh index 1a808cd..c67c9fb 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -1284,7 +1284,7 @@ _asterisk_ensure_live_voicemail_include() { docker exec "$CONTAINER_NAME" asterisk -rx "dialplan reload" &>/dev/null || true } -# ── pjsip.conf: [transport-tls] keep_alive_interval ───────────────────────── +# ── pjsip.conf: [global] keep_alive_interval ───────────────────────────────── # Mitigation for WiFi/LAN SIP clients (e.g. Sipnetic on Android) dropping # their TLS registration every few seconds on some hosts but not others — # confirmed live on an IONOS VPS (never reproduced on a DigitalOcean droplet @@ -1293,15 +1293,28 @@ _asterisk_ensure_live_voicemail_include() { # live disconnect and CrowdSec/packet-loss both ruled out first. Leading # theory: an idle-connection timeout somewhere in IONOS's own network # virtualization layer, below anything client-side tools can see. PJSIP's -# `keep_alive_interval` sends a lightweight double-CRLF over the transport's -# TLS connection on a timer, which is the standard fix for exactly this +# `keep_alive_interval` sends a lightweight double-CRLF over connection-oriented +# transports (TCP/TLS) on a timer, which is the standard fix for exactly this # class of "idle SIP/TLS connection gets silently dropped" symptom. # -# This is a *transport* option, not an endpoint/AOR option — `qualify_frequency` -# (already set globally) is an endpoint-level OPTIONS ping that re-establishes -# a dropped connection, it doesn't stop the drop from happening in the first -# place. `rtp_keepalive` (mobile devices) is unrelated: RTP media keepalive -# during an active call, not SIP signaling connection keepalive while idle. +# **This is a `type=global` option, not a `type=transport` option** — it does +# not exist on `[transport-tls]`/`[transport-tcp]` objects at all, on any +# Asterisk version. An earlier version of this patch inserted it into +# `[transport-tls]` (right after `protocol=tls`), which sorcery always +# rejects: "Could not find option suitable for category 'transport-tls' +# named 'keep_alive_interval'" — and rejecting the option means the whole +# `type=transport` object fails to be created, so `transport-tls` never +# binds at all. Confirmed live: this silently took down TLS SIP entirely on +# a box that had picked up the patch, reproducing the exact same "network +# error on all calls" symptom the rest of this file's health check exists to +# catch. The anchor below targets `type=global` inside `[global]` instead. +# +# This is a *transport-behavior* option, not an endpoint/AOR option — +# `qualify_frequency` (already set globally) is an endpoint-level OPTIONS +# ping that re-establishes a dropped connection, it doesn't stop the drop +# from happening in the first place. `rtp_keepalive` (mobile devices) is +# unrelated: RTP media keepalive during an active call, not SIP signaling +# connection keepalive while idle. _asterisk_patch_keepalive_vendor_files() { local EA_DIR="$1" local ENTRYPOINT="$EA_DIR/docker/entrypoint.sh" @@ -1313,15 +1326,20 @@ _asterisk_patch_keepalive_vendor_files() { for f in "$ENTRYPOINT" "$EASY1" "$EASY2"; do [[ -f "$f" ]] || continue - if ! grep -q '^protocol=tls$' "$f"; then - log_warning "$(basename "$f"): '[transport-tls]' anchor not found — vendor template changed upstream." - log_warning " Add 'keep_alive_interval=15' manually inside [transport-tls] in this file's pjsip.conf heredoc." + # Undo the old, incorrect [transport-tls] placement if an earlier + # run of this function already patched it there. + if grep -q '^protocol=tls$' "$f" && grep -A2 '^protocol=tls$' "$f" | grep -q '^keep_alive_interval='; then + sed -i '/^protocol=tls$/{n;/^keep_alive_interval=/d}' "$f" + fi + if ! grep -q '^type=global$' "$f"; then + log_warning "$(basename "$f"): '[global]' anchor not found — vendor template changed upstream." + log_warning " Add 'keep_alive_interval=15' manually inside [global] (not [transport-tls]) in this file's pjsip.conf heredoc." continue fi - grep -q '^keep_alive_interval=' "$f" || sed -i '/^protocol=tls$/a keep_alive_interval=15' "$f" + grep -q '^keep_alive_interval=' "$f" || sed -i '/^type=global$/a keep_alive_interval=15' "$f" done - log_success "Vendor generator functions patched for TLS transport keepalive." + log_success "Vendor generator functions patched for SIP keepalive (pjsip.conf [global])." } # Live-file counterpart to the vendor-template patch above, same reasoning @@ -1332,20 +1350,38 @@ _asterisk_patch_keepalive_vendor_files() { # a transport object — PJSIP transports are bound at module load, not # reloadable via sorcery like endpoints/AORs are — so this restarts the # container rather than issuing a reload, same as _asterisk_ensure_live_dangerously. +# +# `keep_alive_interval` belongs in `[global]` (`type=global`), not +# `[transport-tls]` — see the comment on _asterisk_patch_keepalive_vendor_files +# for why the old placement made sorcery reject the transport object outright +# (killing TLS SIP entirely, not just the keepalive). This also self-heals a +# box that already has the bad `[transport-tls]` entry from before that fix. _asterisk_ensure_live_keepalive() { local EA_DIR="$1" CONTAINER_NAME="$2" local CONF_LIVE="$EA_DIR/config/asterisk/pjsip.conf" [[ -f "$CONF_LIVE" ]] || return 0 - if ! grep -q '^\[transport-tls\]$' "$CONF_LIVE"; then - log_warning "Couldn't find '[transport-tls]' in the live pjsip.conf — add" + + local CHANGED=false + if sed -n '/^\[transport-tls\]$/,/^\[/{/^keep_alive_interval=/p}' "$CONF_LIVE" | grep -q .; then + sed -i '/^\[transport-tls\]$/,/^\[/{/^keep_alive_interval=/d}' "$CONF_LIVE" + log_warning "Removed 'keep_alive_interval' from [transport-tls] — that option doesn't exist on a PJSIP" + log_warning "transport object and was making the whole TLS transport fail to bind. Moving it to [global]." + CHANGED=true + fi + + if ! grep -q '^\[global\]$' "$CONF_LIVE"; then + log_warning "Couldn't find '[global]' in the live pjsip.conf — add" log_warning "'keep_alive_interval=15' manually inside that section, then: docker restart ${CONTAINER_NAME}" return 0 fi - grep -q '^keep_alive_interval=' "$CONF_LIVE" && return 0 - - sed -i '/^\[transport-tls\]$/,/^\[/{/^protocol=tls$/a keep_alive_interval=15 + if ! grep -q '^keep_alive_interval=' "$CONF_LIVE"; then + sed -i '/^\[global\]$/,/^\[/{/^type=global$/a keep_alive_interval=15 }' "$CONF_LIVE" - log_success "Patched keep_alive_interval=15 into the live pjsip.conf's [transport-tls] transport." + log_success "Patched keep_alive_interval=15 into the live pjsip.conf's [global] section." + CHANGED=true + fi + + [[ "$CHANGED" == true ]] || return 0 log_info "Restarting Asterisk to apply (transport options aren't picked up by a reload)..." docker restart "$CONTAINER_NAME" &>/dev/null \