Skip to content

[BUG] rtp_relay: teardown sends rtpproxy D (delete) command with an empty From-tag #3955

Description

@sahmed-bw

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.

  1. 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);
}
  1. 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.

  1. 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;
}
  1. 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

  1. Engage media via rtp_relay_engage("rtpproxy", ...) (so an rtp_relay context is
    created on the dialog).
  2. Complete a call (offer/answer) and then send BYE.
  3. 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.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions