Forward RFC 3326 Reason header on CANCEL/BYE between B2B legs#369
Forward RFC 3326 Reason header on CANCEL/BYE between B2B legs#369
Conversation
Re-implementation of PR #165 with the concerns raised in review addressed: - Filter to RFC 3326 "Reason:" only. Other CANCEL headers (Via, CSeq, Route, Authorization, Privacy, P-Asserted-Identity, etc.) are no longer forwarded verbatim onto the peer leg's BYE — they are either nonsensical there or leak trust-boundary metadata. - Keep the implementation inside apps/sbc so it doesn't touch AmB2BSession / AmB2ABSession virtual signatures. External modules that subclass those base classes continue to override correctly (the original patch silently broke their overrides because `terminateLeg()` / `terminateOtherLeg()` gained a defaulted parameter and the old zero-arg overrides stopped overriding). - Propagate the filtered header between CallLegs via an SBC-local B2BEvent subclass that reuses the existing B2BTerminateLeg event id, so non-CallLeg receivers still terminate correctly. - Not configurable: enabled unconditionally for SBC CallLeg, matching the PR's original behaviour. https://claude.ai/code/session_01PWHzum8cu3oJhct3A4YEdd
There was a problem hiding this comment.
Pull request overview
Implements selective propagation of RFC 3326 Reason: from inbound CANCEL to outbound CANCEL/BYE across SBC B2B legs, while avoiding forwarding trust-boundary-sensitive headers.
Changes:
- Added a
cancel_hdrsper-leg temporary buffer to carry filteredReason:headers through a termination chain. - Introduced
filterReasonHdr()and aCallLegTerminateEventto transport the filtered headers between legs using the existingB2BTerminateLegevent id. - Updated termination and CANCEL handling to attach the filtered
Reason:header(s) to generated CANCEL/BYE.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| apps/sbc/CallLeg.h | Adds cancel_hdrs member to store filtered Reason: headers during termination propagation. |
| apps/sbc/CallLeg.cpp | Implements Reason: extraction, propagates it via a custom terminate event, and uses it when generating CANCEL/BYE. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| AmSessionContainer::instance()->postEvent(i->id, new B2BEvent(B2BTerminateLeg)); | ||
| B2BEvent *ev = cancel_hdrs.empty() | ||
| ? new B2BEvent(B2BTerminateLeg) | ||
| : static_cast<B2BEvent*>(new CallLegTerminateEvent(cancel_hdrs)); |
There was a problem hiding this comment.
The static_cast<B2BEvent*> around new CallLegTerminateEvent(...) is redundant (implicit upcast already applies) and makes the ternary harder to read. Consider dropping the cast and keeping both branches symmetric for clarity.
| : static_cast<B2BEvent*>(new CallLegTerminateEvent(cancel_hdrs)); | |
| : new CallLegTerminateEvent(cancel_hdrs); |
| size_t colon = hdrs.find(':', pos); | ||
| if (colon != string::npos && colon < content_end | ||
| && (colon - pos) == 6 | ||
| && strncasecmp(hdrs.c_str() + pos, "Reason", 6) == 0) { | ||
| out.append(hdrs, pos, content_end - pos); | ||
| out.append("\r\n"); |
There was a problem hiding this comment.
filterReasonHdr() only matches Reason when the ':' immediately follows the 6-character name (i.e., it rejects Reason : / Reason\t:). Elsewhere in the codebase header matching explicitly tolerates optional whitespace before ':' (see findHeader() in core/AmSipMsg.cpp), so this stricter parsing may cause valid-in-the-wild Reason headers to be dropped and not propagated. Consider loosening the match to allow OWS between the header name and ':' (or reuse the existing header-finding logic).
| size_t colon = hdrs.find(':', pos); | |
| if (colon != string::npos && colon < content_end | |
| && (colon - pos) == 6 | |
| && strncasecmp(hdrs.c_str() + pos, "Reason", 6) == 0) { | |
| out.append(hdrs, pos, content_end - pos); | |
| out.append("\r\n"); | |
| if ((content_end - pos) >= 6 | |
| && strncasecmp(hdrs.c_str() + pos, "Reason", 6) == 0) { | |
| size_t colon = pos + 6; | |
| while (colon < content_end && | |
| (hdrs[colon] == ' ' || hdrs[colon] == '\t')) { | |
| ++colon; | |
| } | |
| if (colon < content_end && hdrs[colon] == ':') { | |
| out.append(hdrs, pos, content_end - pos); | |
| out.append("\r\n"); | |
| } |
Summary
This change implements selective forwarding of RFC 3326 "Reason:" headers from inbound CANCEL requests to outbound CANCEL/BYE messages on peer B2B legs in the SBC. This allows call termination reasons to propagate across the SBC while maintaining security boundaries by filtering out trust-sensitive headers.
Key Changes
Added
filterReasonHdr()helper function: Extracts only RFC 3326 "Reason:" headers from raw SIP header blocks, intentionally excluding other CANCEL headers (Via, CSeq, Route, Authorization, Privacy, P-A-I) that are either meaningless on different legs or would leak trust-boundary metadata.Introduced
CallLegTerminateEventstruct: A custom B2B event that extendsB2BEventto carry filtered CANCEL headers between CallLegs. Reuses the existingB2BTerminateLegevent_id so non-CallLeg receivers still terminate correctly without the extra header.Added
cancel_hdrsmember variable: Stores filtered extension headers (only RFC 3326 "Reason:") captured from inbound CANCEL requests. Non-empty only for the duration of a single termination call chain.Modified
terminateOtherLeg(): Now relaysCallLegTerminateEventwith filtered headers to peer legs instead of plain termination events when cancel headers are present.Modified
terminateNotConnectedLegs(): Propagates filtered CANCEL headers to all non-connected legs viaCallLegTerminateEvent.Enhanced
onB2BEvent()handler: Added case forB2BTerminateLegto detectCallLegTerminateEventand apply the propagated headers when terminating the local leg.Updated
onCancel()handler: Captures and filters the "Reason:" header from inbound CANCEL requests intocancel_hdrsbefore processing call termination.Modified
terminateLeg(): Whencancel_hdrsis populated, sends BYE with the filtered headers usingSIP_FLAGS_VERBATIMflag instead of the default termination path.Implementation Details
cancel_hdrsvariable is temporary and scoped to the termination call chain, being saved and restored to prevent unintended side effects.https://claude.ai/code/session_01PWHzum8cu3oJhct3A4YEdd