Skip to content

Commit 28b11dc

Browse files
heckoclaude
authored andcommitted
Add SIPREC recording support (RFC 7865/7866) to SBC
Implement a SIPREC Session Recording Client (SRC) as an SBC call_control module (cc_siprec) that forks RTP to a Session Recording Server (SRS), plus a minimal built-in SRS (siprec_srs) for testing. SIP signaling (RFC 7866): - INVITE with Require: siprec and +sip.src Contact feature tag - multipart/mixed body: SDP offer + metadata XML - Content-Disposition: recording-session on metadata parts - Separate sendonly m-lines per CS direction with a=label - Symmetric RTP with allocated local port pairs (RFC 7866 8.1.8) - RTCP port allocation on port+1 (RFC 7866 8.1.1) - Configurable RTP profile: RTP/AVP, RTP/SAVP, RTP/AVPF, RTP/SAVPF - CANCEL for pending INVITE on call teardown - re-INVITE for hold/resume with updated SDP + metadata - BYE with final metadata and stop-time Metadata XML (RFC 7865): - Namespace urn:ietf:params:xml:ns:recording:1 - session, participant, stream elements with base64 IDs - sessionrecordingassoc and participantsessionassoc elements - participantstreamassoc with send/recv text-content stream refs - ISO 8601 timestamps, partial updates on hold Recording indication (RFC 7866 6.1.2): - a=record:on injected into B-leg INVITE SDP CS SDP codec extraction: - Parses initial INVITE SDP to auto-detect codec for RS offer - Falls back to configured codec (PCMA/PCMU) when unavailable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 00823b9 commit 28b11dc

29 files changed

+3026
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*.so
99
*.swp
1010
*~
11+
.claude
1112
/CMakeCache.txt
1213
CMakeFiles/
1314
CTestTestfile.cmake

README.md

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,29 @@ The following applications are shipped with SEMS :
5555

5656
### Back-to-back User Agent
5757

58-
* **sbc** - flexible SBC application, supports
59-
- identity change
60-
- header manipulation (filter etc)
61-
- (multihomed) RTP relay, NAT handling, transcoding
62-
- SIP authentication
63-
- Session timer, call timer, prepaid
64-
- etc
58+
* **sbc** - flexible SBC (Session Border Controller) application, supports
59+
- header manipulation and filtering
60+
- (multihomed) RTP relay, NAT handling
61+
- audio transcoding with codec mapping
62+
- SIP authentication (UAC, A/B leg)
63+
- session timer (SST) for both legs
64+
- contact hiding and identity change
65+
- SIP reply code/reason translation
66+
- DTMF filtering and detection
67+
- RTP bandwidth limiting
68+
- registration caching and retargeting
69+
- call_control modules:
70+
- **cc_bl_redis** - Redis-based call blacklisting
71+
- **cc_call_timer** - call duration limit enforcement
72+
- **cc_ctl** - profile control via SIP headers
73+
- **cc_dsm** - roles for DSM state machine scripting
74+
- **cc_parallel_calls** - concurrent call limiting per user
75+
- **cc_prepaid** - local credit-based call control
76+
- **cc_prepaid_xmlrpc** - prepaid via external XMLRPC server
77+
- **cc_registrar** - REGISTER caching and routing
78+
- **cc_rest** - call control via REST API
79+
- **cc_siprec** - SIPREC (RFC 7865/7866) call recording
80+
- **cc_syslog_cdr** - call detail records via syslog
6581

6682
### Announcements (Prompts, Ringbacktones, Pre-call-prompts):
6783

@@ -153,6 +169,15 @@ The following applications are shipped with SEMS :
153169

154170
* **mwi** - Message Waiting Indication (MWI) support
155171

172+
### Recording
173+
174+
* **siprec_srs** - minimal SIPREC Session Recording Server (RFC 7865/7866).
175+
Receives recording INVITEs from SIPREC clients (such as cc_siprec),
176+
records both call legs to separate WAV files, and saves the SIPREC
177+
metadata XML. Can run on the same SEMS instance as the SBC for
178+
self-contained call recording. See also **cc_siprec** under the SBC
179+
call_control modules.
180+
156181
### Media Processing
157182

158183
* **mp3** - MP3 codec support and media processing

apps/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ if(RTMP_FOUND AND SPEEX_FOUND)
3535
add_subdirectory(rtmp)
3636
endif(RTMP_FOUND AND SPEEX_FOUND)
3737
add_subdirectory(sbc)
38+
add_subdirectory(siprec_srs)
3839
add_subdirectory(voicebox)
3940
add_subdirectory(voicemail)
4041
add_subdirectory(webconference)

apps/sbc/ExtendedCCInterface.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
class SBCCallLeg;
88
struct SBCCallProfile;
99
class SimpleRelayDialog;
10+
class AmRtpPacket;
1011

1112
struct InitialInviteHandlerParams
1213
{
@@ -76,6 +77,14 @@ class ExtendedCCInterface
7677
virtual void resumeAccepted(SBCCallLeg *call) { }
7778
virtual void resumeRejected(SBCCallLeg *call) { }
7879

80+
// RTP relay hooks (called from RTP receiver thread)
81+
82+
/** called after an RTP packet has been successfully relayed to the
83+
* B2B peer leg. Can be used for RTP stream forking (e.g. SIPREC).
84+
* Note: called from RTP receiver thread - must be non-blocking. */
85+
virtual void onAfterRTPRelay(SBCCallLeg *call, AmRtpPacket* p,
86+
sockaddr_storage* remote_addr) { }
87+
7988
/** Possibility to influence messages relayed to the B2B peer leg.
8089
return value:
8190
- lower than 0 means error (returned upstream, the one

apps/sbc/SBC.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ void SBCFactory::onOoDRequest(const AmSipRequest& req)
412412
}
413413

414414
if(req.max_forwards == 0) {
415+
WARN("rejecting %s %s from %s:%d with 483 Too Many Hops (Max-Forwards=0)\n",
416+
req.method.c_str(), req.r_uri.c_str(),
417+
req.remote_ip.c_str(), req.remote_port);
415418
AmSipDialog::reply_error(req, 483, SIP_REPLY_TOO_MANY_HOPS);
416419
return;
417420
}

apps/sbc/SBCCallLeg.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,8 @@ void SBCCallLeg::onInvite(const AmSipRequest& req)
845845
ctx.app_param = getHeader(req.hdrs, PARAM_HDR, true);
846846

847847
if (req.max_forwards <= 0) {
848+
WARN("rejecting INVITE %s from %s:%d with 483 Too Many Hops (Max-Forwards=%d)\n",
849+
req.r_uri.c_str(), req.remote_ip.c_str(), req.remote_port, req.max_forwards);
848850
throw AmSession::Exception(483, SIP_REPLY_TOO_MANY_HOPS);
849851
}
850852

@@ -1395,6 +1397,11 @@ void SBCCallLeg::onAfterRTPRelay(AmRtpPacket* p, sockaddr_storage* remote_addr)
13951397
it != rtp_pegs.end(); ++it) {
13961398
(*it)->inc(p->getBufferSize());
13971399
}
1400+
1401+
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin();
1402+
i != cc_ext.end(); ++i) {
1403+
(*i)->onAfterRTPRelay(this, p, remote_addr);
1404+
}
13981405
}
13991406

14001407
void SBCCallLeg::logCallStart(const AmSipReply& reply)

apps/sbc/call_control/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_subdirectory(prepaid)
99
# ADD_SUBDIRECTORY (prepaid_xmlrpc)
1010
add_subdirectory(registrar)
1111
# ADD_SUBDIRECTORY (rest)
12+
add_subdirectory(siprec)
1213
add_subdirectory(syslog_cdr)
1314
# This one is actually a template and isn't intended for any real use.
1415
# ADD_SUBDIRECTORY (template)

apps/sbc/call_control/registrar/Registrar.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class CCRegistrarFactory : public AmDynInvokeFactory
5757
if (CCRegistrar::instance()->onLoad())
5858
return -1;
5959

60-
DBG("template call control loaded.\n");
60+
DBG("registrar call control loaded.\n");
6161

6262
return 0;
6363
}
@@ -228,10 +228,14 @@ void CCRegistrar::start(const string& cc_name, const string& ltag,
228228
if (NULL == req)
229229
REFUSE_WITH_404;
230230

231-
if ((req->method == "INVITE") && (retarget(req->r_uri, values, call_profile))){
231+
if ((req->method == "INVITE") && (retarget(req->r_uri, values, call_profile))){
232+
INFO("Registrar: retargeted INVITE '%s' -> '%s'\n",
233+
req->r_uri.c_str(), call_profile->ruri.c_str());
232234
return;
233235
}
234236

237+
INFO("Registrar: no registration found for '%s', rejecting with 404\n",
238+
req->r_uri.c_str());
235239
REFUSE_WITH_404;
236240
}
237241

@@ -256,16 +260,22 @@ void CCRegistrar::route(const string& cc_name,
256260

257261

258262
if (ood_req->method == "REGISTER"){
263+
INFO("Registrar: REGISTER from '%s', Contact: '%s', source: %s:%d\n",
264+
ood_req->from.c_str(), ood_req->contact.c_str(),
265+
ood_req->remote_ip.c_str(), ood_req->remote_port);
266+
259267
RegisterCacheCtx reg_cache_ctx;
260268

261269
// reply 200 if possible, else continue
262270
bool replied = RegisterCache::instance()->saveSingleContact(reg_cache_ctx, *ood_req);
263271

264272
if(replied) {
265-
DBG("replied!");
273+
INFO("Registrar: registration saved for '%s'\n", ood_req->from.c_str());
266274
res.push(AmArg());
267275
AmArg& res_cmd = res.back();
268276
res_cmd[SBC_CC_ACTION] = SBC_CC_DROP_ACTION;
277+
} else {
278+
WARN("Registrar: failed to save registration for '%s'\n", ood_req->from.c_str());
269279
}
270280
} else {
271281
if (retarget(ood_req->r_uri, values, call_profile)){

0 commit comments

Comments
 (0)