Summary
When media is engaged through the rtp_relay abstraction (rtp_relay_engage("rtpproxy", ...))
rather than the classic rtpproxy_engage(), the session teardown sends an rtpproxy D
(delete) command without the From-tag. The From-tag slot in the command is empty, so
rtpproxy cannot match the session that was created at offer/answer time and the media session
is not torn down (leaked relay ports / stale sessions).
The Call-ID and the To-tag are present; only the From-tag is missing.
Affected versions
- 3.6.7 (confirmed)
- 3.6.5 (confirmed)
- current
master / git (code path is identical)
Environment
- OpenSIPS 3.6.7
rtp_relay + rtpproxy modules
- rtpproxy control port 9002 (UDP)
- Media engaged with
rtp_relay_engage("rtpproxy", ...) (used for SIPREC, so an
rtp_relay context is created on the dialog — rtpproxy_engage() does not create one).
Observed rtpproxy control traffic
Captured on the rtpproxy control socket (192.168.101.46:9002). Cookie is the first token;
1 is the caller (From) tag, 4954 is the callee (To) tag.
# OFFER (U) — caller from-tag = 1
U 192.168.101.98:33611 -> 192.168.101.46:9002
47_4293_446 Uc0,101 1-2611667@10.248.16.47 10.248.16.47 6000 1;1
47_4293_446 41856 192.168.101.46
# ANSWER (L) — from-tag = 1, to-tag = 4954
U 192.168.101.98:13292 -> 192.168.101.46:9002
47_4293_447 Lc0,101 1-2611667@10.248.16.47 10.248.16.52 20000 1;1 4954;1
47_4293_447 36116 192.168.101.46
# DELETE (D) — From-tag MISSING (note the double space), only To-tag (4954) is present
U 192.168.101.98:13292 -> 192.168.101.46:9002
47_4293_448 D 1-2611667@10.248.16.47 4954
47_4293_448 E50
The D line should be D 1-2611667@10.248.16.47 1 4954 (callid, from-tag, to-tag).
Instead the from-tag token is empty, producing the double space and the trailing
to-tag with no from-tag. (rtpproxy replies E50 = session not found.)
Root cause
modules/rtp_relay/rtp_relay_ctx.c
The teardown path builds the delete request but never populates the From-tag.
rtp_relay_delete_ctx() zero-initialises the request and sets only Call-ID and To-tag:
static void rtp_relay_delete_ctx(struct rtp_relay_ctx *ctx,
struct rtp_relay_sess *sess, int leg)
{
struct rtp_relay_session info;
memset(&info, 0, sizeof info);
info.callid = &ctx->callid;
if (!info.callid->len)
info.callid = &ctx->dlg_callid;
info.to_tag = &ctx->to_tag; /* <-- from_tag never set; stays NULL */
info.branch = RTP_RELAY_ALL_BRANCHES;
rtp_relay_delete(&info, ctx, sess, leg);
}
rtp_relay_delete() back-fills the To-tag from the context, but there is no
equivalent back-fill for the From-tag:
static int rtp_relay_delete(struct rtp_relay_session *info,
struct rtp_relay_ctx *ctx, struct rtp_relay_sess *sess, int leg)
{
...
/* from_tag is never filled in here */
if (!info->to_tag && ctx->to_tag.len)
info->to_tag = &ctx->to_tag;
...
ret = sess->relay->funcs.delete(info, &sess->server, ...);
}
Compare with rtp_relay_offer() / rtp_relay_answer(), which set both tags
(selecting from ctx->from_tag / ctx->to_tag according to the leg). The delete
path simply omits the From-tag.
- In the rtpproxy module,
rtpproxy_api_delete()
(modules/rtpproxy/rtpproxy.c) calls
rtpproxy_fill_call_args(..., require_from_tag = 0). Because sess->from_tag is
NULL and require_from_tag == 0, args->from_tag is left empty
(no fallback to the SIP message either — info.msg is NULL on the dialog-end
callback):
if (!sess->from_tag) {
if (require_from_tag &&
(!sess->msg ||
get_from_tag(sess->msg, &args->from_tag) == -1 ||
args->from_tag.len == 0)) {
LM_ERR("can't get From tag\n");
return 0;
}
/* require_from_tag == 0 -> args->from_tag stays {NULL, 0} */
} else {
args->from_tag = *sess->from_tag;
}
unforce_rtpproxy() then lays out the command with the (empty) from-tag in slot
vu[4]:
RTPP_VCMD_INIT(vdel, 4 + 3, {"D", 1}, {" ", 1}, {NULL, 0}, {" ", 1},
/*.vu[0]*//*.vu[1]*//*.vu[2]*//*.vu[3]*/
{NULL, 0}, {" ", 1}, {NULL, 0});
/*.vu[4]*//*.vu[5]*//*.vu[6] */
STR2IOVEC(args->callid, vdel.vu[2]);
STR2IOVEC(args->from_tag, vdel.vu[4]); /* empty -> "D <callid> <to_tag>" */
STR2IOVEC(args->to_tag, vdel.vu[6]);
Result: D <callid> <empty> <to_tag> — exactly the trace above.
Impact
- rtpproxy cannot match the session (it was created with the caller from-tag), so the
relay session is not deleted on BYE/dialog termination.
- Leaked rtpproxy ports / stale sessions accumulate over time.
- Affects every
rtp_relay-engaged call (notably SIPREC setups, which must use
rtp_relay_engage() to obtain an rtp_relay context on the dialog).
Proposed fix
Back-fill the From-tag from the context in rtp_relay_delete(), mirroring the existing
To-tag handling. This covers all callers (dialog-end teardown and the failover teardown
path), needs no leg-based swap (rtpproxy matches the session by Call-ID + from-tag +
to-tag regardless of order), and avoids any NULL dereference:
--- a/modules/rtp_relay/rtp_relay_ctx.c
+++ b/modules/rtp_relay/rtp_relay_ctx.c
@@ rtp_relay_delete()
+ if (!info->from_tag && ctx->from_tag.len)
+ info->from_tag = &ctx->from_tag;
if (!info->to_tag && ctx->to_tag.len)
info->to_tag = &ctx->to_tag;
After the fix the teardown emits the correct command:
D 1-2611667@10.248.16.47 1 4954
and rtpproxy removes the session.
Reproduction
- Engage media via
rtp_relay_engage("rtpproxy", ...) (so an rtp_relay context is
created on the dialog).
- Complete a call (offer/answer) and then send BYE.
- Capture the rtpproxy control socket — the
D command has an empty From-tag and
rtpproxy replies E50 (session not found); the session is never removed.
Summary
When media is engaged through the
rtp_relayabstraction (rtp_relay_engage("rtpproxy", ...))rather than the classic
rtpproxy_engage(), the session teardown sends an rtpproxyD(delete) command without the From-tag. The From-tag slot in the command is empty, so
rtpproxy cannot match the session that was created at offer/answer time and the media session
is not torn down (leaked relay ports / stale sessions).
The Call-ID and the To-tag are present; only the From-tag is missing.
Affected versions
master/ git (code path is identical)Environment
rtp_relay+rtpproxymodulesrtp_relay_engage("rtpproxy", ...)(used for SIPREC, so anrtp_relaycontext is created on the dialog —rtpproxy_engage()does not create one).Observed rtpproxy control traffic
Captured on the rtpproxy control socket (
192.168.101.46:9002). Cookie is the first token;1is the caller (From) tag,4954is the callee (To) tag.The
Dline should beD 1-2611667@10.248.16.47 1 4954(callid, from-tag, to-tag).Instead the from-tag token is empty, producing the double space and the trailing
to-tag with no from-tag. (rtpproxy replies
E50= session not found.)Root cause
modules/rtp_relay/rtp_relay_ctx.cThe teardown path builds the delete request but never populates the From-tag.
rtp_relay_delete_ctx()zero-initialises the request and sets only Call-ID and To-tag:rtp_relay_delete()back-fills the To-tag from the context, but there is noequivalent back-fill for the From-tag:
Compare with
rtp_relay_offer()/rtp_relay_answer(), which set both tags(selecting from
ctx->from_tag/ctx->to_tagaccording to the leg). The deletepath simply omits the From-tag.
rtpproxy_api_delete()(
modules/rtpproxy/rtpproxy.c) callsrtpproxy_fill_call_args(..., require_from_tag = 0). Becausesess->from_tagisNULLandrequire_from_tag == 0,args->from_tagis left empty(no fallback to the SIP message either —
info.msgisNULLon the dialog-endcallback):
unforce_rtpproxy()then lays out the command with the (empty) from-tag in slotvu[4]:Result:
D <callid> <empty> <to_tag>— exactly the trace above.Impact
relay session is not deleted on BYE/dialog termination.
rtp_relay-engaged call (notably SIPREC setups, which must usertp_relay_engage()to obtain anrtp_relaycontext on the dialog).Proposed fix
Back-fill the From-tag from the context in
rtp_relay_delete(), mirroring the existingTo-tag handling. This covers all callers (dialog-end teardown and the failover teardown
path), needs no leg-based swap (rtpproxy matches the session by Call-ID + from-tag +
to-tag regardless of order), and avoids any NULL dereference:
After the fix the teardown emits the correct command:
and rtpproxy removes the session.
Reproduction
rtp_relay_engage("rtpproxy", ...)(so anrtp_relaycontext iscreated on the dialog).
Dcommand has an empty From-tag andrtpproxy replies
E50(session not found); the session is never removed.