{"id":50826955,"url":"https://github.com/voicetel/mod_siprec","last_synced_at":"2026-06-13T20:01:10.603Z","repository":{"id":356042866,"uuid":"1230473411","full_name":"voicetel/mod_siprec","owner":"voicetel","description":"mod_siprec is an RFC 7866 SIPREC Session Recording Client for FreeSWITCH.","archived":false,"fork":false,"pushed_at":"2026-06-11T22:13:43.000Z","size":207,"stargazers_count":3,"open_issues_count":2,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T00:07:34.847Z","etag":null,"topics":["audio-recording","freeswitch","freeswitch-mod","rfc7866","sip","siprec","telephony","voip"],"latest_commit_sha":null,"homepage":"https://github.com/voicetel/mod_siprec","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/voicetel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T03:07:22.000Z","updated_at":"2026-06-11T22:13:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/voicetel/mod_siprec","commit_stats":null,"previous_names":["voicetel/mod_siprec"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/voicetel/mod_siprec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voicetel%2Fmod_siprec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voicetel%2Fmod_siprec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voicetel%2Fmod_siprec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voicetel%2Fmod_siprec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/voicetel","download_url":"https://codeload.github.com/voicetel/mod_siprec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voicetel%2Fmod_siprec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34298247,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-recording","freeswitch","freeswitch-mod","rfc7866","sip","siprec","telephony","voip"],"created_at":"2026-06-13T20:00:37.336Z","updated_at":"2026-06-13T20:01:10.577Z","avatar_url":"https://github.com/voicetel.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mod_siprec\n\nA FreeSWITCH module that implements [RFC 7866][rfc7866] SIPREC\n(Session Recording Protocol) as a Session Recording Client (SRC),\nwith [RFC 7865][rfc7865]-compliant metadata.\n\nFor each call you want to record, mod_siprec opens a parallel\nSIP INVITE ([RFC 3261][rfc3261]) to a configured Session\nRecording Server (SRS), attaches a `multipart/mixed`\n([RFC 2046][rfc2046]) body containing both the [SDP][rfc4566]\noffer and an XML metadata document, taps the original call's\naudio via a FreeSWITCH media bug, and forks the captured audio\nas [RFC 3550][rfc3550] RTP to the endpoints negotiated in the\nSRS's 200 OK answer.\n\n[rfc2046]: https://datatracker.ietf.org/doc/html/rfc2046\n[rfc3261]: https://datatracker.ietf.org/doc/html/rfc3261\n[rfc3550]: https://datatracker.ietf.org/doc/html/rfc3550\n[rfc3551]: https://datatracker.ietf.org/doc/html/rfc3551\n[rfc3711]: https://datatracker.ietf.org/doc/html/rfc3711\n[rfc4566]: https://datatracker.ietf.org/doc/html/rfc4566\n[rfc4568]: https://datatracker.ietf.org/doc/html/rfc4568\n[rfc5888]: https://datatracker.ietf.org/doc/html/rfc5888\n[rfc7865]: https://datatracker.ietf.org/doc/html/rfc7865\n[rfc7866]: https://datatracker.ietf.org/doc/html/rfc7866\n\n## Status\n\nAll paths build, lint clean, and the unit-test suite for the\nSDP / metadata builders passes 124/124 assertions. The dispatch /\nmedia / signalling pipeline has been audited and the broken\npieces from the original fork have been replaced. Live\nintegration against `cb-srs` is documented in\n[`tests/README.md`](tests/README.md) as the operator\nverification path; production interop is verified against\n[TransNexus ClearIP][clearip] as well.\n\n[clearip]: https://transnexus.com/clearip/\n\n| Concept | Spec | Status |\n|---|---|---|\n| SRC INVITE with `Require: siprec` | [RFC 7866 §6.1][rfc7866-6.1] | ✅ via `switch_ivr_originate` ovars |\n| `+sip.src` Contact feature tag | [RFC 7866 §5.2.1][rfc7866-5.2.1] | ✅ via `sip_invite_contact_params=~+sip.src` ovar; the leading `~` tells `sofia_overcome_sip_uri_weakness` (sofia_glue.c:854,891) to place the tag AFTER the closing `\u003e`, yielding `Contact: \u003csip:src@host:port\u003e;+sip.src` per the spec |\n| `multipart/mixed` (SDP + metadata) | [RFC 7866 §6.1.2][rfc7866-6.1.2] / [RFC 2046][rfc2046] | ✅ `sip_multipart` channel var |\n| BYE on hangup | [RFC 7866 §6.4][rfc7866-6.4] | ✅ on_destroy state-handler |\n| pause / resume re-INVITE | [RFC 7866 §6.4][rfc7866-6.4] | ✅ `siprec_pause` / `siprec_resume` apps; SDP direction-flip preserves negotiated session. **PCI-safe**: pause sets the recording bug's native `SMBF_PAUSE` (FreeSWITCH stops capturing audio at the io pump — no frames forked, nothing buffered to burst on resume) *before* sending `a=inactive`, so cardholder audio never leaves the box |\n| explicit stop | [RFC 7866 §6.4][rfc7866-6.4] | ✅ `siprec_stop` app — detaches the media fork + BYEs the SRS leg mid-call; no-arg form stops every recording on the leg (PCI-safe default). Not resumable; start a fresh `siprec` to record again |\n| sendonly direction on SRC streams | [RFC 7866 §7.4][rfc7866-7.4] | ✅ sofia auto-gen offer |\n| `a=label:N` per stream | [RFC 7866 §8.5][rfc7866-8.5] | ✅ per-block sequential labels (1st m= → `label:1`, 2nd → `label:2`, …) injected via post-originate re-INVITE on the auto-generated SDP. mod_sofia's auto-gen is single-track today, so the wire effect is `label:1`; the moment a multi-track offer path lands (sofia SDP-override hook OR `siprec_sdp_build`-driven originate) the same call site picks up `label:1` + `label:2` + … without code changes |\n| Single mixed-audio stream (both directions) | [RFC 7866 §7][rfc7866-7] | ✅ one `m=audio` block in the offer, one `\u003cstream\u003e` in metadata. The audio bug observes both directions (`SMBF_READ_STREAM \\| SMBF_WRITE_STREAM`); `switch_core_media_bug_read` returns them already mixed (read+write summed, normalized to 16-bit), so the single forked stream carries **both** parties. Drained on a fixed `SMBF_READ_PING` tick, the same way mod_sofia's `session_record` clocks its mixed capture. RFC 7866 §7 permits \"MAY\" send multiple streams; we send one mixed stream. Separated per-direction (labelled) tracks are a planned follow-up, gated on the multi-track offer path noted in the §8.5 row above — which needs the recording leg placed in proxy mode so mod_sofia emits a custom SDP instead of regenerating it |\n| DTMF tone forking | [RFC 7866 §8.4][rfc7866-8.4] | ✅ passes through the audio bug |\n| communication-failure soft-fail | [RFC 7866 §11.1.1][rfc7866-11.1.1] | ✅ original call unaffected on dispatch failure |\n| SRS failover (multiple endpoints, ordered) | [RFC 7866 §11.1.1][rfc7866-11.1.1] | ✅ multiple `\u003crecording-server\u003e` entries, walked in config order |\n| SRTP for the recording RTP fork | [RFC 7866 §11.2][rfc7866-11.2] / [RFC 3711][rfc3711] / [RFC 4568][rfc4568] | ❌ not supported. SDES keymat must travel in the initial offer (RFC 4568 §5.1) and our offer is sofia auto-gen which doesn't carry `a=crypto`. The clean path needs the same offer-time SDP-override hook the multi-track work needs. SRSes that require SRTP will reject our `RTP/AVP` offer with `488 Not Acceptable Here`; failover or pin a strict-SRTP-not-required SRS. |\n| SIPS transport for SRC→SRS | [RFC 7866 §11.3][rfc7866-11.3] | ✅ `transport=tls` config |\n| SDP body shape (`v=`, `o=`, `s=`, `c=`, `t=`, `m=`, `a=rtpmap`, `a=ptime`, `a=label`, `a=sendonly`) | [RFC 4566][rfc4566] / [RFC 7866 §7][rfc7866-7] | ✅ `siprec_sdp_build` (offered to operators that build their own; sofia auto-gen used otherwise) |\n| Pause/resume SDP direction-flip with `o=` version bump | [RFC 4566 §5.2][rfc4566] | ✅ `siprec_sdp_flip_direction` |\n| RTP packet framing (V=2, M-bit at talkspurt start, big-endian seq/ts/SSRC) | [RFC 3550 §5.1][rfc3550] / [RFC 3551 §4.1][rfc3551] | ✅ `siprec_media.c` |\n| Random SSRC | [RFC 3550 §8.1][rfc3550] | ✅ /dev/urandom seed |\n| G.711 µ-law / A-law encoders | [G.711][g711] | ✅ inline encoders, INT16_MIN-safe |\n| `\u003crecording\u003e` schema (top-level element + sequence) | [RFC 7865 §5 / Appendix A][rfc7865] | ✅ |\n| `\u003cdatamode\u003e` (`complete` + `partial`) | [RFC 7865 §5.1][rfc7865] | ✅ |\n| `\u003cgroup\u003e` (`group_id`, `\u003cassociate-time\u003e`) | [RFC 7865 Appendix A][rfc7865] | ✅ |\n| `\u003csession\u003e` (`session_id`, `\u003creason\u003e`, `\u003cgroup-ref\u003e`, `\u003cstart-time\u003e`) | [RFC 7865 Appendix A][rfc7865] | ✅ |\n| `\u003cparticipant\u003e` (`participant_id`, `\u003cnameID\u003e`) | [RFC 7865 Appendix A][rfc7865] | ✅ schema-strict (no inline send/recv, no session_id attr) |\n| `\u003cstream\u003e` (`stream_id`, `session_id`, `\u003clabel\u003e`) | [RFC 7865 Appendix A][rfc7865] | ✅ |\n| `\u003cparticipantsessionassoc\u003e` / `\u003cparticipantstreamassoc\u003e` | [RFC 7865 Appendix A][rfc7865] | ✅ |\n| XML escaping for caller-supplied content | [RFC 7865 §5][rfc7865] | ✅ \u0026amp; \u0026lt; \u0026gt; \u0026quot; \u0026apos; |\n\n[rfc7866-5.2.1]: https://datatracker.ietf.org/doc/html/rfc7866#section-5.2.1\n[rfc7866-6.1]: https://datatracker.ietf.org/doc/html/rfc7866#section-6.1\n[rfc7866-6.1.2]: https://datatracker.ietf.org/doc/html/rfc7866#section-6.1.2\n[rfc7866-6.4]: https://datatracker.ietf.org/doc/html/rfc7866#section-6.4\n[rfc7866-7]: https://datatracker.ietf.org/doc/html/rfc7866#section-7\n[rfc7866-7.4]: https://datatracker.ietf.org/doc/html/rfc7866#section-7.4\n[rfc7866-8.4]: https://datatracker.ietf.org/doc/html/rfc7866#section-8.4\n[rfc7866-8.5]: https://datatracker.ietf.org/doc/html/rfc7866#section-8.5\n[rfc7866-11.1.1]: https://datatracker.ietf.org/doc/html/rfc7866#section-11.1.1\n[rfc7866-11.2]: https://datatracker.ietf.org/doc/html/rfc7866#section-11.2\n[rfc7866-11.3]: https://datatracker.ietf.org/doc/html/rfc7866#section-11.3\n[g711]: https://www.itu.int/rec/T-REC-G.711\n\n## Build\n\nmod_siprec uses FreeSWITCH's in-tree autotools build. Stage the\nsources alongside your FS source tree, register the module in\n`build/modules.conf.in`, and bootstrap:\n\n```sh\nln -sf $PWD src/mod/applications/mod_siprec\necho 'applications/mod_siprec' \u003e\u003e build/modules.conf.in\n./bootstrap.sh \u0026\u0026 ./configure\nmake mod_siprec-install\n```\n\nDrop the config into FS's `autoload_configs`:\n\n```sh\ncp autoload_conf/siprec.conf.xml /usr/local/freeswitch/conf/autoload_configs/\n```\n\nReload:\n\n```sh\nfs_cli -x 'reload mod_siprec'\n```\n\n### Standalone unit tests + lint (no FS required)\n\nThe SDP and metadata builders are pure C and exercised by a\nstandalone test target:\n\n```sh\nmake -f Makefile.test test    # 77 / 77 assertions\nmake -f Makefile.test lint    # cppcheck --enable=all clean\n```\n\n## Configuration\n\n`autoload_conf/siprec.conf.xml`:\n\n```xml\n\u003cconfiguration name=\"siprec.conf\"\n               description=\"SIPREC (RFC 7866) module config\"\u003e\n  \u003csettings\u003e\n    \u003cparam name=\"src-enabled\" value=\"true\"/\u003e\n    \u003cparam name=\"srs-enabled\" value=\"false\"/\u003e\n  \u003c/settings\u003e\n  \u003crecording-servers\u003e\n    \u003crecording-server name=\"default\"\u003e\n      \u003csettings\u003e\n        \u003cparam name=\"host\" value=\"127.0.0.1\"/\u003e\n        \u003cparam name=\"port\" value=\"5070\"/\u003e\n        \u003cparam name=\"register\" value=\"false\"/\u003e\n        \u003cparam name=\"username\" value=\"\"/\u003e\n        \u003cparam name=\"password\" value=\"\"/\u003e\n      \u003c/settings\u003e\n    \u003c/recording-server\u003e\n  \u003c/recording-servers\u003e\n\u003c/configuration\u003e\n```\n\nMultiple `\u003crecording-server\u003e` entries can coexist; the dialplan\nchooses one by name.\n\n## Dialplan usage\n\n```xml\n\u003cextension name=\"record-with-siprec\"\u003e\n  \u003ccondition\u003e\n    \u003caction application=\"siprec\" data=\"default\"/\u003e\n    \u003c!-- ...the rest of your call flow... --\u003e\n  \u003c/condition\u003e\n\u003c/extension\u003e\n```\n\nPause and resume:\n\n```xml\n\u003caction application=\"siprec_pause\"  data=\"default\"/\u003e\n\u003c!-- RTP fork is gated OFF immediately: no audio leaves the box,\n     and a re-INVITE (a=inactive) tells the SRS to expect none.\n     The SIP dialog stays up so resume is a fast re-INVITE. --\u003e\n\u003caction application=\"siprec_resume\" data=\"default\"/\u003e\n```\n\n`siprec_pause` is **PCI-DSS safe**: it pauses the recording bug\nnatively (`SMBF_PAUSE`) *before* the re-INVITE is even sent, so the\nFreeSWITCH core stops feeding audio to the bug entirely — sensitive\naudio (a card number / CVV read aloud) is never captured, never\nforked, and nothing is buffered to burst out on resume. Wrap the\ncard-capture step in `siprec_pause` / `siprec_resume`.\n\nStop a recording outright:\n\n```xml\n\u003caction application=\"siprec_stop\" data=\"default\"/\u003e\n\u003c!-- detaches the media fork (RTP stops at once) and BYEs the\n     SRS leg. Not resumable — start a fresh `siprec` to record\n     again. --\u003e\n```\n\nNote the argument asymmetry: `siprec_pause` / `siprec_resume` with\nno `data` act on the server named `\"default\"`, but `siprec_stop`\nwith no `data` stops **every** recording on the leg — the PCI-safe\nchoice, so a \"stop now\" can't accidentally leave a second SRS still\nreceiving audio. Pass a `\u003crecording_server\u003e` name to `siprec_stop`\nto stop just that one.\n\nHang-up tears any remaining recording dialog down automatically —\nthe module installs an `on_destroy` state-handler when the\nrecording starts, so an explicit `siprec_stop` is optional.\n\n## Architecture\n\n```\n                                 ┌─────────────────────┐\n                                 │   FreeSWITCH        │\n                                 │                     │\n   carrier ──────────RTP─────────┤  original call leg  │\n                                 │      │              │\n                                 │      ▼              │\n                                 │  ┌────────────┐     │\n                                 │  │ media bug  │     │\n                                 │  │ (siprec)   │     │\n                                 │  └────────────┘     │\n                                 │      │              │\n                                 │      ▼              │\n                                 │  PCMU/PCMA encode   │\n                                 │  RFC 3550 RTP pack  │\n                                 │      │              │\n                                 │      ▼              │\n                                 │  recording leg ─────┼─INVITE─┐\n                                 │  (sofia originate)  │ +RTP   │\n                                 └─────────────────────┘        │\n                                                                ▼\n                                                         ┌─────────────┐\n                                                         │ SRS         │\n                                                         │ (cb-srs,    │\n                                                         │  ClearIP,   │\n                                                         │  Genesys,   │\n                                                         │  NICE, ...) │\n                                                         └─────────────┘\n```\n\nFiles:\n\n- **`mod_siprec.{c,h}`** — module entry, app dispatch, config load.\n- **`recording_session.{c,h}`** — start / stop session lifecycle,\n  state-handler binding.\n- **`siprec_invite.{c,h}`** — SIP INVITE / BYE / re-INVITE via\n  FreeSWITCH's sofia profile.\n- **`siprec_media.{c,h}`** — media bug callback + RFC 3550 RTP\n  fork, inline G.711 µ-law / A-law encoders.\n- **`siprec_sdp.{c,h}`** — [RFC 7866 §7][rfc7866-7] SDP body\n  builder + `siprec_sdp_flip_direction` helper used by the\n  pause/resume re-INVITE path.\n- **`siprec_metadata.{c,h}`** — [RFC 7865 Appendix A][rfc7865]\n  schema-conformant metadata XML builder with full XML-entity\n  escaping.\n- **`siprec_test.c`** — unit-test assertions for the builders\n  and the SDP direction-flip helper.\n- **`autoload_conf/siprec.conf.xml`** — module config schema.\n- **[`ARCHITECTURE.md`](ARCHITECTURE.md)** — phase plan + RFC mapping.\n- **[`tests/README.md`](tests/README.md)** — operator field-test\n  checklist for first deploy.\n\n## Contributing\n\nIssues and PRs welcome. Two things to keep in mind:\n\n1. **The SDP and metadata builders are pure C.** Any change to\n   `siprec_sdp.c` / `siprec_metadata.c` must keep `make -f\n   Makefile.test test` and `make -f Makefile.test lint` green.\n2. **The FS-dependent files** can't be unit-tested without a\n   FreeSWITCH source tree. Use the `tests/README.md` field-test\n   checklist on a live build before merging behavior changes\n   in `siprec_invite.c` / `siprec_media.c` / `recording_session.c`.\n\n## Why this fork\n\nThis module started as a fork of\n[StefanYohansson/mod_siprec][upstream]. The upstream readme\ndescribes itself as an \"initial idea, not working yet\" and the\ncode reflects that: dispatching the `siprec` app crashed\nFreeSWITCH within a few hundred milliseconds because of an\nuninitialised memory pool, a NULL key into APR's hash, a\nduplicate-detect lookup against the wrong hash, and a double-\nfree of an XML child after its parent had already been released.\n\nThis fork:\n\n- fixes those four crash bugs on the dispatch path,\n- fixes two memory-pool leaks on the start/stop paths,\n- replaces the empty stub of `start_recording_session` with a\n  full SRC pipeline (multipart MIME, SDP, metadata XML, INVITE,\n  media bug, RTP fork, BYE),\n- ships a unit test suite that runs in ~0.2 s with no FreeSWITCH\n  dependency, plus a `cppcheck --enable=all\n  --check-level=exhaustive` clean codebase.\n\nThe full feature set (RFC 7865 metadata, RFC 7866 §6.4 pause /\nresume, RFC 7866 §5.2.1 `+sip.src` Contact tag, RFC 7866 §8.5\n`a=label:N`, etc.) is documented in the **Status** table near\nthe top of this file.\n\n[upstream]: https://github.com/StefanYohansson/mod_siprec\n\n## 🙌 Contributors\n\nWe welcome contributions! Thanks to these awesome people:\n\n- [Michael Mavroudis](https://github.com/mavroudis) - Lead Developer \u0026 Architect\n\n## 💖 Sponsors\n\nProudly supported by:\n\n| Sponsor | Contribution |\n|---------|--------------|\n| [VoiceTel Communications](http://www.voicetel.com) | Primary development and testing infrastructure |\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\nFiles derived from the original\n[StefanYohansson/mod_siprec][upstream] (which is itself derived\nfrom FreeSWITCH) retain their MPL 1.1 license headers and are\ndual-licensed under MPL 1.1 / MIT. New files written for this\nfork (`siprec_sdp.*`, `siprec_metadata.*`, `siprec_invite.*`,\n`siprec_media.*`, `siprec_test.c`) are MIT only.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoicetel%2Fmod_siprec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoicetel%2Fmod_siprec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoicetel%2Fmod_siprec/lists"}