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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions