diff --git a/README.md b/README.md index 2dc4ce742..47bb03e6e 100644 --- a/README.md +++ b/README.md @@ -852,6 +852,11 @@ Optionally included keys are: Replace the address found in the *session-level connection* (c=) line of the SDP body. Corresponds to *rtpproxy* `c` flag. + - `SDP version` + + Take control of the version field in the SDP and make sure it's increased every + time the SDP changes, and left unchanged if the SDP is the same. + * `direction` Contains a list of two strings and corresponds to the *rtpproxy* `e` and `i` flags. Each element must diff --git a/daemon/call.c b/daemon/call.c index 1bb580c74..e1fd1c7e7 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2626,6 +2626,8 @@ static void __call_free(void *p) { g_hash_table_destroy(m->other_tags); g_hash_table_destroy(m->branches); g_hash_table_destroy(m->media_ids); + if (m->last_sdp) + g_string_free(m->last_sdp, TRUE); g_slice_free1(sizeof(*m), m); } diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 3694c875a..b4ffb8eb5 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -706,13 +706,23 @@ static void call_ng_flags_rtcp_mux(struct sdp_ng_flags *out, str *s, void *dummy } static void call_ng_flags_replace(struct sdp_ng_flags *out, str *s, void *dummy) { str_hyphenate(s); - if (!str_cmp(s, "origin")) - out->replace_origin = 1; - else if (!str_cmp(s, "session-connection")) - out->replace_sess_conn = 1; - else - ilog(LOG_WARN, "Unknown 'replace' flag encountered: '" STR_FORMAT "'", - STR_FMT(s)); + switch (__csh_lookup(s)) { + case CSH_LOOKUP("origin"): + out->replace_origin = 1; + break; + case CSH_LOOKUP("session-connection"): + out->replace_sess_conn = 1; + break; + case CSH_LOOKUP("sdp-version"): + out->replace_sdp_version = 1; + break; + case CSH_LOOKUP("SDP-version"): + out->replace_sdp_version = 1; + break; + default: + ilog(LOG_WARN, "Unknown 'replace' flag encountered: '" STR_FORMAT "'", + STR_FMT(s)); + } } static void call_ng_flags_supports(struct sdp_ng_flags *out, str *s, void *dummy) { if (!str_cmp(s, "load limit")) diff --git a/daemon/sdp.c b/daemon/sdp.c index 0f43eaaaf..c971e40cd 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -29,8 +29,10 @@ struct network_address { struct sdp_origin { str username; str session_id; - str version; + str version_str; struct network_address address; + unsigned long long version_num; + size_t version_output_pos; int parsed:1; }; @@ -347,9 +349,10 @@ static int parse_origin(str *value_str, struct sdp_origin *output) { EXTRACT_TOKEN(username); EXTRACT_TOKEN(session_id); - EXTRACT_TOKEN(version); + EXTRACT_TOKEN(version_str); EXTRACT_NETWORK_ADDRESS_NF(address); + output->version_num = strtoull(output->version_str.s, NULL, 10); output->parsed = 1; return 0; } @@ -1656,6 +1659,29 @@ INLINE void chopper_append_c(struct sdp_chopper *c, const char *s) { INLINE void chopper_append_str(struct sdp_chopper *c, const str *s) { chopper_append(c, s->s, s->len); } +static void chopper_replace(struct sdp_chopper *c, str *old, size_t *old_pos, + const char *repl, size_t repl_len) +{ + // adjust for offsets created within this run + *old_pos += c->offset; + // is our new value longer? + if (repl_len > old->len) { + // overwrite + insert + g_string_overwrite_len(c->output, *old_pos, repl, old->len); + g_string_insert(c->output, *old_pos + old->len, repl + old->len); + c->offset += repl_len - old->len; + old->len = repl_len; + } + else { + // overwrite + optional erase + g_string_overwrite(c->output, *old_pos, repl); + if (repl_len < old->len) { + g_string_erase(c->output, *old_pos + repl_len, old->len - repl_len); + c->offset -= old->len - repl_len; + old->len = repl_len; + } + } +} #define chopper_append_printf(c, f...) g_string_append_printf((c)->output, f) @@ -2363,6 +2389,41 @@ static void insert_rtcp_attr(struct sdp_chopper *chop, struct packet_stream *ps, } +static void sdp_version_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologue *monologue) { + char version_str[64]; + snprintf(version_str, sizeof(version_str), "%llu", monologue->sdp_version); + size_t version_len = strlen(version_str); + chop->offset = 0; // start from the top + + for (GList *l = sessions->head; l; l = l->next) { + struct sdp_session *session = l->data; + struct sdp_origin *origin = &session->origin; + // update string unconditionally to keep position tracking intact + chopper_replace(chop, &origin->version_str, &origin->version_output_pos, version_str, version_len); + } +} + +static void sdp_version_check(struct sdp_chopper *chop, GQueue *sessions, struct call_monologue *monologue) { + // we really expect only a single session here, but we treat all the same regardless, + // and use the same version number on all of them + + // first update all versions to match our single version + sdp_version_replace(chop, sessions, monologue); + // then check if we need to change + if (!monologue->last_sdp) + goto dup; + if (g_string_equal(monologue->last_sdp, chop->output)) + return; + + // mismatch detected. increment version, update again, and store copy + monologue->sdp_version++; + sdp_version_replace(chop, sessions, monologue); + g_string_free(monologue->last_sdp, TRUE); +dup: + monologue->last_sdp = g_string_new_len(chop->output->str, chop->output->len); +} + + /* called with call->master_lock held in W */ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologue *monologue, struct sdp_ng_flags *flags) @@ -2393,6 +2454,15 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu goto error; ps = j->data; + // record position of o= line and init SDP version + copy_up_to(chop, &session->origin.version_str); + session->origin.version_output_pos = chop->output->len; + if (!monologue->sdp_version) { + monologue->sdp_version = session->origin.version_num; + if (monologue->sdp_version == 0 || monologue->sdp_version == ULLONG_MAX) + monologue->sdp_version = random(); + } + sess_conn = 0; if (flags->replace_sess_conn) sess_conn = 1; @@ -2576,6 +2646,11 @@ next: } copy_remainder(chop); + + if (flags->replace_sdp_version) + sdp_version_check(chop, sessions, monologue); + + return 0; error: diff --git a/include/call.h b/include/call.h index dd45ddefc..f03466d05 100644 --- a/include/call.h +++ b/include/call.h @@ -371,6 +371,8 @@ struct call_monologue { GQueue medias; GHashTable *media_ids; struct media_player *player; + unsigned long long sdp_version; + GString *last_sdp; int block_dtmf:1; int block_media:1; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index cce7f2871..110de1f31 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -71,6 +71,7 @@ struct sdp_ng_flags { port_latching:1, replace_origin:1, replace_sess_conn:1, + replace_sdp_version:1, rtcp_mux_offer:1, rtcp_mux_require:1, rtcp_mux_demux:1, diff --git a/include/sdp.h b/include/sdp.h index cc527597d..b68d92b72 100644 --- a/include/sdp.h +++ b/include/sdp.h @@ -9,12 +9,9 @@ struct sdp_chopper { str *input; - int position; + size_t position; GString *output; -// GStringChunk *chunk; -// GArray *iov; -// int iov_num; -// int str_len; + ssize_t offset; // for post-processing using chopper_replace }; extern const str rtpe_instance_id; diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 7c518e7e6..dda557d29 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -12407,4 +12407,551 @@ rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); $resp = rtpe_req('statistics', 'statistics'); + + +# SDP version tests + +new_call; + +offer('SDP version simple increments', { replace => ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < ['SDP version'] }, < \$options{'unidirectional'}, 'replace-origin' => \$options{'replace-origin'}, 'replace-session-connection' => \$options{'replace-session connection'}, + 'replace-sdp-version' => \$options{'replace-sdp version'}, 'client-address=s' => \$options{'client-address'}, 'sdp=s' => \$options{'sdp'}, 'sdp-file=s' => \$options{'sdp-file'}, @@ -99,7 +100,7 @@ for my $x (split(/,/, 'TOS,delete-delay')) { for my $x (split(/,/, 'trust address,symmetric,asymmetric,unidirectional,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,full rtcp attribute,loop protect,record call,always transcode,all,pad crypto,generate mid,fragment,original sendrecv,symmetric codecs,asymmetric codecs,inject DTMF,generate RTCP,single codec')) { defined($options{$x}) and push(@{$packet{flags}}, $x); } -for my $x (split(/,/, 'origin,session connection')) { +for my $x (split(/,/, 'origin,session connection,sdp version')) { defined($options{'replace-' . $x}) and push(@{$packet{replace}}, $x); } for my $x (split(/,/, 'rtcp-mux,SDES,supports,T.38,OSRTP')) {