Commit graph imsd/tests/Ipsec
Author SHA1 Message Date
b60a3633c1
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-13 22:28:02 +02:00
729ccaa01d
imsd: a resumed registration echoes the 401's Security-Server list from the state file
A warm resume rebuilt its Security-Verify from the kernel's SA: one
mechanism with hmac-sha-1-96 and aes-cbc written in, where RFC 3329
§2.3.1 has the client mirror the server's whole list. The fresh
registration kept the 401's list in memory, so a registration this
process made fresh echoed it; after a restart, the resumed
registration's re-REGISTER, its keepalives, its reg-event SUBSCRIBE and
its INVITEs carried the rebuilt entry until the next fresh registration.
KPN's one-entry answer to the default offer is the rebuilt entry byte
for byte; against a six-entry list (O2's) the mirror is broken, which
the RFC has the server answer with a 494, and a refused resume
registers fresh.

The fresh registration now stores the joined Security-Server value in the
state file (security_server, next to the challenge answer) and the resume
takes it from there, logging the mechanism count, when the entry
carrying its SPIs names the SA pair in the kernel: a file an attempt the
process did not live to record left behind (its SAs installed, its 200
not written) names another. A file from before this change holds none.
In both cases the resume falls back to the rebuilt entry, says which,
and the registration's next state write stores that entry, which is what
it echoes; the choice is Imsd:Ipsec's, pinned in tests/Ipsec with O2's
recorded list and KPN's. A value with a quote, a backslash or a newline in it is
written empty, which the resume treats as absent; RFC 3329's token
syntax produces none of these, a quoted-string extension parameter
could carry the first two.

Tested on the dev phone (KPN) with the build of this tree: a resume from
a state file the 0.3.11 daemon wrote logs the fallback and succeeds, so
do one from a file whose list names another SA pair and one whose list
is empty; two keepalives at a 120-s interval; a fresh registration
stores KPN's list byte-for-byte as the journal shows it received; a
restart resumes with the list from the file; an outgoing call.
2026-10-11 02:56:47 +02:00
7eb2deae0c
ims-pdn-up, imsd: register on the PDN's own address, not the kernel's temporary one
imsd took the first global IPv6 on the ims netdev as its address, and
the kernel lists the temporary (privacy) SLAAC address first: `temporary
dynamic`, preferred for a day and valid for two by its lifetimes (read
2026-09-24). Contact, Via and the four SAs were bound to it, so once the
kernel removes it the registration has lost its address. The address
ModemManager assigned — the one the script installs — sat third on the
list. Seen on the dev phone; a field unit's journal shows the split too
— imsd registered on an address other than the one the script installed
(ims ledger F18).

Hand the assigned address to the daemon as LOCAL through /run/imsd.env,
next to DEV. For a manual run without it, prefer a global address that
is neither `temporary` nor `dynamic`, then any non-temporary one.

A restart after the upgrade finds the registration's SAs on the old
address. It resumes there while that address is still on the link, and
moves to the PDN's address in place: with its next fresh registration,
or when the old address leaves the link first. When the address it is
registered on leaves the netdev and the PDN's own address is not there
to move to, a registered imsd exits at its next recovery step, non-zero,
for a systemd restart, which runs the PDN bring-up again.

Tested on the dev phone (KPN) in builds of this series; not every path
has run there.
2026-10-06 14:01:33 +02:00
52736295a1
ipsec: build the SAs from the Security-Server mechanism the UE selects
A P-CSCF may answer the challenge with every mechanism it supports and
attach the SPIs and ports to the one it applied. O2 UK's core lists six
ipsec-3gpp entries with q-values, md5 first, and marks the fourth (sha1,
no ealg). imsd took the first ealg= anywhere in the header - the 3DES of
an entry the P-CSCF had not selected - and installed SAs the far end
could not decrypt: every protected packet after the 401 was dropped at
its IPsec layer, over TCP and UDP alike, with nothing to answer.

The 401 handling now parses the Security-Server as a mechanism list
(commas outside quotes, parameters on semicolons, names and values
case-insensitive) and selects the highest-q ipsec-3gpp mechanism that
matches what Security-Client offered (RFC 3329 2.3.1, TS 24.229
5.1.1.5.1): alg equal to ours, ealg (absent = null) equal to the offer.
The SPIs and ports are per registration and come from the entry carrying
them. When no entry matches the offer, the entry carrying the SPIs is
installed as listed and the journal says so; hmac-md5-96 becomes
hmac(md5) with IK as the key. A single-mechanism header selects itself:
on KPN the SAs are the same as before (unit-pinned argv, verified on the
phone). An answer that cannot be used - no ipsec-3gpp entry, a missing
spi/port, an alg, ealg, prot or mod the kernel cannot be handed - defers
the bring-up on the throttle schedule with the reason, before the AKA is
spent, instead of a fatal that would restart imsd every RestartSec with
an initial REGISTER each time; spi-s=0 stays the throttle deferral.
Repeated Security-Server header lines are joined into the one list
Security-Verify echoes.

The journal shows the Security-Server line, the selected mechanism, the
SA parameters, and any ip xfrm command that fails (keys masked) at every
fresh registration - the silence this bug produced had no line to read.
2026-09-19 21:36:57 +02:00
6e77823933 imsd 0.2.7 — initial public snapshot
Userspace VoLTE/IMS daemon for mainline Linux phones (developed on the
Fairphone 6): a GLib-free core (SIP, SDP, USIM AKA, IPsec SA setup, RTP
media, call engine) behind a GDBus control daemon, a standalone AMR-WB
media leg, and a Plasma Dialer backend. C++26 modules built with Crafter
Build; the tree is clean under the project's house-style linter
(crafter-build lint) across all three build products.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-22 23:04:21 +02:00