feat: implement RFC 4028 session timer compliance - #51
Conversation
Implement SIP session timers per RFC 4028 for both internal B2BUA calls and trunk calls, with configurable Session-Expires and Min-SE values exposed as CLI flags (-session-timer, -min-se). Session timer negotiation: - Add Supported: timer, Min-SE, and Session-Expires headers to outbound INVITEs on both internal and trunk legs - Negotiate Session-Expires and refresher (uac/uas) in 200 OK responses to Alice when the inbound INVITE supports timer - Reject inbound INVITEs with Session-Expires below Min-SE using 422 Session Interval Too Small (new status code constant) - Handle 422 responses from Bob/trunk peers by raising Min-SE and retrying the INVITE with an updated Session-Expires (trunk path retries in place; internal path forwards the 422 to Alice) Timer lifecycle (new internal/b2bua/sessiontimer.go): - SessionTimer struct tracks interval, Min-SE, refresher role, and expiry per call leg (Alice and Bob independently) - Refresher side sends a re-INVITE at half the interval; on refresh failure or rejection (408/481) the call is torn down - Non-refresher side tears down the call if no refresh arrives before a safety margin of min(32s, interval/3) prior to expiry - Helpers for parsing and formatting Session-Expires/Min-SE headers and detecting timer support in the Supported header In-dialog re-INVITE handling: - Detect re-INVITEs by To tag and Call-ID in HandleInvite and route them to a new dialog handler - Forward re-INVITEs (with SDP if present) to the opposite leg and relay provisional, 200, and error responses back - Reset the session timer for both legs on re-INVITE and its 200 OK Teardown: - Replace the ad-hoc trunkSessionTimer goroutine with unified sendBye/sendByeBothLegs helpers that send BYE with a Reason header, stop timers and media, remove the call from the store, and release trunk channels Add unit tests for Session-Expires/Min-SE parsing, timer support detection, and header formatting helpers.
There was a problem hiding this comment.
Pull request overview
This PR adds RFC 4028 session timer support to the B2BUA by introducing per-leg session timer state, negotiating Session-Expires/Min-SE with peers (including 422 handling), and adding in-dialog re-INVITE forwarding and timer-driven refresh/teardown behavior.
Changes:
- Added
SessionTimerinfrastructure (parsing/formatting helpers + refresher/non-refresher timer loops + refresh re-INVITE sending). - Added in-dialog re-INVITE detection/forwarding and unified BYE teardown helpers.
- Added configuration flags for default session timer interval and minimum acceptable session interval.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| proto/sip_constants.go | Adds RFC 4028 status code constant (422). |
| internal/b2bua/sessiontimer.go | New session timer implementation (parse/format helpers, timer loops, refresh sending). |
| internal/b2bua/sessiontimer_test.go | Unit tests for session timer parsing/formatting helpers. |
| internal/b2bua/handler.go | Integrates session timers into INVITE flows, adds re-INVITE forwarding, 422 handling, and unified BYE teardown. |
| internal/b2bua/call.go | Stores per-leg session timer state on the Call object. |
| cmd/trecsd/main.go | Adds CLI flags for session timer defaults (--session-timer, --min-se). |
Suppressed comments (1)
internal/b2bua/handler.go:1678
- In b2buaResponseLoop, the 422 handler logs that it is "retrying" but then explicitly forwards the 422 to Alice because there is no retry mechanism. This contradicts the intended RFC 4028 behavior and the surrounding log message, and it also mutates
h.minSEglobally based on one peer response.
// Handle 422 Session Interval Too Small (RFC 4028 §5).
if sc == proto.SIPStatusSessionIntervalTooSmall {
h.cancelPRACK(cc.callID)
peerMinSE := ParseMinSE(resp.Headers.GetFirst("Min-SE"))
if peerMinSE > h.minSE {
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The B2BUA session timer logic had several bugs around RFC 4028 negotiation. Min-SE was mutated on the handler-wide default instead of per Call-ID, the refresher role for non-timer peers was miscalculated, and 422 retries / re-INVITEs / session refreshes used unregistered Via branches, so responses could not be routed back to their UAC transactions. This change scopes Min-SE negotiation to each call, negotiates the Bob leg timer from the peer 200 OK correctly, propagates session-timer headers across legs, and creates UAC transactions before building requests so their branches are registered in the UACManager. CANCEL now targets the live retry transaction, and BYE uses the dialog's incremented local CSeq. Also fixes flag validation (Min-SE >= 90s per RFC 4028 §5), a UAS refresher check that would never fire, and adds tests covering re-INVITE branch matching, session header forwarding, and session timer negotiation.
Add a deadline to waitForRefreshResponse so the server tears down the call if the peer does not accept the re-INVITE within the session interval. The trunk test ghost peer now stops answering re-INVITEs after the initial INVITE so the teardown path is exercised. Update the wait time accordingly.
Merging this branch will increase overall coverage
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. Changed unit test files
|
Summary
Implements full RFC 4028 session timer compliance, replacing the previous fire-and-forget timer with a proper session timer system that negotiates parameters with peers, sends re-INVITEs to refresh sessions, and handles 422 responses.
Changes
Session Timer Infrastructure (
internal/b2bua/sessiontimer.go)SessionTimerstruct tracking interval, refresher role, and expiry per dialog legParseSessionExpires,ParseMinSE,HasTimerSupportStartSessionTimer/StopSessionTimer/ResetSessionTimerfor timer lifecycleIn-Dialog re-INVITE Handling (
internal/b2bua/handler.go)HandleInvitedetects re-INVITEs by checking To tag + Call-ID in storehandleReInviteforwards re-INVITEs across legs with response relay viareInviteResponseLoopSession Timer Header Negotiation
Supported: timer,Min-SE, andSession-ExpiresSession-Expires,Supported: timer, andRequire: timerSession-ExpiresbelowMin-SEare rejected with 422Min-SEand higherSession-ExpiresUnified BYE Handling
sendByeandsendByeBothLegsreplace ad-hoc BYE logic with shared helpersConfiguration
--session-timerflag (default 1800s) — global default Session-Expires--min-seflag (default 90s) — minimum acceptable Session-ExpiresSessionExpiresSeccontinues to work as overrideReferences
Testing