2026-07-22 22:53:28 +02:00
|
|
|
#!/bin/sh
|
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-09-01 18:10:04 +02:00
|
|
|
# ims-pdn-up.sh — boot bring-up for the `ims` PDN (journal/ims.md s45).
|
2026-07-22 22:53:28 +02:00
|
|
|
#
|
2026-09-01 18:10:04 +02:00
|
|
|
# Finds-or-creates the ims bearer via ModemManager, connects it, and
|
2026-07-22 22:53:28 +02:00
|
|
|
# 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.
|
2026-09-01 18:10:04 +02:00
|
|
|
#
|
|
|
|
|
# Configuration, via the unit's EnvironmentFile /etc/imsd.env (also read
|
|
|
|
|
# directly so manual runs behave the same):
|
2026-09-17 13:45:19 +02:00
|
|
|
# IMS_APN ims APN name (default ims)
|
2026-09-01 18:10:04 +02:00
|
|
|
# IMS_IP_TYPE bearer ip-type (default ipv6)
|
2026-09-17 13:45:19 +02:00
|
|
|
# IMS_PROFILE_ID 3GPP profile index to connect through (default: looked up
|
|
|
|
|
# by APN in the modem's profile list; empty/none = connect by
|
|
|
|
|
# APN string). A profile-indexed call is exempt from the
|
|
|
|
|
# modem's attach-family forcing that refuses an APN-string
|
|
|
|
|
# IPv6 request when the attach PDN was granted IPv4-only
|
|
|
|
|
# (journal/ims 2026-09-16/17 bench: Odido, O2 UK, Swisscom).
|
2026-09-01 18:10:04 +02:00
|
|
|
# IMS_REG_TIMEOUT max seconds to wait for network registration before
|
|
|
|
|
# connect attempts start counting anyway (default 300)
|
2026-07-22 22:53:28 +02:00
|
|
|
|
|
|
|
|
log() { echo "ims-pdn-up: $*"; }
|
|
|
|
|
|
|
|
|
|
kv() { mmcli "$@" -K 2>/dev/null; }
|
|
|
|
|
|
2026-09-01 18:10:04 +02:00
|
|
|
# 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'; }
|
|
|
|
|
|
2026-07-22 22:53:28 +02:00
|
|
|
bearer_paths() {
|
|
|
|
|
kv -m "$MODEM" | sed -n 's/^modem\.generic\.bearers\.value\[[0-9]*\] *: *//p'
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-01 18:10:04 +02:00
|
|
|
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}
|
2026-09-17 13:45:19 +02:00
|
|
|
IMS_APN=${IMS_APN:-$(envval IMS_APN)}
|
|
|
|
|
IMS_APN=${IMS_APN:-ims}
|
|
|
|
|
PROFILE_ID=${IMS_PROFILE_ID:-$(envval IMS_PROFILE_ID)}
|
2026-09-01 18:10:04 +02:00
|
|
|
REG_TIMEOUT=${IMS_REG_TIMEOUT:-$(envval IMS_REG_TIMEOUT)}
|
|
|
|
|
REG_TIMEOUT=${REG_TIMEOUT:-300}
|
|
|
|
|
|
2026-07-22 22:53:28 +02:00
|
|
|
# ---- wait for a modem (MM + modem firmware take a while after boot)
|
|
|
|
|
n=0
|
|
|
|
|
while :; do
|
|
|
|
|
MODEM=$(mmcli -L 2>/dev/null | sed -n 's,.*/Modem/\([0-9]*\).*,\1,p' | head -n1)
|
|
|
|
|
[ -n "$MODEM" ] && break
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
[ "$n" -ge 60 ] && { log "no modem after 120 s"; exit 1; }
|
|
|
|
|
sleep 2
|
|
|
|
|
done
|
|
|
|
|
log "modem $MODEM"
|
|
|
|
|
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
# ---- 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
|
2026-09-17 13:45:19 +02:00
|
|
|
fi
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
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 } }')
|
|
|
|
|
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"
|
|
|
|
|
else
|
|
|
|
|
log "ims profile lookup failed$1: $(squash "$OUT")"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
lookup_profile " (modem state: $(modem_state))"
|
2026-09-17 13:45:19 +02:00
|
|
|
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
# ---- 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
|
2026-07-22 22:53:28 +02:00
|
|
|
for B in $(bearer_paths); do
|
|
|
|
|
INFO=$(kv -b "$B") || continue
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
has status.connected "$1" || continue
|
2026-09-17 13:45:19 +02:00
|
|
|
if [ -n "$PROFILE_ID" ]; then
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
if [ "$1" = yes ]; then
|
|
|
|
|
has properties.profile-id "$PROFILE_ID" || has properties.apn "$IMS_APN" || continue
|
|
|
|
|
else
|
|
|
|
|
has properties.profile-id "$PROFILE_ID" || continue
|
|
|
|
|
fi
|
2026-09-17 13:45:19 +02:00
|
|
|
else
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
has properties.apn "$IMS_APN" || continue
|
2026-09-17 13:45:19 +02:00
|
|
|
fi
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
[ "$2" = relaxed ] || has properties.ip-type "$IP_TYPE" || continue
|
2026-07-22 22:53:28 +02:00
|
|
|
echo "$B"
|
|
|
|
|
return 0
|
|
|
|
|
done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
# 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
|
|
|
|
|
}
|
2026-07-22 22:53:28 +02:00
|
|
|
|
|
|
|
|
BEARER=$(find_ims_bearer yes)
|
2026-09-01 18:10:04 +02:00
|
|
|
|
|
|
|
|
# ---- 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)"
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
# 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
|
2026-09-01 18:10:04 +02:00
|
|
|
fi
|
|
|
|
|
|
2026-07-22 22:53:28 +02:00
|
|
|
n=0
|
|
|
|
|
while [ -z "$BEARER" ]; do
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
[ "$n" -gt 10 ] && { log "bearer connect failed after 10 attempts"; exit 1; }
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
B=$(find_ims_bearer no) # reuse a stale disconnected ims bearer of this run's shape
|
2026-07-22 22:53:28 +02:00
|
|
|
if [ -z "$B" ]; then
|
2026-09-17 13:45:19 +02:00
|
|
|
if [ -n "$PROFILE_ID" ]; then
|
|
|
|
|
SPEC="profile-id=$PROFILE_ID,ip-type=$IP_TYPE"
|
|
|
|
|
else
|
|
|
|
|
SPEC="apn=$IMS_APN,ip-type=$IP_TYPE"
|
|
|
|
|
fi
|
|
|
|
|
OUT=$(mmcli -m "$MODEM" --create-bearer="$SPEC" 2>&1)
|
2026-09-01 18:10:04 +02:00
|
|
|
B=$(printf '%s' "$OUT" | sed -n 's,.*\(/org/freedesktop/ModemManager1/Bearer/[0-9]*\).*,\1,p')
|
|
|
|
|
if [ -n "$B" ]; then
|
2026-09-17 13:45:19 +02:00
|
|
|
log "created bearer $B ($SPEC)"
|
2026-09-01 18:10:04 +02:00
|
|
|
else
|
|
|
|
|
log "create-bearer attempt $n failed: $(squash "$OUT"); retrying in 10 s"
|
|
|
|
|
sleep 10
|
|
|
|
|
continue
|
|
|
|
|
fi
|
2026-07-22 22:53:28 +02:00
|
|
|
fi
|
2026-09-01 18:10:04 +02:00
|
|
|
if OUT=$(mmcli -b "$B" --connect 2>&1); then
|
2026-07-22 22:53:28 +02:00
|
|
|
BEARER=$B
|
|
|
|
|
else
|
2026-09-01 18:10:04 +02:00
|
|
|
log "connect attempt $n failed (state: $(modem_state)): $(squash "$OUT"); retrying in 10 s"
|
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.
2026-09-20 16:07:46 +02:00
|
|
|
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
|
2026-07-22 22:53:28 +02:00
|
|
|
sleep 10
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
log "connected: $BEARER"
|
|
|
|
|
|
|
|
|
|
# ---- configure the muxed netdev with the MM-assigned address
|
|
|
|
|
INFO=$(kv -b "$BEARER")
|
|
|
|
|
IFACE=$(echo "$INFO" | sed -n 's/^bearer\.status\.interface *: *//p')
|
|
|
|
|
ADDR=$(echo "$INFO" | sed -n 's/^bearer\.ipv6-config\.address *: *//p')
|
|
|
|
|
PREFIX=$(echo "$INFO" | sed -n 's/^bearer\.ipv6-config\.prefix *: *//p')
|
|
|
|
|
if [ -z "$IFACE" ] || [ -z "$ADDR" ]; then
|
|
|
|
|
log "bearer up but no interface/address in mmcli output"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
ip link set "$IFACE" up || exit 1
|
|
|
|
|
ip -6 addr replace "$ADDR/${PREFIX:-64}" dev "$IFACE" || exit 1
|
|
|
|
|
|
|
|
|
|
# wait out IPv6 DAD: binding a tentative address gives EADDRNOTAVAIL
|
|
|
|
|
# (first boot attempt cost a 120 s systemd retry exactly this way)
|
|
|
|
|
n=0
|
|
|
|
|
while ip -6 addr show dev "$IFACE" | grep -q tentative; do
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
[ "$n" -ge 10 ] && { log "address still tentative after 10 s"; break; }
|
|
|
|
|
sleep 1
|
|
|
|
|
done
|
|
|
|
|
log "$IFACE up, $ADDR/${PREFIX:-64}"
|
|
|
|
|
|
|
|
|
|
# ---- hand the connected ims netdev to imsd (imsd.service reads
|
|
|
|
|
# /run/imsd.env) so the daemon carries no baked-in interface name
|
|
|
|
|
echo "DEV=$IFACE" > /run/imsd.env
|
|
|
|
|
|
|
|
|
|
# ---- open the IMS protected ports in the firewall (rung 5b root cause,
|
|
|
|
|
# journal/ims.md s56): pmOS's default nftables INPUT chain is policy-drop and
|
|
|
|
|
# explicitly drops all inbound on qmapmux*, which silently killed every
|
|
|
|
|
# network-initiated request (reg-event NOTIFY, MT INVITE) after ESP decap —
|
|
|
|
|
# the P-CSCF's TCP SYNs to the protected server port never reached the
|
|
|
|
|
# listener, so terminating delivery failed and MT calls fell back to CS.
|
|
|
|
|
# 45061/45062 = imsd's protected client/server ports (kPortUc/kPortUs).
|
|
|
|
|
# Best-effort: never fail the unit over a missing/foreign firewall.
|
|
|
|
|
if nft list table inet filter >/dev/null 2>&1; then
|
|
|
|
|
if ! nft list chain inet filter input | grep -q imsd-protected-ports; then
|
|
|
|
|
nft insert rule inet filter input iifname "qmapmux*" tcp dport 45061-45062 accept comment '"imsd-protected-ports"' &&
|
|
|
|
|
nft insert rule inet filter input iifname "qmapmux*" udp dport 45061-45062 accept comment '"imsd-protected-ports"' &&
|
|
|
|
|
log "nftables: opened protected ports 45061-45062 on qmapmux*" ||
|
|
|
|
|
log "nftables: rule insert failed (continuing)"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
exit 0
|