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

Aperta
#2,240 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
40/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
c

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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

Lingua principale
Perl
Stelle
992
Fork
459
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di sipwise/rtpengine

Tutte le issue di sipwise/rtpengine

Issue simili

Altre issue su Perl

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.