sip: log a refused REGISTER or SUBSCRIBE with its status line and reason headers
A refused final was logged as its bare status code ("re-REGISTER failed
503", "reg-event SUBSCRIBE -> 500") — the unprotected REGISTER's as
"expected 401", without even the code — so a field journal could not say
which node refused or what it said. O2 UK answered a phone's 30-minute
refresh with 503 twenty-one times over seventy minutes and then refused
a fresh registration's reg-event SUBSCRIBE with 500 (alyx, 2026-09-24);
the journal holds nothing but the numbers.
Log the status line with the headers that carry the reason —
Retry-After, Warning, Reason, Server — for a REGISTER's refused final
(initial, protected, refresh) and for a refused SUBSCRIBE the run loop
reads, and dump the refused message under DUMP_SIP as its own fail dump
next to the 200s.
To tell a REGISTER's final from what else arrives, the final is now
matched by Call-ID and CSeq, and a request that arrives on the client
flow while one is awaited is dispatched: a refresh no longer reads such
a request as a failed REGISTER. An INVITE among those requests is a call
like any other, announced on the bus: a Dial whose reconnect it arrived
under meets the call-in-progress rule again before it places its own,
and when a resume, a refresh or a reconnect fails the call that arrived
under it ends with the flow it came in on (486 while the flow still
carries one, and the bus told). The unprotected REGISTER's reply is
matched the same way on the init port.
Tested on the dev phone (KPN) in builds of this series; not every path
has run there.
This commit is contained in:
parent
e7543c59f3
commit
46bf363915
4 changed files with 212 additions and 45 deletions
|
|
@ -196,6 +196,51 @@ int main() {
|
|||
Check(!m4.has_value(), "absurd Content-Length: discarded, no overflow");
|
||||
}
|
||||
|
||||
// ---- RetryAfter / RefusalSummary (what a refused final is logged as)
|
||||
{
|
||||
std::string r503 =
|
||||
"SIP/2.0 503 Service Unavailable\r\n"
|
||||
"Via: SIP/2.0/TCP [2001:db8::db43]:45062;branch=z9hG4bKx\r\n"
|
||||
"Retry-After: 120\r\n"
|
||||
"Warning: 399 pcscf1.example \"S-CSCF unreachable\"\r\n"
|
||||
"Server: Mavenir-P-CSCF/4.2\r\n"
|
||||
"Content-Length: 0\r\n\r\n";
|
||||
Check(RetryAfter(r503) == 120, "Retry-After parsed");
|
||||
Check(RefusalSummary(r503) == "503 Service Unavailable; Retry-After: 120; Warning: 399 pcscf1.example \"S-CSCF unreachable\"; Server: Mavenir-P-CSCF/4.2", "summary: status line + reason headers in order");
|
||||
std::string r500 =
|
||||
"SIP/2.0 500 Server Internal Error\r\n"
|
||||
"Via: SIP/2.0/TCP [2001:db8::db43]:45062;branch=z9hG4bKx\r\n"
|
||||
"Content-Length: 0\r\n\r\n";
|
||||
Check(!RetryAfter(r500).has_value(), "no Retry-After -> nullopt");
|
||||
Check(RefusalSummary(r500) == "500 Server Internal Error", "summary without reason headers is the status line");
|
||||
std::string two =
|
||||
"SIP/2.0 403 Forbidden\r\n"
|
||||
"Reason: Q.850;cause=21;text=\"Call rejected\"\r\n"
|
||||
"Reason: SIP;cause=403\r\n"
|
||||
"Retry-After: soon\r\n"
|
||||
"Content-Length: 0\r\n\r\n";
|
||||
Check(!RetryAfter(two).has_value(), "non-numeric Retry-After -> nullopt");
|
||||
Check(RefusalSummary(two) == "403 Forbidden; Retry-After: soon; Reason: Q.850;cause=21;text=\"Call rejected\"; Reason: SIP;cause=403", "every instance of a reason header is kept");
|
||||
Check(RefusalSummary("NOTIFY sip:x SIP/2.0\r\nEvent: reg\r\n\r\n") == "NOTIFY sip:x SIP/2.0", "a request summarises to its request line");
|
||||
Check(RefusalSummary("") == "", "empty message -> empty summary");
|
||||
}
|
||||
|
||||
// ---- ResponseOf (which datagram a REGISTER wait may judge)
|
||||
{
|
||||
Check(ResponseOf(Resp200, "12345@host", "2 REGISTER"), "the transaction's own final");
|
||||
Check(!ResponseOf(Resp200, "12345@host", "1 REGISTER"), "another CSeq is not it");
|
||||
std::string stale =
|
||||
"SIP/2.0 408 Request Timeout\r\n"
|
||||
"Via: SIP/2.0/UDP [2001:db8::db43]:5060;branch=z9hG4bKa;rport=5060\r\n"
|
||||
"Call-ID: earlier@[2001:db8::db43]\r\n"
|
||||
"CSeq: 1 REGISTER\r\n"
|
||||
"Content-Length: 0\r\n\r\n";
|
||||
Check(!ResponseOf(stale, "12345@host", "1 REGISTER"), "an earlier attempt's late final is not it");
|
||||
Check(ResponseOf(stale, "earlier@[2001:db8::db43]", "1 REGISTER"), "matched by Call-ID and CSeq");
|
||||
Check(!ResponseOf("NOTIFY sip:x SIP/2.0\r\nCall-ID: 12345@host\r\nCSeq: 2 REGISTER\r\n\r\n", "12345@host", "2 REGISTER"), "a request is never a response");
|
||||
Check(!ResponseOf("", "12345@host", "2 REGISTER"), "nothing is not it");
|
||||
}
|
||||
|
||||
if (Failures == 0) std::println("Sip: all tests passed");
|
||||
return Failures;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue