diff --git a/README.md b/README.md index 2dd1f1c3c..e36eec43b 100644 --- a/README.md +++ b/README.md @@ -1015,6 +1015,14 @@ Optionally included keys are: Omit the `a=rtcp` line from the outgoing SDP. + - `loop protect` + + Inserts a custom attribute (`a=rtpengine:...`) into the outgoing SDP to prevent *rtpengine* + processing and rewriting the same SDP multiple times. This is useful if your setup + involves signalling loops and need to make sure that *rtpengine* doesn't start looping + media packets back to itself. When this flag is present and *rtpengine* sees a matching + attribute already present in the SDP, it will leave the SDP untouched and not process + the message. * `replace` diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index c6d760dc1..0c0862ce5 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -608,6 +608,8 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { out->record_call = 1; else if (!str_cmp(s, "no-rtcp-attribute")) out->no_rtcp_attr = 1; + else if (!str_cmp(s, "loop-protect")) + out->loop_protect = 1; else { // handle values aliases from other dictionaries if (call_ng_flags_prefix(out, s, "SDES-", ng_sdes_option, NULL)) @@ -734,6 +736,13 @@ static const char *call_offer_answer_ng(bencode_item_t *input, call_ng_process_flags(&flags, input); flags.opmode = opmode; + if (flags.loop_protect && sdp_is_duplicate(&parsed)) { + ilog(LOG_INFO, "Ignoring message as SDP has already been processed by us"); + bencode_dictionary_add_str(output, "sdp", &sdp); + errstr = NULL; + goto out; + } + errstr = "Incomplete SDP specification"; if (sdp_streams(&parsed, &streams, &flags)) goto out; diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h index 7114bd9eb..f154f9913 100644 --- a/daemon/call_interfaces.h +++ b/daemon/call_interfaces.h @@ -54,6 +54,7 @@ struct sdp_ng_flags { dtls_passive:1, reset:1, record_call:1, + loop_protect:1, dtls_off:1, sdes_off:1, sdes_unencrypted_srtp:1, diff --git a/daemon/sdp.c b/daemon/sdp.c index 8702d6475..538aa088f 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -201,6 +201,7 @@ struct sdp_attribute { ATTR_RTPMAP, ATTR_FMTP, ATTR_IGNORE, + ATTR_RTPENGINE, ATTR_END_OF_CANDIDATES, } attr; @@ -219,6 +220,11 @@ struct sdp_attribute { +static char __id_buf[6*2 + 1]; // 6 hex encoded characters +static const str instance_id = STR_CONST_INIT(__id_buf); + + + INLINE struct sdp_attribute *attr_get_by_id(struct sdp_attributes *a, int id) { return g_hash_table_lookup(a->id_hash, &id); @@ -854,6 +860,8 @@ static int parse_attribute(struct sdp_attribute *a) { ret = parse_attribute_candidate(a); else if (!str_cmp(&a->name, "ice-ufrag")) a->attr = ATTR_ICE_UFRAG; + else if (!str_cmp(&a->name, "rtpengine")) + a->attr = ATTR_RTPENGINE; break; case 11: if (!str_cmp(&a->name, "ice-options")) @@ -2005,6 +2013,14 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu goto error; } + copy_up_to_end_of(chop, &session->s); + + if (flags->loop_protect) { + chopper_append_c(chop, "a=rtpengine:"); + chopper_append_str(chop, &instance_id); + chopper_append_c(chop, "\r\n"); + } + media_index = 1; for (k = session->media_streams.head; k; k = k->next) { @@ -2126,5 +2142,24 @@ error: return -1; } +int sdp_is_duplicate(GQueue *sessions) { + for (GList *l = sessions->head; l; l = l->next) { + struct sdp_session *s = l->data; + GQueue *attr_list = attr_list_get_by_id(&s->attributes, ATTR_RTPENGINE); + if (!attr_list) + return 0; + for (GList *ql = attr_list->head; ql; ql = ql->next) { + struct sdp_attribute *attr = ql->data; + if (!str_cmp_str(&attr->value, &instance_id)) + goto next; + } + return 0; +next: + ; + } + return 1; +} + void sdp_init() { + rand_hex_str(instance_id.s, instance_id.len / 2); } diff --git a/daemon/sdp.h b/daemon/sdp.h index ed76f7844..f31bb1e24 100644 --- a/daemon/sdp.h +++ b/daemon/sdp.h @@ -22,6 +22,7 @@ int sdp_parse(str *body, GQueue *sessions); int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *); void sdp_free(GQueue *sessions); int sdp_replace(struct sdp_chopper *, GQueue *, struct call_monologue *, struct sdp_ng_flags *); +int sdp_is_duplicate(GQueue *sessions); struct sdp_chopper *sdp_chopper_new(str *input); void sdp_chopper_destroy(struct sdp_chopper *chop); diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 6082bf5ec..927ae2582 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -43,6 +43,7 @@ GetOptions( 'delete-delay=i' => \$options{'delete-delay'}, 'reset' => \$options{'reset'}, 'port-latching' => \$options{'port latching'}, + 'loop-protect' => \$options{'loop protect'}, 'media-address=s' => \$options{'media address'}, 'codec-strip=s@' => \$options{'codec-strip'}, 'codec-offer=s@' => \$options{'codec-offer'}, @@ -59,7 +60,7 @@ for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address, for my $x (split(/,/, 'TOS,delete-delay')) { defined($options{$x}) and $packet{$x} = $options{$x}; } -for my $x (split(/,/, 'trust address,symmetric,asymmetric,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute')) { +for my $x (split(/,/, 'trust address,symmetric,asymmetric,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,loop protect')) { defined($options{$x}) and push(@{$packet{flags}}, $x); } for my $x (split(/,/, 'origin,session connection')) {