From a12aae78196046f4837498ca65c0c7254e7229e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 02:56:34 +0000 Subject: [PATCH] asterisk: add PJSIP transport-tls keep_alive_interval for IONOS WiFi drops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sipnetic clients on the user's home WiFi/VLAN lose SIP/TLS registration every ~5s when Asterisk runs on IONOS, but never on DigitalOcean or over mobile data — CrowdSec, OPNsense firewall/IDS, TURN-for-registration, and raw packet loss have all been ruled out via live testing. Leading theory is an idle-connection timeout inside IONOS's network virtualization layer. keep_alive_interval sends a periodic double-CRLF over the TLS transport to keep it from going idle, the standard mitigation for this failure class. Follows the existing dual-patch pattern (vendor-template copy + live file) since transport objects aren't picked up by `pjsip reload` and need a container restart to apply, same as the live_dangerously fix. --- services/asterisk.sh | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/services/asterisk.sh b/services/asterisk.sh index b9628c9..6bbec0c 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -1230,6 +1230,75 @@ _asterisk_ensure_live_voicemail_include() { docker exec "$CONTAINER_NAME" asterisk -rx "dialplan reload" &>/dev/null || true } +# ── pjsip.conf: [transport-tls] 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 +# running the identical stack, and never over mobile data on either +# provider), with OPNsense's own firewall/IDS logs confirmed clean during a +# 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 +# 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. +_asterisk_patch_keepalive_vendor_files() { + local EA_DIR="$1" + local ENTRYPOINT="$EA_DIR/docker/entrypoint.sh" + local EASY1="$EA_DIR/easy-asterisk.sh" + local EASY2 + EASY2="$(find "$EA_DIR" -maxdepth 1 -name 'easy-asterisk-v*.sh' | head -1)" + [[ -z "$EASY2" ]] && EASY2="$EA_DIR/easy-asterisk-v0.10.0.sh" + local f + + 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." + continue + fi + grep -q '^keep_alive_interval=' "$f" || sed -i '/^protocol=tls$/a keep_alive_interval=15' "$f" + done + + log_success "Vendor generator functions patched for TLS transport keepalive." +} + +# Live-file counterpart to the vendor-template patch above, same reasoning +# as _asterisk_ensure_live_voicemail_include (Easy Asterisk's entrypoint +# only regenerates pjsip.conf if it's missing, so a box with existing +# devices never picks up the vendor patch on a plain restart). Unlike +# extensions.conf/voicemail.conf, `pjsip reload` does not pick up changes to +# 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. +_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" + 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 +}' "$CONF_LIVE" + log_success "Patched keep_alive_interval=15 into the live pjsip.conf's [transport-tls] transport." + + log_info "Restarting Asterisk to apply (transport options aren't picked up by a reload)..." + docker restart "$CONTAINER_NAME" &>/dev/null \ + && log_success "Restarted." \ + || log_warning "Restart failed — check: docker logs $CONTAINER_NAME" +} + _asterisk_remove_presence_timer() { systemctl disable --now asterisk-presence-alert.timer 2>/dev/null || true rm -f /etc/systemd/system/asterisk-presence-alert.timer /etc/systemd/system/asterisk-presence-alert.service @@ -2141,6 +2210,7 @@ install_asterisk() { _asterisk_write_voicemail_dialplan "$EA_DIR/config/asterisk/voicemail-dialplan.conf" _asterisk_write_voicemail_conf "$EA_DIR/config/asterisk/voicemail.conf" _asterisk_ensure_live_voicemail_include "$EA_DIR" "$CONTAINER" + _asterisk_patch_keepalive_vendor_files "$EA_DIR" ensure_docker_dir_ownership "$EA_DIR/config/asterisk" chmod 644 "$EA_DIR/config/asterisk/messaging-dialplan.conf" "$EA_DIR/config/asterisk/voicemail-dialplan.conf" @@ -2148,6 +2218,7 @@ install_asterisk() { if docker compose up -d --build --force-recreate; then log_success "Update complete — vendor files and docker-compose.yml refreshed." _asterisk_ensure_live_dangerously "$EA_DIR" "$CONTAINER" + _asterisk_ensure_live_keepalive "$EA_DIR" "$CONTAINER" else log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs" fi @@ -2237,6 +2308,7 @@ install_asterisk() { _asterisk_write_voicemail_dialplan "$EA_DIR/config/asterisk/voicemail-dialplan.conf" _asterisk_write_voicemail_conf "$EA_DIR/config/asterisk/voicemail.conf" _asterisk_ensure_live_voicemail_include "$EA_DIR" "$CONTAINER" + _asterisk_patch_keepalive_vendor_files "$EA_DIR" ensure_docker_dir_ownership "$EA_DIR/config/asterisk" chmod 644 "$EA_DIR/config/asterisk/messaging-dialplan.conf" "$EA_DIR/config/asterisk/voicemail-dialplan.conf" @@ -2495,6 +2567,7 @@ ENV if docker compose up -d --build; then log_success "Easy Asterisk started" _asterisk_ensure_live_dangerously "$EA_DIR" "$CONTAINER" + _asterisk_ensure_live_keepalive "$EA_DIR" "$CONTAINER" else log_warning "Start failed — check: docker compose logs" fi