From f8fef89f5bdd8d3e36563e3fc24035062b4574d0 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 31 Jan 2023 15:17:49 -0500 Subject: [PATCH] MT#56521 support legacy non-RFC OSRTP Change-Id: I9ba7f1d36ea061c18f8451428f8856edce074436 --- README.md | 16 +++- daemon/call.c | 4 +- daemon/call_interfaces.c | 10 ++- daemon/sdp.c | 105 +++++++++++++++++++--- include/call.h | 3 + include/call_interfaces.h | 3 +- t/auto-daemon-tests.pl | 179 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 305 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4455102bb..9a0cd5444 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ the following additional features are available: + HMAC-SHA1 packet authentication + Bridging between RTP and SRTP user agents + Opportunistic SRTP (RFC 8643) + + Legacy non-RFC (dual `m=` line) best-effort SRTP + AES-GCM Authenticated Encryption (AEAD) (RFC 7714) + `a=tls-id` as per RFC 8842 - Support for RTCP profile with feedback extensions (RTP/AVPF, RFC 4585 and 5124) @@ -990,11 +991,24 @@ Optionally included keys are: When processing a non-OSRTP offer, convert it to an OSRTP offer. Will result in RTP/SRTP transcoding if the OSRTP offer is accepted. - - `accept` + - `accept-RFC` When processing a non-OSRTP answer in response to an OSRTP offer, accept the OSRTP offer anyway. Results in RTP/SRTP transcoding. + - `accept-legacy` + + Enables support for legacy, non-RFC "best effort" SRTP offers, which + consist of media sections being advertised twice, once as plain RTP and + once as SRTP. With this option set, *rtpengine* will treat such SDPs as + SRTP SDPs, removing the duplicated media sections. This flag must be + given for both offer and answer messages. + + - `accept` + + Short for both `accept-RFC` and `accept-legacy`. Can be used + unconditionally in all signalling if so desired. + * `ptime` Contains an integer. If set, changes the `a=ptime` attribute's value in the outgoing diff --git a/daemon/call.c b/daemon/call.c index c1bcc8f88..56a4807ba 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2513,7 +2513,7 @@ static void __update_media_protocol(struct call_media *media, struct call_media && media->protocol && media->protocol->osrtp) { // accept it? - if (flags->osrtp_accept) + if (flags->osrtp_accept_rfc) ; else media->protocol = NULL; // reject @@ -2912,7 +2912,7 @@ static int __media_init_from_flags(struct call_media *other_media, struct call_m bf_copy_same(&other_media->media_flags, &sp->sp_flags, SHARED_FLAG_RTCP_MUX | SHARED_FLAG_ASYMMETRIC | SHARED_FLAG_UNIDIRECTIONAL | SHARED_FLAG_ICE | SHARED_FLAG_TRICKLE_ICE | SHARED_FLAG_ICE_LITE_PEER | - SHARED_FLAG_RTCP_FB); + SHARED_FLAG_RTCP_FB | SHARED_FLAG_LEGACY_OSRTP); // duplicate the entire queue of offered crypto params crypto_params_sdes_queue_clear(&other_media->sdes_in); diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index c7f9dc669..5e9cded6a 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -580,8 +580,16 @@ INLINE void ng_sdes_option(struct sdp_ng_flags *out, str *s, void *dummy) { INLINE void ng_osrtp_option(struct sdp_ng_flags *out, str *s, void *dummy) { switch (__csh_lookup(s)) { + case CSH_LOOKUP("accept-rfc"): + case CSH_LOOKUP("accept-RFC"): + out->osrtp_accept_rfc = 1; + break; + case CSH_LOOKUP("accept-legacy"): + out->osrtp_accept_legacy = 1; + break; case CSH_LOOKUP("accept"): - out->osrtp_accept = 1; + out->osrtp_accept_rfc = 1; + out->osrtp_accept_legacy = 1; break; case CSH_LOOKUP("offer"): out->osrtp_offer = 1; diff --git a/daemon/sdp.c b/daemon/sdp.c index bf2f32b15..4c172a6d3 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -80,6 +80,8 @@ struct sdp_media { struct sdp_attributes attributes; GQueue format_list; /* list of slice-alloc'd str objects */ enum media_type media_type_id; + + unsigned int legacy_osrtp:1; }; struct attribute_rtcp { @@ -1536,6 +1538,73 @@ static void __sdp_t38(struct stream_params *sp, struct sdp_media *media) { } +static void sp_free(void *p) { + struct stream_params *s = p; + + codec_store_cleanup(&s->codecs); + ice_candidates_free(&s->ice_candidates); + crypto_params_sdes_queue_clear(&s->sdes_params); + g_slice_free1(sizeof(*s), s); +} + + +// Check the list for a legacy non-RFC OSRTP offer: +// Given m= lines must be alternating between one RTP and one SRTP m= line, with matching +// types between each pair. +// If found, rewrite the list to pretend that only the SRTP m=line was given, and mark +// the session media accordingly. +// TODO: should be handled by monologue_offer_answer, without requiring OSRTP-accept to be +// set for re-invites. SDP rewriting and skipping media sections should be handled by +// associating offer/answer media sections directly with each other, instead of requiring +// the indexing to be in order and instead of requiring all sections between monologue and sdp_media +// lists to be matching. +static void legacy_osrtp_accept(struct stream_params *sp, GQueue *streams, GList *prev_media_link, + struct sdp_ng_flags *flags, unsigned int *num) +{ + if (!streams->tail) + return; + if (!prev_media_link) + return; + struct stream_params *last = streams->tail->data; + + if (!flags->osrtp_accept_legacy) + return; + + // protocols must be known + if (!sp->protocol) + return; + if (!last->protocol) + return; + // types must match + if (sp->type_id != last->type_id) + return; + + // we must be looking at a SRTP media section + if (!sp->protocol->rtp) + return; + if (!sp->protocol->srtp) + return; + + // previous one must be a plain RTP section + if (!last->protocol->rtp) + return; + if (last->protocol->srtp) + return; + + // is this a non-rejected SRTP section? + if (sp->rtp_endpoint.port) { + // looks ok. remove the previous one and only retain this one. mark it as such. + g_queue_pop_tail(streams); + sp_free(last); + + SP_SET(sp, LEGACY_OSRTP); + struct sdp_media *prev_media = prev_media_link->data; + prev_media->legacy_osrtp = 1; + sp->index--; + (*num)--; + } +} + /* XXX split this function up */ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *flags) { struct sdp_session *session; @@ -1543,10 +1612,9 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *fl struct stream_params *sp; GList *l, *k; const char *errstr; - int num; + unsigned int num = 0; struct sdp_attribute *attr; - num = 0; for (l = sessions->head; l; l = l->next) { session = l->data; @@ -1662,6 +1730,8 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *fl sp->protocol = &transport_protocols[sp->protocol->osrtp_proto]; } + legacy_osrtp_accept(sp, streams, k->prev, flags, &num); + // a=mid attr = attr_get_by_id(&media->attributes, ATTR_MID); if (attr) @@ -1707,14 +1777,6 @@ error: return -1; } -static void sp_free(void *p) { - struct stream_params *s = p; - - codec_store_cleanup(&s->codecs); - ice_candidates_free(&s->ice_candidates); - crypto_params_sdes_queue_clear(&s->sdes_params); - g_slice_free1(sizeof(*s), s); -} void sdp_streams_free(GQueue *q) { g_queue_clear_full(q, sp_free); } @@ -2797,6 +2859,13 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu for (k = session->media_streams.head; k; k = k->next) { sdp_media = k->data; + + // skip over received dummy SDP sections + if (sdp_media->legacy_osrtp) { + skip_over(chop, &sdp_media->s); + continue; + } + err = "no matching media"; if (!m) goto error; @@ -2810,6 +2879,22 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu goto error; ps = rtp_ps_link->data; + // generate rejected m= line for accepted legacy OSRTP + if (MEDIA_ISSET(call_media, LEGACY_OSRTP) + && call_media->protocol + && call_media->protocol->srtp) + { + chopper_append_c(chop, "m="); + chopper_append_str(chop, &call_media->type); + const struct transport_protocol *prtp + = &transport_protocols[call_media->protocol->rtp_proto]; + chopper_append_c(chop, " 0 "); + chopper_append_c(chop, prtp->name); + chopper_append_c(chop, " "); + chopper_append_str(chop, &call_media->format_str); + chopper_append_c(chop, "\r\n"); + } + bool is_active = true; if (flags->ice_option != ICE_FORCE_RELAY && call_media->type_id != MT_MESSAGE) { diff --git a/include/call.h b/include/call.h index 353c312ff..050237ddc 100644 --- a/include/call.h +++ b/include/call.h @@ -113,6 +113,7 @@ enum { #define SHARED_FLAG_ICE_LITE_PEER 0x00000800 #define SHARED_FLAG_UNIDIRECTIONAL 0x00001000 #define SHARED_FLAG_RTCP_FB 0x00002000 +#define SHARED_FLAG_LEGACY_OSRTP 0x00004000 /* struct stream_params */ #define SP_FLAG_NO_RTCP 0x00010000 @@ -130,6 +131,7 @@ enum { #define SP_FLAG_TRICKLE_ICE SHARED_FLAG_TRICKLE_ICE #define SP_FLAG_ICE_LITE_PEER SHARED_FLAG_ICE_LITE_PEER #define SP_FLAG_RTCP_FB SHARED_FLAG_RTCP_FB +#define SP_FLAG_LEGACY_OSRTP SHARED_FLAG_LEGACY_OSRTP /* struct packet_stream */ #define PS_FLAG_RTP 0x00010000 @@ -184,6 +186,7 @@ enum { #define MEDIA_FLAG_ECHO 0x10000000 #define MEDIA_FLAG_BLACKHOLE 0x20000000 #define MEDIA_FLAG_REORDER_FORCED 0x40000000 +#define MEDIA_FLAG_LEGACY_OSRTP SHARED_FLAG_LEGACY_OSRTP /* access macros */ #define SP_ISSET(p, f) bf_isset(&(p)->sp_flags, SP_FLAG_ ## f) diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 2d934042d..e0770fd0b 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -138,7 +138,8 @@ struct sdp_ng_flags { media_handover:1, dtls_passive:1, dtls_reverse_passive:1, - osrtp_accept:1, + osrtp_accept_legacy:1, + osrtp_accept_rfc:1, osrtp_offer:1, reset:1, egress:1, diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 48ffb76a8..f049bed0c 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -80,6 +80,185 @@ sub stun_succ { +new_call; + +offer('legacy OSRTP offer, control', + { flags => [ ] }, < [ 'OSRTP-accept' ], 'transport-protocol' => 'RTP/AVP' }, < [ 'OSRTP-accept' ] }, < 'nonew' }, < [ 'OSRTP-accept' ] }, <