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.
This commit is contained in:
Jorijn van der Graaf 2026-10-10 23:42:01 +02:00
commit 729ccaa01d
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
5 changed files with 171 additions and 25 deletions

View file

@ -29,8 +29,9 @@ import :Sip;
export namespace imsd::msg {
// Everything the builders need that is stable for the life of a
// registration. Ports default to the fixed protected values; route/ppi/
// securityServer are filled in from the REGISTER 200 OK.
// registration. Ports default to the fixed protected values; route and
// ppi are filled in from the REGISTER 200 OK, securityServer from the
// 401 (at a resume, from the state file).
struct Context {
imsd::aka::Identity id;
std::string local;
@ -63,7 +64,7 @@ export namespace imsd::msg {
// self-targeted requests like reg-event
// SUBSCRIBE; the temporary (IMSI-derived)
// IMPU is barred for anything but REGISTER
std::string securityServer; // Security-Verify value (the network's ss)
std::string securityServer; // Security-Verify value: the 401's Security-Server list (from the state file on a resume)
// P-Access-Network-Info value (imsd::aka::ParsePani from live mmcli
// location: real MCC/MNC/TAC/ECI, FDD). Empty omits the header —
// never fabricate one: the network reads it to learn which access