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.
This commit is contained in:
parent
bd52afd931
commit
1d28063700
6 changed files with 186 additions and 86 deletions
109
AGENTS.md
109
AGENTS.md
|
|
@ -23,7 +23,10 @@ and GNOME Mobile — places and receives real VoLTE calls on a commercial
|
|||
network through it.
|
||||
|
||||
It talks to the modem through ModemManager (`mmcli`) and `qmicli` over QRTR
|
||||
(the USIM AKA run goes through a UIM logical channel), installs its security
|
||||
(the USIM AKA run goes through a UIM logical channel) and, for the P-CSCF list
|
||||
the network put in the ims PDN's PCO, through its own QMI WDS client
|
||||
(`imsd-pcscf`, a raw QRTR socket bound to the bearer's mux data port — §13),
|
||||
installs its security
|
||||
associations with `ip xfrm`, and plays and records audio with the PipeWire
|
||||
tools (`pw-play`, `pw-record`). In practice that means a Qualcomm modem on a
|
||||
mainline kernel with working WWAN/ModemManager integration, kernel ESP, and
|
||||
|
|
@ -71,7 +74,7 @@ in every source but the re-exporting `Imsd.cppm`, clang + libc++ + lld.
|
|||
| `imsd-media` | executable | `implementations/media.cpp` | `-ldl -lpthread` | The RTP/AMR data plane, spawned once per call. No GLib, no core. A separate process for crash isolation and for the far-end-hangup exit-code contract (§7.6). `-- --product=media`. |
|
||||
| `imsd-dialerd` | executable | `implementations/dialerd.cpp` | gio-2.0 | Plasma Mobile integration: a session daemon owning `org.kde.telephony.{CallUtils,DeviceUtils,UssdUtils}`, bridging to imsd. GDBus translation only. A separate process because those are per-user session-bus names a root system daemon cannot own. `-- --product=dialerd`. |
|
||||
| `imsd-ofonod` | executable | `implementations/ofonod.cpp` | `imsd-bus`, gio-2.0 | GNOME Calls integration: a system daemon owning `org.ofono`, presenting imsd as an oFono modem. GDBus translation plus the polkit gate on its own callers. `-- --product=ofonod`. |
|
||||
| `imsd-pcscf` | executable | `implementations/pcscf.cpp` | `imsd-core` | The P-CSCF reader (standalone in this tree: nothing runs it yet): one QMI WDS client over a raw QRTR socket, bound to the bearer's mux data port, asking for the call's runtime settings. Prints the PCO's P-CSCF list; never sends Start Network or Stop Network (its writes are its own client's mux binding and IP family, §12), and the call stayed connected and the registration stood after it (§13). Built with `-- --product=pcscf`. |
|
||||
| `imsd-pcscf` | executable | `implementations/pcscf.cpp` | `imsd-core` | The P-CSCF reader `ims-pdn-up.sh` runs once the ims PDN is up (§7.3): one QMI WDS client over a raw QRTR socket, bound to the bearer's mux data port, asking for the call's runtime settings. Prints the PCO's P-CSCF list; never sends Start Network or Stop Network (its writes are its own client's mux binding and IP family, §12), and the call stayed connected and the registration stood after it (§13). Built with `-- --product=pcscf`. |
|
||||
|
||||
The rule behind the split: **`imsd-core` stays GLib-free and free of I/O so
|
||||
that every decision in it has a unit test.** New protocol logic goes into a
|
||||
|
|
@ -255,8 +258,9 @@ What the suites pin:
|
|||
other addresses left).
|
||||
- `Qmi` — the request bytes `imsd-pcscf` sends (four of the five the probe's,
|
||||
byte for byte; Get Profile Settings is the helper's own, answered by the
|
||||
modem on the dev phone in the probe capture's `--profile 2 -v` run — the
|
||||
`profile-pcscf-via-pco` flag line),
|
||||
modem on the dev phone in the probe capture's `--profile 2 -v` run and on the dev phone's bring-up
|
||||
discoveries (the 2026-10-11/12 KPN bench), which pass `--profile` when the
|
||||
ims profile index is known — the `profile-pcscf-via-pco` flag line),
|
||||
the parser on the modem's 163-byte Get Current Settings answer in its exact
|
||||
TLV shape (addresses re-encoded to `2001:db8::/32`), the OutOfCall answer,
|
||||
the IPv4/FQDN list readers on synthetic lists, the tolerance of a count
|
||||
|
|
@ -390,9 +394,16 @@ Rules:
|
|||
pair); `RemoveDeadPair`'s
|
||||
no-peer line (`N xfrm entries of imsd's own with no pair to resume:
|
||||
removing them`); `RunOwnXfrmDeletes`' line for a delete that failed (`… -> rc
|
||||
N`: an entry gone between the read and the delete); the holds file's `pdn`
|
||||
compare in canonical form (no capture
|
||||
stages a holds file whose `pdn` is spelled unlike `LOCAL`).
|
||||
N`: an entry gone between the read and the delete); the holds file's `pdn` compare in canonical form (no capture
|
||||
stages a holds file whose `pdn` is spelled unlike `LOCAL`); the discovery
|
||||
arms beyond the plain found one — the v4/FQDN `also gave` line, the
|
||||
eight-address cap, the IPv4-only and no-mux-id skips, the comma-list
|
||||
`PCSCF=` refusal, the reader's rc-0 answer without an address — on the mock
|
||||
bench only (S13, S19–S22, S55): KPN's PCO carries
|
||||
two IPv6 addresses and nothing else, so on the real PCO the dev phone
|
||||
reached the found arm alone; the configured skip, the bracketed refusal
|
||||
and, through the `IMSD_PCSCF` seam, the reader's failure and its exit 2 ran
|
||||
on the dev phone too (2026-10-11/12).
|
||||
- `imsd-pcscf` ran on the dev phone (KPN) on 2026-10-11 against the live ims
|
||||
bearer: the `--mux 1 --profile 2 -v` sequence, a plain run, and `OutOfCall`
|
||||
on an unbound mux (the fp6 journal's `2026-10-11-pcscf-pco-probe` capture,
|
||||
|
|
@ -480,9 +491,12 @@ through `/etc/skel/.config/autostart/org.kde.modem.daemon.desktop`);
|
|||
|
||||
`imsd.service` runs two `ExecStartPre=` steps before the daemon:
|
||||
|
||||
1. `/usr/libexec/ims-pdn-up.sh` brings the `ims` PDN up through ModemManager
|
||||
(§7.3) and writes `/run/imsd.env` with `DEV=` (the connected netdev) and
|
||||
`LOCAL=` (the PDN address ModemManager assigned).
|
||||
1. `/usr/libexec/ims-pdn-up.sh` brings the `ims` PDN up through ModemManager,
|
||||
reads the P-CSCF list the network put in the PDN's PCO (`imsd-pcscf`;
|
||||
skipped when `PCSCF=` is set), gives the first a host route (§7.3) and
|
||||
writes `/run/imsd.env` with `DEV=` (the connected netdev), `LOCAL=` (the
|
||||
PDN address ModemManager assigned) and `PCSCF_DISCOVERED=` (the IPv6 addresses of that list, at most eight,
|
||||
comma-separated in the modem's order; IPv4 entries and names dropped).
|
||||
2. `/usr/libexec/imsd-handover.sh` (`ExecStartPre=-`, failure-tolerant) hands
|
||||
a pre-0.3.10 daemon's throttle wait over (§7.5).
|
||||
|
||||
|
|
@ -498,9 +512,8 @@ a 5-minute registration gate, connect retries). `Restart=on-failure`,
|
|||
The daemon's own bring-up (`BringUp()` in `main.cpp`):
|
||||
|
||||
1. The P-CSCF a fresh registration goes to is `PCSCF=` when set, else the
|
||||
first address of `PCSCF_DISCOVERED=` (a comma-separated IPv6 list read
|
||||
from the environment; nothing in this tree writes it yet, so a bench sets
|
||||
it by hand); the journal says which (`pcscf: configured …` / `pcscf:
|
||||
first address of `PCSCF_DISCOVERED=` (the bring-up's reading of the PDN's
|
||||
PCO, §7.3); the journal says which (`pcscf: configured …` / `pcscf:
|
||||
discovered … (using …)`). A resume needs neither: the kernel's SAs name
|
||||
its P-CSCF (step 3). With neither, a bring-up that did not resume fails
|
||||
with `no P-CSCF for a fresh registration: …`, and since no fresh one
|
||||
|
|
@ -672,30 +685,48 @@ configures the muxed netdev. Idempotent; a non-zero exit fails the unit.
|
|||
walled garden and hang). The RA's prefix and MTU still apply. An RA default
|
||||
route already present (an imsd restart) is flushed.
|
||||
- Sets the PDN MTU ModemManager reports on the link (1280 on the FP6), adds
|
||||
the address, waits out DAD, and installs a host route to `PCSCF`
|
||||
(`dev <netdev>`, no gateway — a raw-IP netdev without header_ops). The PDN
|
||||
is IPv6-only, so `PCSCF` must be an IPv6 address in RFC 5952 §4 form (§7.2 step 3: what
|
||||
`ip` prints for a plain IPv6 address; an IPv4-mapped address in hex only —
|
||||
a bracketed value is refused here with that hint, an upper-case or
|
||||
uncompressed one by the daemon with the spelling to use once the route
|
||||
step took it); anything else on the IMS
|
||||
network (its DNS servers, a traceroute target) needs its own host route
|
||||
(`ip -6 route add <addr> dev qmapmux0.0`). imsd installs a host route
|
||||
the address and waits out DAD.
|
||||
- Resolves the P-CSCF: `PCSCF` when set (one IPv6 address; it wins and
|
||||
discovery is skipped — a list or a non-IPv6 value exits non-zero), else
|
||||
`imsd-pcscf --mux <the netdev's rmnet mux_id> [--profile <index>]` reads
|
||||
the list the network put in the PDN's PCO from the modem (§13; the mux id
|
||||
from `ip -d link show <netdev>`), not for `IMS_IP_TYPE=ipv4`, which the bring-up does not support (it
|
||||
configures only the PDN's IPv6 address). The usable
|
||||
addresses — IPv6; IPv4 ones and names are logged and dropped — are handed
|
||||
over (at most 8) and the first, the daemon's fresh target, gets a host route
|
||||
(`dev <netdev>`, no gateway — a raw-IP netdev without header_ops); 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 (§7.2 steps 3 and
|
||||
4) —, as each call's media peer is. Nothing usable is logged with
|
||||
its cause (`nothing usable (…)`) and is not the script's failure: imsd
|
||||
resumes a registration of its own when it can and stays up on it (a fresh
|
||||
registration later restarts the daemon for want of a target); with nothing
|
||||
to resume it says so and exits, and the unit's restart runs discovery
|
||||
again. Anything else on
|
||||
the IMS network (its DNS servers, a traceroute target) needs its own host
|
||||
route (`ip -6 route add <addr> dev qmapmux0.0`). imsd installs a host route
|
||||
per media peer itself, and one to the P-CSCF it registers to or resumes on
|
||||
(§7.2 step 3).
|
||||
- Opens imsd's protected ports 45061–45062 (TCP and UDP) on `qmapmux*` in
|
||||
nftables when a `inet filter` table exists (pmOS's default INPUT chain drops
|
||||
inbound on `qmapmux*`, which silently killed every network-initiated
|
||||
request after ESP decapsulation). Best-effort.
|
||||
- Writes `/run/imsd.env`: `DEV=<netdev>`, `LOCAL=<PDN address>`. imsd
|
||||
- Writes `/run/imsd.env`: `DEV=<netdev>`, `LOCAL=<PDN address>`,
|
||||
`PCSCF_DISCOVERED=<list>` (comma-separated in the modem's order; empty when
|
||||
nothing was discovered or `PCSCF` is set), and logs the line it wrote. imsd
|
||||
registers on the address the network assigned, not on the kernel's
|
||||
temporary SLAAC address from the RA, which the netdev lists first and
|
||||
retires after about two days (the SAs and the Contact would go with it).
|
||||
The script never reads `PCSCF_DISCOVERED` from its environment: the unit
|
||||
hands it the previous run's file, and a stale discovered value must not
|
||||
pass for the admin's `PCSCF=`.
|
||||
|
||||
Its own settings (read from the environment, else from `/etc/imsd.env` for
|
||||
manual runs): `IMS_APN` (default `ims`), `IMS_IP_TYPE` (`ipv6`),
|
||||
`IMS_PROFILE_ID` (empty = look up; `none` = connect by APN string),
|
||||
`IMS_REG_TIMEOUT` (300), `PCSCF`.
|
||||
`IMS_REG_TIMEOUT` (300) and `PCSCF`; from the environment only, `IMSD_PCSCF`
|
||||
(the reader's path, default `/usr/libexec/imsd-pcscf` — the mock bench's
|
||||
seam).
|
||||
|
||||
### 7.4 Registration recovery (`RegRecovery`, `Imsd-Engine.cppm`)
|
||||
|
||||
|
|
@ -905,8 +936,8 @@ account `user`, its old default.
|
|||
|
||||
| Path | Written by | Content |
|
||||
|---|---|---|
|
||||
| `/etc/imsd.env` | the admin | configuration (§10); `PCSCF=` is where a fresh registration goes — a resume needs none (§7.2 step 1). The apk adds a drop-in with `ConditionPathExists=/etc/imsd.env` (§9.2), so an unconfigured phone does not boot into a failing unit |
|
||||
| `/run/imsd.env` | `ims-pdn-up.sh` | `DEV=`, `LOCAL=` of the connected PDN |
|
||||
| `/etc/imsd.env` | the admin | configuration (§10), none required where discovery finds a P-CSCF (§7.3): `PCSCF=` overrides what discovery found, and names the P-CSCF where discovery is skipped or finds none. The apk adds a drop-in with `ConditionPathExists=/etc/imsd.env` (§9.2), so a phone without the file does not start the unit |
|
||||
| `/run/imsd.env` | `ims-pdn-up.sh` | `DEV=`, `LOCAL=` of the connected PDN, `PCSCF_DISCOVERED=` the IPv6 addresses of the PCO's P-CSCF list in the modem's order, at most eight (IPv4 entries and names are dropped; empty when the PCO gave no IPv6 P-CSCF, when discovery did not run or failed, or when `PCSCF=` is set) |
|
||||
| `/run/imsd.holds` (`HOLDS_FILE`) | imsd, `imsd-handover.sh` | the network's holds, the schedule's place and the reducer's refusal count (§7.4) |
|
||||
| `/run/imsd.handover` | `imsd.post-upgrade` | the InvocationID of the daemon an upgrade replaces, written when no holds file exists (§7.5); consumed by `imsd-handover.sh` |
|
||||
| `/run/imsd-ofonod.started` | `imsd-ofono-restart-calls.sh` | marker: imsd-ofonod has started once since boot |
|
||||
|
|
@ -949,7 +980,7 @@ Lines the operator (and the scripts) look for, all prefixed `imsd: `:
|
|||
P-CSCF — a pre-1.0.0 file does not; with no fresh target both end in
|
||||
`nothing can resume it: the pair is removed` instead), `no P-CSCF for a
|
||||
fresh registration: none configured (PCSCF=) and none discovered
|
||||
(PCSCF_DISCOVERED empty)`.
|
||||
(PCSCF_DISCOVERED empty — see the ims-pdn-up lines)`.
|
||||
- `PCSCF='…' is not written as ip prints it; use PCSCF=…` (under `bring-up
|
||||
FAILED:`) — one accepted spelling of the setting (§7.2 step 3); the
|
||||
bring-up script's `ims-pdn-up: PCSCF '…': write the bare address, as ip
|
||||
|
|
@ -995,8 +1026,12 @@ Lines the operator (and the scripts) look for, all prefixed `imsd: `:
|
|||
- `connect: no answer from the P-CSCF N times (…)` when the protected
|
||||
connect series ends unanswered.
|
||||
|
||||
From the scripts: `ims-pdn-up: …`, `imsd-handover: …`,
|
||||
`imsd-ofono-restart-calls: …`.
|
||||
From the scripts: `ims-pdn-up: …` — among them `P-CSCF discovered: a, b
|
||||
(pco-requested 1, profile-pcscf-via-pco 1)`, `P-CSCF discovery also gave v4 …
|
||||
(not usable on the IPv6-only ims PDN)`, `P-CSCF discovery: nothing usable
|
||||
(…)`, `P-CSCF X configured: discovery skipped`, `host route to P-CSCF X dev
|
||||
Y` (the configured address or the first discovered), `wrote /run/imsd.env: DEV=… LOCAL=… PCSCF_DISCOVERED=…` —,
|
||||
`imsd-handover: …`, `imsd-ofono-restart-calls: …`.
|
||||
|
||||
## 8. D-Bus ABI and desktop integration
|
||||
|
||||
|
|
@ -1220,8 +1255,8 @@ there:
|
|||
|
||||
### 10.3 ims-pdn-up.sh and imsd-ofonod
|
||||
|
||||
`IMS_APN`, `IMS_IP_TYPE`, `IMS_PROFILE_ID`, `IMS_REG_TIMEOUT` and `PCSCF` for
|
||||
the bring-up script (§7.3). `imsd-ofonod` reads `EMERGENCY_NUMBERS` (from
|
||||
`IMS_APN`, `IMS_IP_TYPE`, `IMS_PROFILE_ID`, `IMS_REG_TIMEOUT`, `PCSCF` and
|
||||
`IMSD_PCSCF` for the bring-up script (§7.3). `imsd-ofonod` reads `EMERGENCY_NUMBERS` (from
|
||||
`/etc/imsd.env` through its unit) and `IMSD_BUS=session|system` to override
|
||||
only the imsd side of its bus choice.
|
||||
|
||||
|
|
@ -1293,15 +1328,17 @@ only the imsd side of its bus choice.
|
|||
Security-Server list. Dumps are mode 0600 under the state dir, off by default
|
||||
(`DUMP_SIP=0`), and never leave the phone in a commit.
|
||||
- The ims PDN is a walled garden: `ims-pdn-up.sh` keeps it from becoming
|
||||
the default route and gives the P-CSCF a host route; imsd adds one per
|
||||
call for the media peer (§7.3) and one for the P-CSCF it registers to or resumes on (§7.2 step 3; the
|
||||
script routes only the configured address). Nothing else is routed over it.
|
||||
the default route and gives the P-CSCF a host route — the configured one,
|
||||
or the first IPv6 address the modem reports from the PCO (the fresh target; at most eight
|
||||
are handed over, IPv4 entries and names dropped); imsd adds one per call for
|
||||
the media peer (§7.3) and one for the P-CSCF it registers to or resumes on
|
||||
(§7.2 step 3). Nothing else is routed over it.
|
||||
A change that routes anything else over it needs a reason and a bench.
|
||||
- The fixed protected ports (45061/45062) are opened in nftables for
|
||||
`qmapmux*` only, by the bring-up script.
|
||||
|
||||
- **`imsd-pcscf` holds a QMI WDS client of its own on the modem** (nothing
|
||||
in this tree runs it yet). It binds to the bearer's mux data port and reads
|
||||
- **`imsd-pcscf` holds a QMI WDS client of its own on the modem, as root,
|
||||
from `ims-pdn-up.sh`.** It binds to the bearer's mux data port and reads
|
||||
the call's runtime settings; it never sends Start Network or Stop Network. Its only writes
|
||||
are its own client's mux binding and IP family; the call stayed connected
|
||||
and the registration stood after it (§13). One client socket, closed on
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -18,7 +18,7 @@ Call-capable, both directions: outgoing calls place, answer, carry full-duplex
|
|||
AMR-WB audio and tear down. Incoming calls ring, answer and carry media the
|
||||
same way, driven end-to-end from a stock dialer over a frozen D-Bus ABI.
|
||||
Not there yet: DTMF (`SendDtmf` is a stub), hold/multiparty/call waiting,
|
||||
USSD, P-CSCF discovery (the address is configured by hand).
|
||||
USSD.
|
||||
|
||||
Verified by community members to be fully working:
|
||||
|
||||
|
|
@ -34,8 +34,7 @@ Verified by community members to be fully working:
|
|||
imsd talks to the modem through ModemManager plus `qmicli` over QRTR, installs
|
||||
its security associations with `ip xfrm` and plays audio through PipeWire, so
|
||||
in practice: a Qualcomm modem on a mainline kernel with working
|
||||
WWAN/ModemManager integration, kernel ESP, PipeWire, systemd, polkit and
|
||||
your carrier's P-CSCF address.
|
||||
WWAN/ModemManager integration, kernel ESP, PipeWire, systemd and polkit.
|
||||
|
||||
## Install and configure
|
||||
|
||||
|
|
@ -49,17 +48,9 @@ Both can be found in the packages tab:
|
|||
https://forgejo.catcrafts.net/Catcrafts/-/packages/alpine/imsd
|
||||
```
|
||||
|
||||
Configuration is environment variables in `/etc/imsd.env`; the one setting a fresh registration
|
||||
needs is the P-CSCF:
|
||||
|
||||
```sh
|
||||
# your carrier's P-CSCF address, capture via: `adb shell dumpsys telephony.registry` on Android
|
||||
PCSCF=2001:db8::105
|
||||
```
|
||||
|
||||
The apk's `imsd.service` is enabled by preset and starts at boot once that
|
||||
file exists (the condition is checked when the unit starts, so after writing
|
||||
the file run `systemctl start imsd`). It first brings the `ims` PDN up through ModemManager, then registers:
|
||||
The apk's `imsd.service` is enabled by preset and starts at boot once
|
||||
`/etc/imsd.env` exists (`touch /etc/imsd.env` is enough; the condition is checked when
|
||||
the unit starts, so after creating the file run `systemctl start imsd`). It first brings the `ims` PDN up through ModemManager, then registers:
|
||||
resuming an existing registration when it can, registering fresh otherwise,
|
||||
and waiting out whatever the network tells it to wait out. Check on it with:
|
||||
|
||||
|
|
@ -96,10 +87,13 @@ session and root may place, answer and end calls; an ssh login may not.
|
|||
|
||||
## Full config:
|
||||
|
||||
Configuration is environment variables in `/etc/imsd.env`, none are required.
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `PCSCF` | *(the first of `PCSCF_DISCOVERED`)* | P-CSCF address (IPv6), exactly one — not a comma-separated list — in RFC 5952 §4 form (lowercase, leading zeros dropped, the longest zero run compressed, no brackets — what `ip` prints for a plain IPv6 address; an IPv4-mapped address only as hex, `::ffff:a00:1` — the dotted spelling RFC 5952 §5 and `ip` use for it is refused); upper case, leading zeros, an uncompressed run or brackets are refused at start with the spelling to use (in the packaged unit the bring-up script refuses brackets first, with the rule), a dotted-quad tail with the rule; before 1.0.0 an upper-case, zero-padded or uncompressed `PCSCF=` matched no SA in `ip xfrm`'s text and registered fresh at every restart — an upgraded phone with such a setting stops at start with the spelling to use. Set, it wins over `PCSCF_DISCOVERED`. It is where a fresh registration goes; a registration resumed from the kernel's SAs stays on the P-CSCF it was made to until its next fresh registration. With neither this nor `PCSCF_DISCOVERED`, a fresh registration fails with a clear error |
|
||||
| `PCSCF_DISCOVERED` | *(empty)* | a comma-separated IPv6 P-CSCF list, the first of which is used when `PCSCF` is unset; entries `CanonicalIp6` does not read — an IPv4 address, a name, an IPv6 literal with a zone or a dotted-quad tail (an IPv4-mapped entry in hex only) — are logged and ignored. imsd reads it from its environment; nothing in this tree writes it yet (a bench sets it in `/etc/imsd.env`) |
|
||||
| `PCSCF_DISCOVERED` | *(from the PDN's PCO)* | the IPv6 P-CSCF list the network put in the ims PDN's PCO, comma-separated in the order the modem reports it; the first entry `CanonicalIp6` accepts is used when `PCSCF` is unset. The packaged service gets it via `/run/imsd.env` from `ims-pdn-up.sh`, which reads it from the modem with `imsd-pcscf` (at most eight; it routes the first; imsd routes the P-CSCF it uses — the fresh target, the resumed SA peer — itself as well); entries `CanonicalIp6` does not read — an IPv4 address, a name, an IPv6 literal with a zone or a dotted-quad tail (an IPv4-mapped entry in hex only) — are logged and ignored. In `/etc/imsd.env` it is a bench seam |
|
||||
| `IMSD_PCSCF` | `/usr/libexec/imsd-pcscf` | the P-CSCF reader `ims-pdn-up.sh` runs — a bench seam (the mock bench's fake reader; a stub through a unit drop-in on the dev phone), from the environment |
|
||||
| `PCSCF_PORT` | `5060` | unprotected SIP port for the initial REGISTER |
|
||||
| `DEV` | `qmapmux0.0` | ims-PDN netdev; the packaged service passes the real one via `/run/imsd.env` |
|
||||
| `LOCAL` | *(auto)* | UE address (compared in RFC 5952 form). The packaged service gets the PDN address ModemManager assigned, via `/run/imsd.env` from `ims-pdn-up.sh`; a manual run prefers a global IPv6 on `DEV` that is neither temporary nor `dynamic` (RA-derived), then any non-temporary one, then the first (the kernel's temporary SLAAC address is listed first, and a registration bound to it is lost when the kernel retires it) |
|
||||
|
|
|
|||
|
|
@ -1192,7 +1192,7 @@ private:
|
|||
// (FreshRecover), as local_ does against pdnLocal_.
|
||||
std::string pcscfTarget_;
|
||||
std::vector<std::string> pcscfCandidates_;
|
||||
static constexpr std::string_view NoFreshPcscf = "no P-CSCF for a fresh registration: none configured (PCSCF=) and none discovered (PCSCF_DISCOVERED empty)";
|
||||
static constexpr std::string_view NoFreshPcscf = "no P-CSCF for a fresh registration: none configured (PCSCF=) and none discovered (PCSCF_DISCOVERED empty — see the ims-pdn-up lines)";
|
||||
// The PDN's own address (LOCAL, else DetectLocal) — every fresh
|
||||
// registration's. local_ differs from it only while a resume runs on the
|
||||
// address an older registration was made on (TryResume).
|
||||
|
|
@ -1382,10 +1382,9 @@ private:
|
|||
local_ = pdnLocal_; ctx_.local = local_;
|
||||
KeepHolds();
|
||||
pcscf_ = pcscfTarget_;
|
||||
// the target's host route is imsd's own: the bring-up script routes only
|
||||
// the configured address (§7.2 step 3), not a target set in /etc/imsd.env
|
||||
// as PCSCF_DISCOVERED= nor a route lost with the PDN — the same idempotent
|
||||
// replace as a media peer's
|
||||
// the target's host route is imsd's own: the bring-up script routes the
|
||||
// first address of its own read, not a list set in /etc/imsd.env nor a
|
||||
// route lost with the PDN — the same idempotent replace as a media peer's
|
||||
RunCmd({"ip", Is6(pcscf_) ? "-6" : "-4", "route", "replace", pcscf_, "dev", dev_});
|
||||
if (recovery_.Next(Boot()).at > Boot()) Log(std::format("the network's hold stands; {}", NextStep()));
|
||||
if (!WaitForPolicy()) throw Stopping{};
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
#
|
||||
# Finds-or-creates the ims bearer via ModemManager, connects it, and
|
||||
# configures the muxed netdev with the MM-assigned address (the s34 recipe,
|
||||
# automated) and a host route to the P-CSCF — never a default route (F15
|
||||
# below). Idempotent — safe to run when the PDN is already up. Runs as
|
||||
# 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
|
||||
# ExecStartPre of imsd.service, so imsd only starts once the PDN exists; a
|
||||
# nonzero exit fails the unit and systemd retries per Restart/RestartSec.
|
||||
#
|
||||
|
|
@ -22,8 +25,13 @@
|
|||
# (journal/ims 2026-09-16/17 bench: Odido, O2 UK, Swisscom).
|
||||
# IMS_REG_TIMEOUT max seconds to wait for network registration before
|
||||
# connect attempts start counting anyway (default 300)
|
||||
# PCSCF the P-CSCF address imsd registers with (its own
|
||||
# required setting); gets the host route over the ims PDN
|
||||
# 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)
|
||||
|
||||
log() { echo "ims-pdn-up: $*"; }
|
||||
|
||||
|
|
@ -291,31 +299,91 @@ log "$IFACE up, $ADDR/${PREFIX:-64}, ipv6 mtu $(cat "/proc/sys/net/ipv6/conf/$IF
|
|||
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
|
||||
# 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. The PDN is brought up IPv6-only (the address above), so
|
||||
# the P-CSCF must be an IPv6 address; anything else on the IMS network
|
||||
# (its DNS servers, a traceroute target) needs its own host route. Without
|
||||
# this route imsd cannot reach the P-CSCF, so a failure is the unit's
|
||||
# failure.
|
||||
# ---- 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"
|
||||
}
|
||||
PCSCF=${PCSCF:-$(envval PCSCF)}
|
||||
DISCOVERED=
|
||||
case "$PCSCF" in
|
||||
'') log "PCSCF not configured: no P-CSCF host route (imsd needs PCSCF= in /etc/imsd.env)" ;;
|
||||
'')
|
||||
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 ;;
|
||||
*\[*) log "PCSCF '$PCSCF': write the bare address, as ip prints it (no brackets)"; exit 1 ;;
|
||||
*:*) OUT=$(ip -6 route replace "$PCSCF/128" dev "$IFACE" 2>&1) ||
|
||||
{ log "host route to P-CSCF $PCSCF failed: $(squash "$OUT")"; exit 1; }
|
||||
log "host route to P-CSCF $PCSCF dev $IFACE" ;;
|
||||
*:*) log "P-CSCF $PCSCF configured: discovery skipped"; route_pcscf "$PCSCF" ;;
|
||||
*) log "PCSCF '$PCSCF' is not an IPv6 address; the ims PDN is brought up IPv6-only"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ---- hand the connected ims netdev and the PDN's own address 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)
|
||||
printf 'DEV=%s\nLOCAL=%s\n' "$IFACE" "$ADDR" > /run/imsd.env
|
||||
# ---- 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"
|
||||
|
||||
# ---- 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
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ ExecStartPre=/usr/libexec/ims-pdn-up.sh
|
|||
ExecStartPre=-/usr/libexec/imsd-handover.sh
|
||||
TimeoutStartSec=900
|
||||
# /run/imsd.env: written by ims-pdn-up.sh (DEV= the connected ims netdev,
|
||||
# LOCAL= the PDN address ModemManager assigned to it).
|
||||
# /etc/imsd.env: admin configuration — PCSCF= is REQUIRED (the carrier's
|
||||
# P-CSCF address; see AGENTS.md §10, or the README) and wins over the
|
||||
# runtime-derived values.
|
||||
# LOCAL= the PDN address ModemManager assigned to it, PCSCF_DISCOVERED= the
|
||||
# P-CSCF list the network put in the PDN's PCO, read from the modem).
|
||||
# /etc/imsd.env: admin configuration, none required where discovery finds a
|
||||
# P-CSCF (AGENTS.md §10, the README); it wins over the runtime-derived values
|
||||
# — PCSCF= overrides discovery.
|
||||
EnvironmentFile=-/run/imsd.env
|
||||
EnvironmentFile=-/etc/imsd.env
|
||||
# registration state (imsreg.state) + media stats live here
|
||||
|
|
|
|||
|
|
@ -91,9 +91,10 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
// (org.ofono.*) bridging to imsd, for Phosh and
|
||||
// GNOME Mobile (GDBus translation plus the imsd-bus
|
||||
// polkit gate on its callers, no core).
|
||||
// pcscf — imsd-pcscf, the P-CSCF reader for the bring-up: one
|
||||
// QMI WDS client over a raw QRTR socket (pure std +
|
||||
// POSIX, links the core for Imsd:Qmi, no GLib).
|
||||
// pcscf — imsd-pcscf, the P-CSCF reader ims-pdn-up.sh runs
|
||||
// once the ims PDN is up: one QMI WDS client over a
|
||||
// raw QRTR socket (pure std + POSIX, links the core
|
||||
// for Imsd:Qmi, no GLib).
|
||||
bool wantMedia = false;
|
||||
bool wantDialerd = false;
|
||||
bool wantOfonod = false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue