Commit graph imsd/interfaces/Imsd-Messages.cppm
Author SHA1 Message Date
5c89dcb033
sip: UDP for the protected leg, TCP first with a fallback
imsd's protected leg (the second REGISTER and everything after it) was
TCP only. A P-CSCF that never answers the TCP connect on its protected
server port (O2 UK: the SYNs leave ESP-protected, nothing comes back)
left the unit looping with no way forward, although the SAs, the
listener sockets and the firewall rule already covered UDP.

The client flow now carries a transport. UDP binds the same protected
client port and connect()s the datagram socket to the P-CSCF's protected
server port, so the kernel delivers that peer's datagrams to it ahead of
the unconnected listener on the same port. One datagram is one message
(RFC 3261 18.3: a Content-Length that fits truncates, one that does not
fit discards the datagram, none means the rest of the datagram); a
receive error is logged and marks the flow dead; a message over the
single-packet ESP budget at the ims PDN's MTU is logged once per flow,
since the kernel fragments it and a P-CSCF may drop the fragments.
Every protected request's Via follows the transport; the challenge
stays UDP.

SIP_TRANSPORT selects the policy: auto (default) registers over TCP and,
after two unanswered connects, registers again over UDP (new challenge,
new SA pair) - but only on a phone whose state file does not record a
successful TCP registration, so an outage on a TCP carrier never becomes
a second initial REGISTER; a phone whose last registration was UDP goes
straight to UDP. tcp and udp force one. A connect refused locally
(EADDRNOTAVAIL: the 4-tuple still in TIME_WAIT from the previous flow)
keeps retrying and is not counted as silence; SO_ERROR results are
logged by name. The registration's transport is persisted so a warm
resume reconnects the same way, and the resume gives up after two
silences.

Two things the change exposed and fixes: an engine-fatal event exited 0
("exiting for systemd restart" with Restart=on-failure never firing),
and a fresh registration refused with the P-CSCF's fresh-SA throttle
(Security-Server spi-s=0) must not be retried by a 120-s restart loop,
since every attempt re-arms the ~20-min window - it is now waited out
in-process, growing on repeats, with D-Bus commands still served. A
resume the network refuses falls through to a fresh registration. A
retransmitted 200 OK to our INVITE is ACKed again and no longer starts
a second media leg.

Verified on KPN: resume over TCP after a binary swap, MT and MO calls
with media both ways, the throttle deferral and its self-recovery, the
UDP client path up to KPN dropping the datagram. The UDP success path
is a field test; SIP retransmission timers over UDP are not in this
change.
2026-09-17 16:22:22 +02:00
7978b94502 messages: sign our requests with the public identity, not the IMSI IMPU
Every request we originated — INVITE, CANCEL, both ACKs, in-dialog BYE and
friends — put the IMSI-derived temporary IMPU in From. 3GPP allows that
identity in REGISTER only; the same lesson was learned for the reg-event
SUBSCRIBE (480) and never carried to calls. Most P-CSCFs overwrite From
and hid it; a Telia node did not, and a reporter's IMSI appeared on the
callee's screen (field report 2026-09-01). A strict P-CSCF may reject the
INVITE outright.

CallerId(): the registered sip: public identity (P-Associated-URI), else
the tel: one, and the temporary IMPU only before either is learned. One
helper feeds all five builders, so a dialog's From never drifts. As UAS the
dialog's local URI is the INVITE's To (RFC 3261 12.2.1.1), stored on the
Dialog, so an incoming call's BYE is signed the way the network addressed
us. The identity is persisted in the state file and restored on warm
resume, so a call placed before the refresh 200 re-learns it cannot fall
back. DUMP_SIP now also writes the last outgoing INVITE
(imsd-invite-out.raw): the one request a field log could never show.

The byte-pinned INVITE fixture moves to the tel: identity its test context
knows; new scenarios cover the sip: identity, the tel: fallback, the
pre-learning case and the UAS BYE.

Bench-verified on KPN 2026-09-08: outgoing INVITE From is the registered
sip: identity, call accepted and carried; caller ID at the far end
unchanged.
2026-09-08 20:37:47 +02:00
f2a404855f Emergency calling, stage 1: urn:service:sos with a digits fallback
Classify 112/911 (plus EMERGENCY_NUMBERS) at Dial. A classified call
INVITEs urn:service:sos over the existing registration and, on any
non-2xx final not caused by the user hanging up (including the
setup-timeout CANCEL paths), retries once as a plain INVITE of the
dialled digits — the pre-0.3.0 behavior, so classification can never
place a call worse than the status quo. The reverse edge: a 380
Alternative Service whose body carries the emergency indication
upgrades an unclassified call to the sos URN; a bare 380 stays an
error, since promoting an arbitrary redirect would put a
non-emergency call through to a PSAP. An emergency dial preempts an
in-progress call, and an answer racing a deadline-initiated CANCEL is
taken instead of BYE'd (user-initiated CANCEL races still BYE).

No emergency registration, no emergency PDN, no CS fallback, no
SIM-less calling, no AML — and no carrier has confirmed the sos path
end-to-end. The README warning states exactly that.

Assisted-by: Claude:claude-fable-5
2026-08-03 09:27:03 +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