SRTP index (ROC) is kept across a DTLS restart: after the sequence number has wrapped, a new DTLS association gets packets it cannot authenticate

Abierto
#2,240 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
40/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c

Línea de trabajo

Start with daemon/call.c at __dtls_restart() and call_stream_crypto_reset(), then trace how daemon/rtp.c derives the SRTP packet index from ext_seq and how daemon/dtls.c installs new keys. The report proposes resetting the media's inbound and outbound SSRC indices on DTLS restart; check existing tests for DTLS restart and SRTP rollover behavior. Done means a restarted association with a wrapped sequence number can authenticate downlink packets, while existing SRTP behavior still passes.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

bug
rtpengine version the issue has been seen with

mr26.3.0.0. Reproduced with userspace forwarding (table = -1). We also see field calls of the same shape through an SBC with kernel forwarding (table = 0), but haven't proven those separately. The same code is on master as of 2026-11-06 (89589b7)

Used distribution and its version

Debian GNU/Linux 11

Linux kernel version used

5.10.0-35-amd64

CPU architecture issue was seen on (see uname -m)

None

Expected behaviour you didn't see

Setup

WebRTC client (Chrome / Electron, DTLS-SRTP, AEAD_AES_256_GCM) <-> rtpengine <-> Asterisk (plain RTP).
dtls-passive = true. rtpengine forwards Asterisk's SSRC to the client unchanged; no transcoding.
What happens

A call runs long enough for the downlink (Asterisk -> rtpengine -> client) RTP sequence number to wrap past 65535. Asterisk starts its sequence number below 0x8000, so this happens 11 to 22 minutes into a call.
The client replaces its peer connection (a new RTCPeerConnection after a network failure), and sends a re-INVITE with a new a=fingerprint. rtpengine logs DTLS fingerprint changed, restarting DTLS, and the new DTLS handshake completes.
rtpengine keeps sending the downlink with the same SSRC and continuing sequence numbers. Each packet is protected with the old rollover counter (ROC >= 1) under the new keys.
The client's new SRTP context starts with ROC 0 (RFC 3711 3.3.1; RFC 5764 4.2: the DTLS association yields new keys, hence new crypto contexts). Every downlink packet fails authentication. In the browser: the transport's packetsReceived / bytesReceived grow normally, but no inbound-rtp stats object is ever created, and no frame is decoded. The call is one-way silent until it ends.
The uplink is not affected: the new peer connection sends new SSRCs, which get fresh entries.

Reproduction (ours)

One long call; force a new client peer connection by blocking the client's media port for 32 s.
Before the downlink sequence number wraps: the new peer connection plays, 3 of 3.
After the wrap: 0 of 2 play. Transport counters are the same as on the working attempts. SSRC and sequence numbers are the same as before the restart.
Production: 8 calls over 16 days on calls at least 15 minutes old with a wrapped downlink, 0 of 8 recovered. Two calls where the far end's SSRC happened to change did recover.

Not the same as #2220 or #2238: no transcoding and no fixed-egress-SSRC here (plain pass-through, DTLS=passive toward the browser). The failing direction is rtpengine → browser, and the trigger is __dtls_restart() after the downlink sequence has wrapped. I checked master at 89589b7 (2026-11-06): __dtls_restart() still doesn’t reset ext_seq.

Where in the code

daemon/rtp.c packet_index() takes the SRTP index from ssrc_entry_call->stats->ext_seq, and derives the ROC from its upper bits.
daemon/call.c __dtls_restart() (via __fingerprint_changed()) shuts DTLS down and re-runs __init_stream(), but never resets ext_seq. The new keys are installed by crypto_init() in dtls.c, which doesn't touch it either.
call_stream_crypto_reset() already zeroes ext_seq for every SSRC of the media, but only on an endpoint change of a non-DTLS stream.
Proposed fix (patch attached / PR): zero ext_seq for the media's ssrc_hash_in and ssrc_hash_out in __dtls_restart(), the same way call_stream_crypto_reset() does. After the reset, the next packet re-seeds the index from its own sequence number with ROC 0, which matches the peer's new context.

static void __ssrc_hash_reset_index(struct ssrc_hash *h) {
mutex_lock(&h->lock);
for (GList *l = h->nq.head; l; l = l->next) {
struct ssrc_entry_call *se = l->data;
atomic_set_na(&se->stats->ext_seq, 0);
}
mutex_unlock(&h->lock);
}

static void __dtls_restart(struct call_media *m) {
IQUEUE_FOREACH(&m->streams, ps) {
PS_CLEAR(ps, FINGERPRINT_VERIFIED);
dtls_shutdown(ps);
__init_stream(ps);
}
__ssrc_hash_reset_index(&m->ssrc_hash_in);
__ssrc_hash_reset_index(&m->ssrc_hash_out);
}

Unexpected behaviour you saw

No response

Steps to reproduce the problem

No response

Additional program output to the terminal or logs illustrating the issue

Anything else?

No response

Lenguaje dominante
Perl
Estrellas
992
Forks
459
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de sipwise/rtpengine

Todos los issues de sipwise/rtpengine

Issues similares

Más issues de Perl

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.