From e5911c09c2b22541586fb42cac6f1d57419730db Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 18:10:04 +0200 Subject: [PATCH 1/2] ims-pdn-up: log mmcli errors, gate on registration, configurable ip-type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three hardenings, each of which cost a field-debugging round-trip: - Every mmcli failure now lands in the journal verbatim (create-bearer and connect stderr were swallowed, making 'connect attempt N failed' undecodable — no-service and interface-in-use looked identical). - Connect attempts are gated on network registration (up to IMS_REG_TIMEOUT, default 300 s): the 10x10 s window is shorter than some carriers' post-boot attach (~2 min measured), so every attempt could burn out before the network was even attached. The settled modem/packet-service state is logged either way. - Bearer ip-type is configurable via IMS_IP_TYPE in /etc/imsd.env (default ipv6); the bearer find/reuse now matches ip-type too, so a config change cannot silently reuse a stale bearer of the old type. TimeoutStartSec grows 600->900 to cover the registration gate. Tested on the FP6 dev phone (KPN): clean-state bring-up in 4 s; gate + timeout path with modem disabled; per-attempt error lines on a real contested-PDN failure (second ims PDN while one is connected); idempotent reuse + REGISTERED (fresh) via systemd. Co-Authored-By: Claude Fable 5 --- packaging/ims-pdn-up.sh | 64 +++++++++++++++++++++++++++++++++++------ packaging/imsd.service | 7 +++-- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/packaging/ims-pdn-up.sh b/packaging/ims-pdn-up.sh index e09066e..9632ae2 100644 --- a/packaging/ims-pdn-up.sh +++ b/packaging/ims-pdn-up.sh @@ -1,22 +1,42 @@ #!/bin/sh # SPDX-License-Identifier: GPL-3.0-only # SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -# ims-pdn-up.sh — boot bring-up for the IPv6 `ims` PDN (journal/ims.md s45). +# ims-pdn-up.sh — boot bring-up for the `ims` PDN (journal/ims.md s45). # -# Finds-or-creates the ims ipv6 bearer via ModemManager, connects it, and +# Finds-or-creates the ims bearer via ModemManager, connects it, and # configures the muxed netdev with the MM-assigned address (the s34 recipe, # automated). Idempotent — safe to run when the PDN is already up. Runs as # ExecStartPre of imsd.service, so imsd only starts once the PDN exists; a # nonzero exit fails the unit and systemd retries per Restart/RestartSec. +# +# Configuration, via the unit's EnvironmentFile /etc/imsd.env (also read +# directly so manual runs behave the same): +# IMS_IP_TYPE bearer ip-type (default ipv6) +# IMS_REG_TIMEOUT max seconds to wait for network registration before +# connect attempts start counting anyway (default 300) log() { echo "ims-pdn-up: $*"; } kv() { mmcli "$@" -K 2>/dev/null; } +# mmcli's multi-line error text as one journal-friendly line +squash() { printf '%s' "$1" | tr '\n' ' ' | sed 's/ */ /g'; } + +modem_state() { kv -m "$MODEM" | sed -n 's/^modem\.generic\.state *: *//p'; } +packet_state() { kv -m "$MODEM" | sed -n 's/^modem\.3gpp\.packet-service-state *: *//p'; } + bearer_paths() { kv -m "$MODEM" | sed -n 's/^modem\.generic\.bearers\.value\[[0-9]*\] *: *//p' } +envval() { # $1 = key — for manual runs; under systemd the vars are inherited + sed -n "s/^$1=//p" /etc/imsd.env 2>/dev/null | tail -n1 | tr -d '"' +} +IP_TYPE=${IMS_IP_TYPE:-$(envval IMS_IP_TYPE)} +IP_TYPE=${IP_TYPE:-ipv6} +REG_TIMEOUT=${IMS_REG_TIMEOUT:-$(envval IMS_REG_TIMEOUT)} +REG_TIMEOUT=${REG_TIMEOUT:-300} + # ---- wait for a modem (MM + modem firmware take a while after boot) n=0 while :; do @@ -29,11 +49,11 @@ done log "modem $MODEM" # ---- find a connected ims bearer; else find-or-create one and connect it -# (LTE attach can lag boot, so connect attempts retry) find_ims_bearer() { # $1 = required bearer.status.connected value for B in $(bearer_paths); do INFO=$(kv -b "$B") || continue echo "$INFO" | grep -q '^bearer\.properties\.apn *: *ims$' || continue + echo "$INFO" | grep -q "^bearer\.properties\.ip-type *: *$IP_TYPE\$" || continue echo "$INFO" | grep -q "^bearer\.status\.connected *: *$1\$" || continue echo "$B" return 0 @@ -42,20 +62,48 @@ find_ims_bearer() { # $1 = required bearer.status.connected value } BEARER=$(find_ims_bearer yes) + +# ---- gate the connect attempts on network registration: the 10x10 s window +# below is shorter than some carriers' post-boot attach (measured ~2 min), so +# without this every attempt can fail on no-service and the retries end +# before the network is even attached +if [ -z "$BEARER" ]; then + waited=0 last= + while :; do + STATE=$(modem_state) + case "$STATE" in registered|connecting|connected) break ;; esac + [ "$STATE" != "$last" ] && log "waiting for registration (state: ${STATE:-unknown})" + last=$STATE + if [ "$waited" -ge "$REG_TIMEOUT" ]; then + log "not registered after $REG_TIMEOUT s — attempting anyway" + break + fi + sleep 5 + waited=$((waited + 5)) + done + log "modem state: $(modem_state), packet service: $(packet_state)" +fi + n=0 while [ -z "$BEARER" ]; do n=$((n + 1)) [ "$n" -gt 10 ] && { log "bearer connect failed after 10 attempts"; exit 1; } B=$(find_ims_bearer no) # reuse a stale disconnected ims bearer if [ -z "$B" ]; then - B=$(mmcli -m "$MODEM" --create-bearer='apn=ims,ip-type=ipv6' 2>/dev/null | - sed -n 's,.*\(/org/freedesktop/ModemManager1/Bearer/[0-9]*\).*,\1,p') - [ -n "$B" ] && log "created bearer $B" + OUT=$(mmcli -m "$MODEM" --create-bearer="apn=ims,ip-type=$IP_TYPE" 2>&1) + B=$(printf '%s' "$OUT" | sed -n 's,.*\(/org/freedesktop/ModemManager1/Bearer/[0-9]*\).*,\1,p') + if [ -n "$B" ]; then + log "created bearer $B (ip-type=$IP_TYPE)" + else + log "create-bearer attempt $n failed: $(squash "$OUT"); retrying in 10 s" + sleep 10 + continue + fi fi - if [ -n "$B" ] && mmcli -b "$B" --connect >/dev/null 2>&1; then + if OUT=$(mmcli -b "$B" --connect 2>&1); then BEARER=$B else - log "connect attempt $n failed; retrying in 10 s" + log "connect attempt $n failed (state: $(modem_state)): $(squash "$OUT"); retrying in 10 s" sleep 10 fi done diff --git a/packaging/imsd.service b/packaging/imsd.service index 3ce9f51..add1594 100644 --- a/packaging/imsd.service +++ b/packaging/imsd.service @@ -9,10 +9,11 @@ Wants=ModemManager.service [Service] Type=simple -# bring up the ims PDN first; waits for modem + LTE attach, idempotent. -# worst case ~4 min (modem wait + connect retries), hence TimeoutStartSec +# bring up the ims PDN first; waits for modem + network registration, +# idempotent. worst case ~14 min (2 min modem wait + 5 min registration +# gate + connect retries), hence TimeoutStartSec ExecStartPre=/usr/libexec/ims-pdn-up.sh -TimeoutStartSec=600 +TimeoutStartSec=900 # /run/imsd.env: written by ims-pdn-up.sh (DEV= the connected ims netdev). # /etc/imsd.env: admin configuration — PCSCF= is REQUIRED (the carrier's # P-CSCF address; see the README's Configuration section) and wins over the From 17e0f6b53a441bd3618bd7fd628449dcb9191b11 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 18:24:37 +0200 Subject: [PATCH 2/2] packaging: own the apk aport (transferred from fp6-img); version 0.3.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit packaging/aport/ is now the canonical apk packaging, moved here from fp6-img so the daemon and its package live and version together — fp6-img's build.sh consumes this directory instead of carrying its own copy (it pins _commit and generates the source tarball, as before). 0.3.1 ships the ims-pdn-up hardening (visible mmcli errors, registration gate, configurable ip-type) and the TimeoutStartSec bump that goes with it. Co-Authored-By: Claude Fable 5 --- implementations/main.cpp | 2 +- packaging/aport/80-imsd.preset | 1 + packaging/aport/APKBUILD | 81 ++++++++++++++++++++ packaging/aport/org.kde.modem.daemon.desktop | 2 + 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packaging/aport/80-imsd.preset create mode 100644 packaging/aport/APKBUILD create mode 100644 packaging/aport/org.kde.modem.daemon.desktop diff --git a/implementations/main.cpp b/implementations/main.cpp index dee97cf..0ee64c4 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -41,7 +41,7 @@ import Imsd; namespace { // ---- static config (env-overridable, same knobs as imsd.py) --------------- -constexpr const char* Version = "0.3.0"; +constexpr const char* Version = "0.3.1"; constexpr const char* BusName = "net.catcrafts.IMS1"; constexpr const char* ObjPath = "/net/catcrafts/IMS1"; constexpr const char* Iface = "net.catcrafts.IMS1"; diff --git a/packaging/aport/80-imsd.preset b/packaging/aport/80-imsd.preset new file mode 100644 index 0000000..9ee121f --- /dev/null +++ b/packaging/aport/80-imsd.preset @@ -0,0 +1 @@ +enable imsd.service diff --git a/packaging/aport/APKBUILD b/packaging/aport/APKBUILD new file mode 100644 index 0000000..7912d38 --- /dev/null +++ b/packaging/aport/APKBUILD @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: GPL-3.0-only +# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® +# The canonical apk aport for imsd, owned by this repository since +# 2026-09-01 (transferred from fp6-img, which now consumes this directory: +# its build.sh copies packaging/aport/ into the pmaports overlay, pins +# _commit to a reviewed commit, and generates the source tarball with +# git-archive — the Forgejo instance serves no source archives). +# packaging/APKBUILD{,.binary} are the older standalone variants. +maintainer="Jorijn van der Graaf " +pkgname=imsd +pkgver=0.3.1 +pkgrel=0 +pkgdesc="Userspace IMS/VoLTE daemon for mainline Linux phones" +url="https://forgejo.catcrafts.net/Catcrafts/imsd" +# other arches: nothing wrong known, just never run there +arch="aarch64 x86_64" +license="GPL-3.0-only" +# the media leg dlopen's the AMR-WB codecs; pw-record/pw-play drive PipeWire — +# none of which abuild's .so auto-scan can see +depends="modemmanager opencore-amr vo-amrwbenc pipewire-tools" +# clang/libc++ C++26-modules build (upstream Makefile); llvm-runtimes ships +# the libc++ std module sources the build precompiles +makedepends="clang lld libc++-dev llvm-libunwind-dev llvm-runtimes glib-dev pkgconf" +# the versioned provides both satisfies soc-qcom-modem's 81voltd dependency +# and excludes the real package: 81voltd serves the modem firmware's own +# ims-PDN requests, which races imsd for the PDN and flaps it (a new prefix +# every ~2.5 min) — two IMS stacks cannot share one PDN. Installing imsd is +# an explicit choice to hand the IMS PDN to userspace. +provides="81voltd=$pkgver-r$pkgrel" +# no OpenRC service yet: the unit's PDN-bring-up/env-file sequencing is only +# tested under systemd; an initd is welcome once someone can verify one +subpackages="$pkgname-systemd" +# _commit is pinned by the consuming CI, which also drops the git-archive +# tarball (prefix imsd/) next to this APKBUILD. The skel override hides +# kde-telephony's modem daemon autostart for the account created at install — +# imsd-dialerd owns those session D-Bus names instead. +_commit="REPLACED_BY_CI" +source=" + imsd-$_commit.tar.gz + org.kde.modem.daemon.desktop + 80-imsd.preset +" +builddir="$srcdir/$pkgname" + +build() { + make +} + +check() { + make check +} + +package() { + make install DESTDIR="$pkgdir" + install -Dm644 "$srcdir"/org.kde.modem.daemon.desktop \ + "$pkgdir"/etc/skel/.config/autostart/org.kde.modem.daemon.desktop + # enabled by preset: the unit is a no-op until /etc/imsd.env exists, and + # VoLTE surviving reboots must not depend on a manual systemctl enable + install -Dm644 "$srcdir"/80-imsd.preset \ + "$pkgdir"/usr/lib/systemd/system-preset/80-imsd.preset + mkdir -p "$pkgdir"/etc/systemd/system/multi-user.target.wants + ln -s /usr/lib/systemd/system/imsd.service \ + "$pkgdir"/etc/systemd/system/multi-user.target.wants/imsd.service + # ...but only actually start once the carrier config exists, so + # unconfigured systems don't boot into a failing unit + mkdir -p "$pkgdir"/usr/lib/systemd/system/imsd.service.d + printf '[Unit]\nConditionPathExists=/etc/imsd.env\n' \ + > "$pkgdir"/usr/lib/systemd/system/imsd.service.d/10-require-config.conf +} + +systemd() { + install_if="$pkgname=$pkgver-r$pkgrel systemd" + + amove usr/lib/systemd/system +} + +sha512sums=" +REPLACED_BY_CI imsd-REPLACED_BY_CI.tar.gz +REPLACED_BY_CI org.kde.modem.daemon.desktop +REPLACED_BY_CI 80-imsd.preset +" diff --git a/packaging/aport/org.kde.modem.daemon.desktop b/packaging/aport/org.kde.modem.daemon.desktop new file mode 100644 index 0000000..e1e3e17 --- /dev/null +++ b/packaging/aport/org.kde.modem.daemon.desktop @@ -0,0 +1,2 @@ +[Desktop Entry] +Hidden=true