ims-pdn-up: retry the profile lookup, never reuse a stale APN-string bearer

The ims profile lookup ran the moment ModemManager listed the modem, which
on a cold boot can be while it is still enabling; qmicli's error was
discarded, so a failed lookup read as "no profile" and the script fell back
to the APN-string request, which is the one the modem refuses under an
IPv4 attach. Every refused attempt left a disconnected apn=ims bearer
object behind, and the next restart's "reuse a stale disconnected bearer"
step matched it by APN and reconnected it: ten refusals again, no way out
but a reboot that happens to win the race (O2 UK field report; the same
lost race seen on a KPN unit, where the fallback merely connects).

Log qmicli's error and retry the lookup after the registration wait, fall
back to the APN string only when the list has no such APN, reuse a
disconnected bearer only when it has this run's shape and delete leftover
APN-string ims bearers when connecting by profile index, match the APN
case-insensitively, and on call-already-present adopt the connected bearer
instead of asking the modem again.

Ten field shapes replayed against a mock ModemManager (shipped script
5/10, this one 10/10); on the FP6 a warm restart adopts the live PDN and
a cold boot connects by profile index and registers.
This commit is contained in:
Jorijn van der Graaf 2026-09-20 16:07:46 +02:00
commit 6689b8b252
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB

View file

@ -58,37 +58,73 @@ while :; do
done
log "modem $MODEM"
# ---- the ims profile index (WDS profile list), unless configured
ims_profile_index() { # $1 = apn
qmicli -d qrtr://0 --wds-get-profile-list=3gpp 2>/dev/null | awk -v apn="$1" '
# ---- the ims profile index (WDS profile list), unless configured. The
# lookup itself can fail while ModemManager is still enabling the modem; a
# failed lookup is not "no profile" (the APN-string request it would fall
# back to is the one the modem refuses under an IPv4 attach), so it is
# retried after the registration wait and only a list without the APN
# falls back to the APN string.
LOOKUP=pending
if [ -n "$PROFILE_ID" ]; then
[ "$PROFILE_ID" = none ] && PROFILE_ID=
LOOKUP=done
fi
lookup_profile() { # $1 = log suffix; sets PROFILE_ID, LOOKUP=done on a usable list
[ "$LOOKUP" = done ] && return 0
if OUT=$(qmicli -d qrtr://0 --wds-get-profile-list=3gpp 2>&1); then
PROFILE_ID=$(printf '%s\n' "$OUT" | awk -v apn="$IMS_APN" '
/^[ \t]*\[[0-9]+\] 3gpp/ { idx = $1; gsub(/[^0-9]/, "", idx) }
/APN:/ { a = $0; sub(/.*APN: '"'"'/, "", a); sub(/'"'"'.*/, "", a);
if (tolower(a) == tolower(apn) && idx != "") { print idx; exit } }'
}
if [ -z "$PROFILE_ID" ]; then
PROFILE_ID=$(ims_profile_index "$IMS_APN")
if (tolower(a) == tolower(apn) && idx != "") { print idx; exit } }')
LOOKUP=done
[ -n "$PROFILE_ID" ] && log "ims profile: index $PROFILE_ID (apn $IMS_APN)" || log "ims profile: none for apn $IMS_APN, connecting by APN string"
elif [ "$PROFILE_ID" = none ]; then
PROFILE_ID=
fi
else
log "ims profile lookup failed$1: $(squash "$OUT")"
fi
}
lookup_profile " (modem state: $(modem_state))"
# ---- find a connected ims bearer; else find-or-create one and connect it
find_ims_bearer() { # $1 = required bearer.status.connected value
# ---- find a connected ims bearer; else find-or-create one and connect it.
# APN names are case-insensitive. A DISCONNECTED bearer is reused only when
# it is the request this run would make (same profile index, or same APN when
# connecting by APN string): a leftover APN-string bearer from a run whose
# lookup failed is the walled request itself, and reconnecting it fails on
# every restart until the modem is reset (field: O2 UK, 2026-09-20).
has() { printf '%s\n' "$INFO" | grep -qi "^bearer\.$1 *: *$2\$"; }
find_ims_bearer() { # $1 = required bearer.status.connected value; $2 = "relaxed" ignores ip-type
for B in $(bearer_paths); do
INFO=$(kv -b "$B") || continue
has status.connected "$1" || continue
if [ -n "$PROFILE_ID" ]; then
echo "$INFO" | grep -q "^bearer\.properties\.profile-id *: *$PROFILE_ID\$" ||
echo "$INFO" | grep -q "^bearer\.properties\.apn *: *$IMS_APN\$" || continue
if [ "$1" = yes ]; then
has properties.profile-id "$PROFILE_ID" || has properties.apn "$IMS_APN" || continue
else
echo "$INFO" | grep -q "^bearer\.properties\.apn *: *$IMS_APN\$" || continue
has properties.profile-id "$PROFILE_ID" || continue
fi
echo "$INFO" | grep -q "^bearer\.properties\.ip-type *: *$IP_TYPE\$" || continue
echo "$INFO" | grep -q "^bearer\.status\.connected *: *$1\$" || continue
else
has properties.apn "$IMS_APN" || continue
fi
[ "$2" = relaxed ] || has properties.ip-type "$IP_TYPE" || continue
echo "$B"
return 0
done
return 1
}
# leftover disconnected APN-string ims bearers when this run connects by
# profile index: never reused (above), deleted so a later run without an
# index cannot pick one up either
delete_stale_bearers() {
[ -n "$PROFILE_ID" ] || return 0
for B in $(bearer_paths); do
INFO=$(kv -b "$B") || continue
has status.connected no || continue
has properties.apn "$IMS_APN" || continue
has properties.profile-id "$PROFILE_ID" && continue
OUT=$(mmcli -m "$MODEM" --delete-bearer="$B" 2>&1) &&
log "deleted stale bearer $B (apn $IMS_APN, no profile index)" ||
log "could not delete stale bearer $B: $(squash "$OUT")"
done
}
BEARER=$(find_ims_bearer yes)
@ -111,13 +147,26 @@ if [ -z "$BEARER" ]; then
waited=$((waited + 5))
done
log "modem state: $(modem_state), packet service: $(packet_state)"
# a lookup that failed while the modem was still coming up
n=0
while [ "$LOOKUP" != done ] && [ "$n" -lt 3 ]; do
n=$((n + 1))
sleep 5
lookup_profile " (retry $n)"
done
if [ "$LOOKUP" != done ]; then
PROFILE_ID=
log "ims profile: lookup keeps failing, connecting by APN string"
fi
BEARER=$(find_ims_bearer yes) # the index may now match a connected profile-indexed PDN
[ -n "$BEARER" ] || delete_stale_bearers
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
B=$(find_ims_bearer no) # reuse a stale disconnected ims bearer of this run's shape
if [ -z "$B" ]; then
if [ -n "$PROFILE_ID" ]; then
SPEC="profile-id=$PROFILE_ID,ip-type=$IP_TYPE"
@ -138,6 +187,15 @@ while [ -z "$BEARER" ]; do
BEARER=$B
else
log "connect attempt $n failed (state: $(modem_state)): $(squash "$OUT"); retrying in 10 s"
case "$OUT" in *call-already-present*)
# the PDN is up on a bearer this run did not recognise (ip-type or
# APN spelling); use it rather than ask the modem for a second one
if B=$(find_ims_bearer yes relaxed); then
log "adopting the connected bearer $B"
BEARER=$B
continue
fi ;;
esac
sleep 10
fi
done