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,
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
# automated), reads the P-CSCF list the network put in the PDN's PCO from the
|
|
|
|
|
|
# modem (imsd-pcscf) unless PCSCF= names one, and gives the configured or
|
|
|
|
|
|
# the first discovered address (the daemon's fresh target) a host route —
|
|
|
|
|
|
# never a default route (F15 below). Idempotent — safe to run when the PDN
|
|
|
|
|
|
# is already up. Runs as
|
2026-07-22 22:53:28 +02:00
|
|
|
|
# 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)
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
# PCSCF one P-CSCF address, by hand: it wins over discovery
|
|
|
|
|
|
# (which is then skipped) and gets the host route; unset,
|
|
|
|
|
|
# imsd-pcscf reads the PDN's PCO list from the modem and
|
|
|
|
|
|
# this script hands it to imsd as PCSCF_DISCOVERED= in
|
|
|
|
|
|
# /run/imsd.env, the first of them routed
|
|
|
|
|
|
# IMSD_PCSCF the reader's path (default /usr/libexec/imsd-pcscf) — a
|
|
|
|
|
|
# bench seam, from the environment only (not from the file)
|
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-10-06 17:54:09 +02:00
|
|
|
|
# A disabled modem has its radio off (airplane mode): leave it alone. A bearer
|
|
|
|
|
|
# connect on it fails with no-service and leaves ModemManager's state
|
|
|
|
|
|
# `enabled` with the modem still in low power; switching the radio back on
|
|
|
|
|
|
# then no longer brings the modem up (dev phone 2026-10-06).
|
|
|
|
|
|
radio_off() { [ "$(modem_state)" = disabled ]; }
|
|
|
|
|
|
|
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
|
2026-09-24 18:58:59 +02:00
|
|
|
|
# retried after the registration wait; a list without the APN is final —
|
|
|
|
|
|
# the APN string is used — only once the modem is registered.
|
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=pending
|
|
|
|
|
|
if [ -n "$PROFILE_ID" ]; then
|
|
|
|
|
|
[ "$PROFILE_ID" = none ] && PROFILE_ID=
|
|
|
|
|
|
LOOKUP=done
|
2026-09-17 13:45:19 +02:00
|
|
|
|
fi
|
2026-09-24 18:58:59 +02:00
|
|
|
|
lookup_profile() { # $1 = modem state, $2 = log suffix; sets PROFILE_ID, LOOKUP=done on a final answer
|
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" = done ] && return 0
|
|
|
|
|
|
if OUT=$(qmicli -d qrtr://0 --wds-get-profile-list=3gpp 2>&1); then
|
2026-09-20 16:58:43 +02:00
|
|
|
|
# a list with no profiles at all is the modem still coming up, not
|
|
|
|
|
|
# a modem without an ims profile (dev phone cold boot 2026-09-20
|
|
|
|
|
|
# 16:47: exit 0, no entries, index 2 present a minute later)
|
|
|
|
|
|
if ! printf '%s\n' "$OUT" | grep -q -E '^[[:space:]]*\[[0-9]+\] 3gpp'; then
|
2026-09-24 18:58:59 +02:00
|
|
|
|
log "ims profile lookup returned no profiles$2"
|
2026-09-20 16:58:43 +02:00
|
|
|
|
return 0
|
|
|
|
|
|
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
|
|
|
|
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 } }')
|
2026-09-24 18:58:59 +02:00
|
|
|
|
if [ -n "$PROFILE_ID" ]; then
|
|
|
|
|
|
LOOKUP=done
|
|
|
|
|
|
log "ims profile: index $PROFILE_ID (apn $IMS_APN)"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
# a list without the APN is final only once the modem is registered:
|
|
|
|
|
|
# while it enables, the list can be partial (dev phone cold boot
|
|
|
|
|
|
# 2026-09-24 18:18: entries but no ims at `enabling`, ims listed when
|
|
|
|
|
|
# read minutes later), and on a walled carrier the APN-string fallback
|
|
|
|
|
|
# that would follow is the request the modem refuses
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
registered|connecting|connected)
|
|
|
|
|
|
LOOKUP=done
|
|
|
|
|
|
log "ims profile: none for apn $IMS_APN, connecting by APN string" ;;
|
|
|
|
|
|
*) log "ims profile: not in the list yet$2" ;;
|
|
|
|
|
|
esac
|
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
|
|
|
|
else
|
2026-09-24 18:58:59 +02:00
|
|
|
|
log "ims profile lookup failed$2: $(squash "$OUT")"
|
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
|
|
|
|
fi
|
|
|
|
|
|
}
|
2026-09-24 18:58:59 +02:00
|
|
|
|
STATE=$(modem_state)
|
|
|
|
|
|
lookup_profile "$STATE" " (modem state: $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
|
2026-10-06 17:54:09 +02:00
|
|
|
|
if [ "$STATE" = disabled ]; then
|
|
|
|
|
|
log "modem still disabled after $REG_TIMEOUT s (radio off): not connecting"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2026-09-01 18:10:04 +02:00
|
|
|
|
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)"
|
2026-09-24 18:58:59 +02:00
|
|
|
|
# a lookup with no final answer yet: it failed, or its list lacked the
|
|
|
|
|
|
# APN while the modem was still coming up
|
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
|
|
|
|
n=0
|
|
|
|
|
|
while [ "$LOOKUP" != done ] && [ "$n" -lt 3 ]; do
|
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
|
sleep 5
|
2026-09-24 18:58:59 +02:00
|
|
|
|
STATE=$(modem_state)
|
|
|
|
|
|
lookup_profile "$STATE" " (retry $n, modem state: $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
|
|
|
|
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; }
|
2026-10-06 17:54:09 +02:00
|
|
|
|
radio_off && { log "modem disabled (radio off): not connecting"; 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
|
ims-pdn-up: never let the ims PDN be the IPv6 default route
The PGW's router advertisement on the ims netdev offers a default
router, and on IPv4-only wifi that is the phone's only IPv6 default
route. Every AAAA-first client (apk, browsers) then sends its SYN into
the IMS walled garden, which drops it: apk sat in SYN-SENT for nine
minutes on the dev phone, and a field user's remedy was to stop imsd
before package work.
Set accept_ra_defrtr=0 on the muxed netdev before link-up (the router
solicitation goes out once the link-local address passes DAD), flush a
default route an earlier RA installed (an imsd restart), and give the
P-CSCF a host route over the netdev instead. imsd talks to the P-CSCF
only and already installs a host route per media peer; the RA's prefix
and MTU still apply. The host route makes the P-CSCF reachable before
the RA has been processed, so the PDN MTU ModemManager reports is set
on the link as well, the way NetworkManager does for its bearers. A
failed host route, or a P-CSCF that is not an IPv6 address (the PDN is
brought up IPv6-only), fails the unit with a clear message.
Dev phone (KPN, 0.3.8-r0): restart path drops the RA default and
registers over the host route, apk update in 6 s where it hung; MO and
MT calls with audio both ways; cold boot never installs the default
route, the link carries the 1280 MTU before the host route exists,
fresh registration in 1 s.
2026-09-24 04:12:39 +02:00
|
|
|
|
# ---- the ims PDN must never be the phone's IPv6 default route (F15). The
|
|
|
|
|
|
# PGW's router advertisement offers a default router; on IPv4-only wifi
|
|
|
|
|
|
# that is the phone's only v6 default, so every AAAA-first client (apk,
|
|
|
|
|
|
# browsers) sends its SYN into the IMS walled garden, which drops it
|
|
|
|
|
|
# silently (dev phone 2026-09-20: a 9-min apk hang). imsd only talks to the
|
|
|
|
|
|
# P-CSCF (host route below) and to media peers (imsd installs those host
|
|
|
|
|
|
# routes per call); the RA's prefix and MTU are still applied (its hop
|
|
|
|
|
|
# limit is not — the kernel default 64 stays). Per-netdev, via /proc, and
|
|
|
|
|
|
# before link-up: the router solicitation goes out once the link-local
|
|
|
|
|
|
# address passes DAD, ~1–2 s after link-up, and the RA answers at once.
|
|
|
|
|
|
if ! echo 0 > "/proc/sys/net/ipv6/conf/$IFACE/accept_ra_defrtr" 2>/dev/null; then
|
|
|
|
|
|
log "could not set accept_ra_defrtr=0 on $IFACE (continuing)"
|
|
|
|
|
|
fi
|
|
|
|
|
|
# the PDN's MTU as the modem reports it (1280 here), on the link itself:
|
|
|
|
|
|
# the P-CSCF host route makes the P-CSCF reachable before the RA's MTU
|
|
|
|
|
|
# option has been processed, and a send in that window would otherwise be
|
|
|
|
|
|
# sized by the link's default MTU. NetworkManager does the same for its
|
|
|
|
|
|
# bearers. An RA MTU no larger than the link's is still applied.
|
|
|
|
|
|
MTU=$(echo "$INFO" | sed -n 's/^bearer\.ipv6-config\.mtu *: *//p')
|
|
|
|
|
|
case "$MTU" in ''|--|*[!0-9]*) MTU= ;; esac
|
|
|
|
|
|
if [ -n "$MTU" ] && [ "$MTU" -ge 1280 ]; then
|
|
|
|
|
|
ip link set "$IFACE" mtu "$MTU" || log "could not set mtu $MTU on $IFACE (continuing)"
|
|
|
|
|
|
fi
|
2026-07-22 22:53:28 +02:00
|
|
|
|
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
|
ims-pdn-up: never let the ims PDN be the IPv6 default route
The PGW's router advertisement on the ims netdev offers a default
router, and on IPv4-only wifi that is the phone's only IPv6 default
route. Every AAAA-first client (apk, browsers) then sends its SYN into
the IMS walled garden, which drops it: apk sat in SYN-SENT for nine
minutes on the dev phone, and a field user's remedy was to stop imsd
before package work.
Set accept_ra_defrtr=0 on the muxed netdev before link-up (the router
solicitation goes out once the link-local address passes DAD), flush a
default route an earlier RA installed (an imsd restart), and give the
P-CSCF a host route over the netdev instead. imsd talks to the P-CSCF
only and already installs a host route per media peer; the RA's prefix
and MTU still apply. The host route makes the P-CSCF reachable before
the RA has been processed, so the PDN MTU ModemManager reports is set
on the link as well, the way NetworkManager does for its bearers. A
failed host route, or a P-CSCF that is not an IPv6 address (the PDN is
brought up IPv6-only), fails the unit with a clear message.
Dev phone (KPN, 0.3.8-r0): restart path drops the RA default and
registers over the host route, apk update in 6 s where it hung; MO and
MT calls with audio both ways; cold boot never installs the default
route, the link carries the 1280 MTU before the host route exists,
fresh registration in 1 s.
2026-09-24 04:12:39 +02:00
|
|
|
|
log "$IFACE up, $ADDR/${PREFIX:-64}, ipv6 mtu $(cat "/proc/sys/net/ipv6/conf/$IFACE/mtu" 2>/dev/null)"
|
|
|
|
|
|
|
|
|
|
|
|
# a default route an earlier RA already installed (the link was up before
|
|
|
|
|
|
# this run: an imsd restart) — accept_ra_defrtr=0 leaves an existing one in
|
|
|
|
|
|
# place until its lifetime runs out. Any ::/0 via this netdev goes, by the
|
|
|
|
|
|
# policy above.
|
|
|
|
|
|
if [ -n "$(ip -6 route show default dev "$IFACE" 2>/dev/null)" ]; then
|
|
|
|
|
|
ip -6 route flush default dev "$IFACE" && log "dropped the RA default route on $IFACE"
|
|
|
|
|
|
fi
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
# ---- the P-CSCF. PCSCF= when the admin set one (one IPv6 address; it wins
|
|
|
|
|
|
# and discovery is skipped), else the list the network put in the PDN's PCO,
|
|
|
|
|
|
# read from the modem by imsd-pcscf: a QMI WDS client bound to this netdev's
|
|
|
|
|
|
# mux data port — the binding ModemManager gives its own clients, and only
|
|
|
|
|
|
# such a client is shown the call's settings. The list goes to imsd as
|
|
|
|
|
|
# PCSCF_DISCOVERED= (its own name: this script inherits the previous run's
|
|
|
|
|
|
# /run/imsd.env through the unit, so a stale discovered value must never
|
|
|
|
|
|
# pass for the admin's PCSCF=, and it is never read from the environment
|
|
|
|
|
|
# here). The PDN is brought up IPv6-only, so only IPv6 addresses are usable;
|
|
|
|
|
|
# IPv4 ones and names are logged and dropped. Nothing usable is not this
|
|
|
|
|
|
# script's failure: imsd resumes a registration of its own when it can; with
|
|
|
|
|
|
# nothing to resume it says so and exits, and the unit's restart runs
|
|
|
|
|
|
# discovery again.
|
|
|
|
|
|
# The P-CSCF is reachable through the ims PDN only: a host route stands in
|
|
|
|
|
|
# for the default — `dev` without a gateway (a raw-IP netdev without
|
|
|
|
|
|
# header_ops, so the kernel resolves no neighbours), the shape imsd uses for
|
|
|
|
|
|
# media peers; of a discovered list the first address — the daemon's fresh
|
|
|
|
|
|
# target — gets one here; imsd routes the P-CSCF it uses itself as well — the
|
|
|
|
|
|
# fresh target before a fresh registration, the resumed SA peer once its SAs
|
|
|
|
|
|
# are confirmed — with the same idempotent replace as a call's media peer, so
|
|
|
|
|
|
# a target this script did not route (a list set in /etc/imsd.env, a route
|
|
|
|
|
|
# lost with the PDN) is reachable.
|
|
|
|
|
|
# Anything else on the IMS network (its DNS servers, a traceroute
|
|
|
|
|
|
# target) needs its own host route. Without the route imsd cannot reach the
|
|
|
|
|
|
# P-CSCF, so a route failure is the unit's failure.
|
|
|
|
|
|
route_pcscf() { # $1 = address
|
|
|
|
|
|
OUT=$(ip -6 route replace "$1/128" dev "$IFACE" 2>&1) ||
|
|
|
|
|
|
{ log "host route to P-CSCF $1 failed: $(squash "$OUT")"; exit 1; }
|
|
|
|
|
|
log "host route to P-CSCF $1 dev $IFACE"
|
|
|
|
|
|
}
|
ims-pdn-up: never let the ims PDN be the IPv6 default route
The PGW's router advertisement on the ims netdev offers a default
router, and on IPv4-only wifi that is the phone's only IPv6 default
route. Every AAAA-first client (apk, browsers) then sends its SYN into
the IMS walled garden, which drops it: apk sat in SYN-SENT for nine
minutes on the dev phone, and a field user's remedy was to stop imsd
before package work.
Set accept_ra_defrtr=0 on the muxed netdev before link-up (the router
solicitation goes out once the link-local address passes DAD), flush a
default route an earlier RA installed (an imsd restart), and give the
P-CSCF a host route over the netdev instead. imsd talks to the P-CSCF
only and already installs a host route per media peer; the RA's prefix
and MTU still apply. The host route makes the P-CSCF reachable before
the RA has been processed, so the PDN MTU ModemManager reports is set
on the link as well, the way NetworkManager does for its bearers. A
failed host route, or a P-CSCF that is not an IPv6 address (the PDN is
brought up IPv6-only), fails the unit with a clear message.
Dev phone (KPN, 0.3.8-r0): restart path drops the RA default and
registers over the host route, apk update in 6 s where it hung; MO and
MT calls with audio both ways; cold boot never installs the default
route, the link carries the 1280 MTU before the host route exists,
fresh registration in 1 s.
2026-09-24 04:12:39 +02:00
|
|
|
|
PCSCF=${PCSCF:-$(envval PCSCF)}
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
DISCOVERED=
|
ims-pdn-up: never let the ims PDN be the IPv6 default route
The PGW's router advertisement on the ims netdev offers a default
router, and on IPv4-only wifi that is the phone's only IPv6 default
route. Every AAAA-first client (apk, browsers) then sends its SYN into
the IMS walled garden, which drops it: apk sat in SYN-SENT for nine
minutes on the dev phone, and a field user's remedy was to stop imsd
before package work.
Set accept_ra_defrtr=0 on the muxed netdev before link-up (the router
solicitation goes out once the link-local address passes DAD), flush a
default route an earlier RA installed (an imsd restart), and give the
P-CSCF a host route over the netdev instead. imsd talks to the P-CSCF
only and already installs a host route per media peer; the RA's prefix
and MTU still apply. The host route makes the P-CSCF reachable before
the RA has been processed, so the PDN MTU ModemManager reports is set
on the link as well, the way NetworkManager does for its bearers. A
failed host route, or a P-CSCF that is not an IPv6 address (the PDN is
brought up IPv6-only), fails the unit with a clear message.
Dev phone (KPN, 0.3.8-r0): restart path drops the RA default and
registers over the host route, apk update in 6 s where it hung; MO and
MT calls with audio both ways; cold boot never installs the default
route, the link carries the 1280 MTU before the host route exists,
fresh registration in 1 s.
2026-09-24 04:12:39 +02:00
|
|
|
|
case "$PCSCF" in
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
'')
|
|
|
|
|
|
if [ "$IP_TYPE" = ipv4 ]; then
|
|
|
|
|
|
log "P-CSCF discovery: not for IMS_IP_TYPE=$IP_TYPE — the bring-up configures only the PDN's IPv6 address"
|
|
|
|
|
|
else
|
|
|
|
|
|
MUX=$(ip -d link show "$IFACE" 2>/dev/null | sed -n 's/.*rmnet mux_id \([0-9]*\).*/\1/p' | head -n1)
|
|
|
|
|
|
if [ -z "$MUX" ]; then
|
|
|
|
|
|
log "P-CSCF discovery: no rmnet mux id on $IFACE (ip -d link); set PCSCF="
|
|
|
|
|
|
else
|
|
|
|
|
|
OUT=$("${IMSD_PCSCF:-/usr/libexec/imsd-pcscf}" --mux "$MUX" ${PROFILE_ID:+--profile "$PROFILE_ID"} --timeout 10 2>&1); rc=$?
|
|
|
|
|
|
V6=; SHOWN=; OTHER=; FLAGS=; ERR=; n=0
|
|
|
|
|
|
while IFS= read -r line; do
|
|
|
|
|
|
case "$line" in
|
|
|
|
|
|
'') ;;
|
|
|
|
|
|
v4\ *|fqdn\ *) OTHER="${OTHER:+$OTHER }$line" ;;
|
|
|
|
|
|
flag\ *) FLAGS="${FLAGS:+$FLAGS, }${line#flag }" ;;
|
|
|
|
|
|
*[!0-9a-f:]*) ERR="${ERR:+$ERR }$line" ;;
|
|
|
|
|
|
*:*) [ "$n" -ge 8 ] && continue; n=$((n + 1))
|
|
|
|
|
|
V6="${V6:+$V6 }$line"; DISCOVERED="${DISCOVERED:+$DISCOVERED,}$line"; SHOWN="${SHOWN:+$SHOWN, }$line" ;;
|
|
|
|
|
|
*) ERR="${ERR:+$ERR }$line" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done <<EOF
|
|
|
|
|
|
$OUT
|
|
|
|
|
|
EOF
|
|
|
|
|
|
if [ -n "$V6" ]; then
|
|
|
|
|
|
log "P-CSCF discovered: $SHOWN${FLAGS:+ ($FLAGS)}"
|
|
|
|
|
|
[ -n "$OTHER" ] && log "P-CSCF discovery also gave $OTHER (not usable on the IPv6-only ims PDN)"
|
|
|
|
|
|
route_pcscf "${V6%% *}" # the first: the daemon's fresh target; imsd routes the P-CSCF it uses itself as well (the fresh target, the resumed peer)
|
|
|
|
|
|
else
|
|
|
|
|
|
case $rc in
|
|
|
|
|
|
0) log "P-CSCF discovery: nothing usable (imsd-pcscf printed no address${ERR:+: $ERR})" ;;
|
|
|
|
|
|
2) log "P-CSCF discovery: nothing usable (the PCO carried no IPv6 P-CSCF${OTHER:+; it gave $OTHER}${FLAGS:+; $FLAGS})" ;;
|
|
|
|
|
|
*) log "P-CSCF discovery: nothing usable (imsd-pcscf rc $rc${ERR:+: $ERR})" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi ;;
|
|
|
|
|
|
*,*) log "PCSCF '$PCSCF' is a list; one address here (a list is PCSCF_DISCOVERED, the bring-up's)"; exit 1 ;;
|
ipsec, imsd, ims-pdn-up: the SA reader and the holds key compare in canonical form; LOCAL is canonicalised, PCSCF= accepted in RFC 5952 §4 form only
ParseExistingSa and SaLocals compared PCSCF= and LOCAL= as text against
`ip xfrm` output, which prints RFC 5952 form, so an upper-case or
uncompressed PCSCF= found no SAs and the restart registered fresh — on
KPN into the live-SA refusal; the holds file's pdn key compared the same
way. Both sides of each of those compares now go through
imsd::util::CanonicalAddr, LOCAL is canonicalised once on the way in, and PCSCF= is accepted in
that one spelling only — upper case, leading zeros or an uncompressed run
are refused at bring-up with the spelling to use, a dotted-quad tail (the
form ip prints for an IPv4-mapped address, which CanonicalIp6 does not read)
with the rule alone, a bracketed one by the bring-up script, which routes
the value as typed, with the rule (no brackets) — and a comma-separated
PCSCF= (a list) with its own message instead of reaching the socket layer: one spelling,
instead of a leniency both consumers of the setting would have to share.
Tested: crafter-build test, 7 passed; the upper-case PCSCF= refused with the spelling to use, a bracketed one
refused by the script, and the spelling they name resuming, in builds of
this series on the dev phone (KPN).
2026-10-11 15:55:19 +02:00
|
|
|
|
*\[*) log "PCSCF '$PCSCF': write the bare address, as ip prints it (no brackets)"; exit 1 ;;
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
*:*) log "P-CSCF $PCSCF configured: discovery skipped"; route_pcscf "$PCSCF" ;;
|
ims-pdn-up: never let the ims PDN be the IPv6 default route
The PGW's router advertisement on the ims netdev offers a default
router, and on IPv4-only wifi that is the phone's only IPv6 default
route. Every AAAA-first client (apk, browsers) then sends its SYN into
the IMS walled garden, which drops it: apk sat in SYN-SENT for nine
minutes on the dev phone, and a field user's remedy was to stop imsd
before package work.
Set accept_ra_defrtr=0 on the muxed netdev before link-up (the router
solicitation goes out once the link-local address passes DAD), flush a
default route an earlier RA installed (an imsd restart), and give the
P-CSCF a host route over the netdev instead. imsd talks to the P-CSCF
only and already installs a host route per media peer; the RA's prefix
and MTU still apply. The host route makes the P-CSCF reachable before
the RA has been processed, so the PDN MTU ModemManager reports is set
on the link as well, the way NetworkManager does for its bearers. A
failed host route, or a P-CSCF that is not an IPv6 address (the PDN is
brought up IPv6-only), fails the unit with a clear message.
Dev phone (KPN, 0.3.8-r0): restart path drops the RA default and
registers over the host route, apk update in 6 s where it hung; MO and
MT calls with audio both ways; cold boot never installs the default
route, the link carries the 1280 MTU before the host route exists,
fresh registration in 1 s.
2026-09-24 04:12:39 +02:00
|
|
|
|
*) log "PCSCF '$PCSCF' is not an IPv6 address; the ims PDN is brought up IPv6-only"; exit 1 ;;
|
|
|
|
|
|
esac
|
2026-07-22 22:53:28 +02:00
|
|
|
|
|
ims-pdn-up: discover the P-CSCF list once the PDN is up and hand it to imsd
With the netdev configured, the script runs imsd-pcscf against the
netdev's rmnet mux id (and the ims profile index when it knows one) and
hands the IPv6 addresses it prints to imsd as PCSCF_DISCOVERED= in
/run/imsd.env and gives the first — the daemon's fresh target — a host
route (the SA peer of a registration imsd resumes is routed by imsd itself,
as the configured address's arm already left it); IPv4 addresses and names
are logged and dropped (the PDN is IPv6-only), at most eight are handed
over, and nothing usable is logged with its cause and is not the
script's failure: imsd resumes a registration of its own when it can, else
says so and exits, and the restart runs discovery again. PCSCF= still wins and skips discovery; a list in it is refused.
The script does not read the variable from its environment: the unit
hands it the previous run's file, and a stale discovered value must not
pass for the admin's setting. The reader's path is the IMSD_PCSCF seam
the mock bench uses.
Tested: on the mock bench against this commit's script (the twelve 0.3.10
scenarios; this step's discovery arms S13–S17 and S19–S22, with S18 the
pre-existing non-IPv6 refusal; S55, the reader's rc-0 answer without an
address), and in builds of this series on the dev phone (KPN), whose PCO —
two IPv6 addresses, nothing else — reaches the plain found arm; the
configured skip, the bracketed refusal and, through the IMSD_PCSCF seam, the
reader's failure and its exit 2 ran on the phone too; the v4/FQDN line, the
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list refusal
and the rc-0 arm on the mock only. On a tip build of this series (daemon
2ec1a02f62831edb; bench 40, 2026-10-13) a fresh registration reached a target
this script had not routed — the reader stubbed through the IMSD_PCSCF seam to
find none, the address in /etc/imsd.env as PCSCF_DISCOVERED= — over imsd's own
route, and a resume stood with the peer's route removed first.
2026-10-11 16:16:46 +02:00
|
|
|
|
# ---- hand the connected ims netdev, the PDN's own address and the
|
|
|
|
|
|
# discovered P-CSCF list to imsd (imsd.service reads /run/imsd.env): the
|
|
|
|
|
|
# daemon carries no baked-in interface name and registers on the address
|
|
|
|
|
|
# the network assigned — not on the kernel's temporary SLAAC address from
|
|
|
|
|
|
# the RA, which the netdev lists first and which is deprecated after a day
|
|
|
|
|
|
# and removed after two (ims ledger F18; the SAs and the Contact would go
|
|
|
|
|
|
# with it). PCSCF_DISCOVERED= is written on every run, empty when nothing
|
|
|
|
|
|
# was discovered or PCSCF= is set.
|
|
|
|
|
|
printf 'DEV=%s\nLOCAL=%s\nPCSCF_DISCOVERED=%s\n' "$IFACE" "$ADDR" "$DISCOVERED" > /run/imsd.env
|
|
|
|
|
|
log "wrote /run/imsd.env: DEV=$IFACE LOCAL=$ADDR PCSCF_DISCOVERED=$DISCOVERED"
|
2026-07-22 22:53:28 +02:00
|
|
|
|
|
|
|
|
|
|
# ---- 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
|