Skip to content

RTCP APP on m=application UDP lines discarded for peer confirmation (GH #697 workaround breaks NAT learning for m=application line) #2057

@albertpv

Description

@albertpv

Problem performing UDP Hole punching on m=application line caused by GH #697 workaround

In MCX / MCPTT deployments (3GPP TS 24.379 / TS 24.380), calls use two separate media lines: one m=audio for the media stream and one m=application over UDP for floor control (RTCP APP‑based media plane control). (ETSI 24.379, ETSI 24.380 standards)

On the m=application line, the MCPTT client sends RTCP APP packets over UDP for several reasons:

  • Open the NAT pinhole at the client side
  • Convey the effective source IP/port for the floor‑control channel, so that the middlebox can send RTCP APP control messages back correctly (ETSI 24.380 Floor Taken , Floor Idle etc.)
  • Send floor control messages described in MCX standards (floor request, floor release,floor ack, etc)

At the moment, these first RTCP APP packets on the m=application line are discarded by rtpengine for peer address confirmation, so rtpengine does not learn the actual client source IP/port for the m=application stream. This breaks bidirectional MCX floor control when the client is behind NAT, even though the NAT/firewall pinhole has been opened successfully.

Root cause analysis

The behaviour originates from the Asterisk workaround for GH #697 (“Asterisk sends stray RTCP to RTP port”), implemented in daemon/media_socket.c.

The relevant code path (slightly abbreviated for clarity) and found in line 3126 of https://github.com/sipwise/rtpengine/blob/master/daemon/media_socket.c as of 1/23/2026 looks like this:

// GH \#697 - apparent Asterisk bug where it sends stray RTCP to the RTP port.
// Work around this by detecting this situation and ignoring the packet for
// confirmation purposes when needed. This is regardless of whether rtcp-mux
// is enabled...
if (!phc->mp.sfd->confirmed && PS_ISSET(phc->mp.stream, RTP)) {
    if (rtcp_demux_is_rtcp(&phc->s)) {
        ilog(LOG_DEBUG | LOG_FLAG_LIMIT,
             "Ignoring stray RTCP packet for confirmation purposes");
        return false; // <- peer address NOT confirmed
    }
}

Condition breakdown in an MCX/MCPTT m=application case when a client performs UDP Hole punching using an RTCP App packet:

!phc->mp.sfd->confirmed
The peer address is still being learned, so this is true on the first incoming packet.

PS_ISSET(phc->mp.stream, RTP)
The stream object is the “RTP” side of the UDP flow, even for non‑audio m=application UDP lines, so this is also true.

rtcp_demux_is_rtcp(&phc->s)
The first packet on the m=application line is a legitimate RTCP APP packet (PT=204) as defined by TS 24.380, so this check returns true.

Seen Result:

  • The RTCP APP packet successfully opens the NAT pinhole on the client’s firewall.
  • rtpengine, however, discards that packet “for confirmation purposes” and does not mark the peer address as confirmed.

Subsequent traffic from rtpengine is still sent towards the SDP c= line address/port (often a private IP or wrong mapping) instead of the learned public source IP/port, causing one‑way or broken floor control.

BTW this logic seems correct for the original Asterisk bug report (RTCP accidentally sent to the RTP port), but it is too broad for MCX deployments where RTCP APP on m=application UDP media is the only signalling on that 5‑tuple and where RTCP App packets are always used.

We did try the use of other kamailio flags when calling rtp_engine_manage such as SIP-source-address, asymmetric, or strict-source=off to attempt influence how rtpengine interprets SDP and subsequent mismatches between SDP and packet source addresses, but they are never able to bypass this runtime packet filter in media_socket.c in our tests.

The discard happens always before the peer address is confirmed, so the first (and subsequent) RTCP APP packets in m=application line cannot be used for hole punching or address learning regardless of the used signalling flags in Kamailio (or the one sending commands to RTP Engine).

``

Describe the solution you'd like

It would be extremely helpful for MCX/MCPTT deployments if rtpengine could either:

  • Remove or relax this check, allowing RTCP packets on an unconfirmed RTP stream to be used for peer confirmation at least for the m=application; or
  • Provide a configuration/stream‑level option to disable this behaviour generally or for specific media types (e.g. MCX m=application), so that MCPTT floor‑control RTCP APP packets can confirm the peer address.

One possible direction (illustrative only) of the first proposed solution is:

if (!phc->mp.sfd->confirmed && PS_ISSET(phc->mp.stream, RTP)) {

    bool is_application =
        phc->mp.media &&
        phc->mp.media->type == MEDIA_TYPE_APPLICATION;

    if (!is_application && rtcp_demux_is_rtcp(&phc->s)) {
        ilog(LOG_DEBUG | LOG_FLAG_LIMIT,
             "Ignoring stray RTCP packet for confirmation purposes");
        return false;
    }

    /* For application streams (e.g. MCX m=application UDP):
     * allow RTCP (incl. APP) to confirm peer address */
}

In this way m=application streams can opt out of the GH #697 workaround done for Asterisk (aka standard SIP/RTP use cases keep the existing safety check) without breaking use-cases for applications using m=application line like MCX.

Describe alternatives you've considered

Patch that locally, despite i think overall the workaround performed for Asterisk looks too aggresive, that is why opening this feature request.

From an MCX/MCPTT perspective it is important that:

  • The first RTCP APP packet on the m=application UDP line can be used by rtpengine to learn and confirm the peer address.
  • I understand for RTPEngine will be important the existing Asterisk workaround remains available (or default) for traditional SIP audio calls (m=audio) where guess that bug is still relevant.

The rtpengine version you checked that didn't have the feature you are asking for

  • rtpengine-mr11.5.1.46-2.el8.x86_64
  • rtpengine-debuginfo-mr11.5.1.46-2.el8.x86_64

Thanks for any feedback RTPEngine community! :-)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions