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).
This commit is contained in:
Jorijn van der Graaf 2026-10-11 15:55:19 +02:00
commit b60a3633c1
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
7 changed files with 84 additions and 22 deletions

View file

@ -374,23 +374,28 @@ export namespace imsd::ipsec {
// state: (src==LOCAL,dst==P): reqid1->spiPs reqid2->spiPc
// (src==P,dst==LOCAL): reqid1->spiUc reqid2->spiUs
// policy: dir out, reqid1->portPs=dport, reqid2->portPc=dport
// Addresses are compared in RFC 5952 form on both sides (the kernel prints
// that form; PCSCF=/LOCAL= arrive in any spelling), so the spelling in
// /etc/imsd.env can no longer hide a registration's SAs.
// Returns nullopt unless spiPs, spiPc, portPs, portPc are all found.
std::optional<ExistingSa> ParseExistingSa(std::string_view stateText, std::string_view policyText, std::string_view pcscf, std::string_view local) {
const std::string p = imsd::util::CanonicalAddr(pcscf);
const std::string l = imsd::util::CanonicalAddr(local);
ExistingSa sa;
bool havePs = false;
bool havePc = false;
bool havePortPs = false;
bool havePortPc = false;
for (std::string_view b : detail::SrcBlocks(stateText)) {
std::string_view src = detail::TokenAfter(b, "src ");
std::string_view dst = detail::TokenAfter(b, "dst ");
const std::string src = imsd::util::CanonicalAddr(detail::TokenAfter(b, "src "));
const std::string dst = imsd::util::CanonicalAddr(detail::TokenAfter(b, "dst "));
auto spi = detail::ParseHex32(detail::TokenAfter(b, "spi "));
auto reqid = detail::ParseDec(detail::TokenAfter(b, "reqid "));
if (!spi || !reqid) continue;
if (src == local && dst == pcscf && *reqid == 1) { sa.spiPs = *spi; havePs = true; }
if (src == local && dst == pcscf && *reqid == 2) { sa.spiPc = *spi; havePc = true; }
if (src == pcscf && dst == local && *reqid == 1) sa.spiUc = *spi;
if (src == pcscf && dst == local && *reqid == 2) sa.spiUs = *spi;
if (src == l && dst == p && *reqid == 1) { sa.spiPs = *spi; havePs = true; }
if (src == l && dst == p && *reqid == 2) { sa.spiPc = *spi; havePc = true; }
if (src == p && dst == l && *reqid == 1) sa.spiUc = *spi;
if (src == p && dst == l && *reqid == 2) sa.spiUs = *spi;
}
for (std::string_view b : detail::SrcBlocks(policyText)) {
if (b.find("dir out") == std::string_view::npos) continue;
@ -438,11 +443,12 @@ export namespace imsd::ipsec {
// order: where a registration made on another address of the link (the
// kernel's temporary one, before 0.3.10 — ims ledger F18) still lives.
std::vector<std::string> SaLocals(std::string_view stateText, std::string_view pcscf) {
const std::string p = imsd::util::CanonicalAddr(pcscf);
std::vector<std::string> locals;
for (std::string_view b : detail::SrcBlocks(stateText)) {
std::string_view src = detail::TokenAfter(b, "src ");
if (src.empty() || detail::TokenAfter(b, "dst ") != pcscf) continue;
if (std::ranges::find(locals, src) == locals.end()) locals.emplace_back(src);
const std::string src = imsd::util::CanonicalAddr(detail::TokenAfter(b, "src "));
if (src.empty() || imsd::util::CanonicalAddr(detail::TokenAfter(b, "dst ")) != p) continue;
if (std::ranges::find(locals, src) == locals.end()) locals.push_back(src);
}
return locals;
}