Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions daemon/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -3165,6 +3165,11 @@ static void media_init_from_flags(struct call_media *media, sdp_ng_flags *flags)

if (flags->recrypt)
MEDIA_SET(media, RECRYPT);

if (flags->fixed_egress_ssrc) {
while (!media->fixed_egress_ssrc)
media->fixed_egress_ssrc = ssl_random();
}
}

__attribute__((nonnull(1, 2)))
Expand Down
4 changes: 4 additions & 0 deletions daemon/call_flags.c
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,10 @@ const char *call_ng_flags_flags(str *s, unsigned int idx, helper_arg arg) {
case CSH_LOOKUP("strict source"):
out->strict_source = true;
break;
case CSH_LOOKUP("fixed-egress-SSRC"):
case CSH_LOOKUP("fixed egress SSRC"):
out->fixed_egress_ssrc = true;
break;
case CSH_LOOKUP("strip-extmap"):
case CSH_LOOKUP("strip extmap"):
return call_ng_flags_str_ht(STR_PTR("all"), 0, &out->rtpext_strip);
Expand Down
15 changes: 15 additions & 0 deletions daemon/call_interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,21 @@ static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, const char *addr)
/* place return output SDP */
ctx->ngbuf->sdp_out = sdp_out.s;
ctx->parser_ctx.parser->dict_add_str(output, "sdp", &sdp_out);

/* report the SSRCs picked for media sent towards the recipient of this SDP,
* one entry per m= section */
if (flags.fixed_egress_ssrc && to_ml->medias) {
parser_arg ssrcs = parser->dict_add_list(output, "egress SSRC");
for (unsigned int i = 0; i < to_ml->medias->len; i++) {
struct call_media *media = to_ml->medias->pdata[i];
if (!media || !media->fixed_egress_ssrc)
continue;
parser_arg ent = parser->list_add_dict(ssrcs);
parser->dict_add_int(ent, "index", media->index);
parser->dict_add_str(ent, "type", &media->type);
parser->dict_add_int(ent, "SSRC", media->fixed_egress_ssrc);
}
}
if (flags.supports_rollback) {
parser_arg supported = parser->dict_add_list(output, "supported");
parser->list_add_string(supported, "rollback");
Expand Down
7 changes: 7 additions & 0 deletions daemon/codec.c
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,13 @@ static int handler_func_passthrough(struct codec_handler *h, struct media_packet

ML_CLEAR(mp->media->monologue, DTMF_INJECTION_ACTIVE);

// substitute a fixed egress SSRC. done here rather than in the SSRC
// passthrough handler as that one is only built with transcoding support.
if (mp->rtp && mp->media_out->fixed_egress_ssrc && mp->ssrc_out) {
mp->rtp->ssrc = htonl(mp->ssrc_out->h.ssrc);
mp->rtp->seq_num = htons(ntohs(mp->rtp->seq_num) + mp->ssrc_out->seq_diff);
}

__buffer_delay_raw(h->delay_buffer, h, codec_add_raw_packet, mp, h->source_pt.clock_rate);

return 0;
Expand Down
31 changes: 28 additions & 3 deletions daemon/media_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -1859,7 +1859,8 @@ static const char *kernelize_one(kernelize_state *s,

}

if (MEDIA_ISSET(media, ECHO) || sink_handler->attrs.transcoding)
if (MEDIA_ISSET(media, ECHO) || sink_handler->attrs.transcoding
|| sink->media->fixed_egress_ssrc)
redi->output.ssrc_subst = 1;

__re_address_translate_ep(&redi->output.dst_addr, &sink->endpoint);
Expand Down Expand Up @@ -3049,15 +3050,39 @@ static void media_packet_rtp_out(struct packet_handler_ctx *phc, struct sink_han

const char *unkern = NULL;

uint32_t fixed_ssrc = phc->mp.media_out->fixed_egress_ssrc;

/* A fixed egress SSRC is just a mapping from the ingress SSRC, so record it
* where every consumer already looks for one. */
if (fixed_ssrc && phc->mp.ssrc_in)
phc->mp.ssrc_in->ssrc_map_out = fixed_ssrc;

if (G_LIKELY(!phc->rtcp && phc->mp.rtp)) {
unkern = __stream_ssrc_out(phc->out_srtp, phc->mp.rtp->ssrc, phc->mp.ssrc_in,
&phc->mp.ssrc_out, &phc->mp.media_out->ssrc_hash_out,
sh->attrs.transcoding ? true : false);
sh->attrs.transcoding || fixed_ssrc);

/* a fixed egress SSRC outlives the ingress SSRC, so carry the
* sequence numbering across a change of source */
if (fixed_ssrc && phc->mp.ssrc_out) {
struct ssrc_entry_call *so = phc->mp.ssrc_out;
uint32_t in_ssrc = ntohl(phc->mp.rtp->ssrc);
uint16_t seq = ntohs(phc->mp.rtp->seq_num);
if (so->fixed_in_ssrc_set && so->fixed_in_ssrc != in_ssrc) {
/* the egress sequence counter lives in memory shared with
* the kernel module, so it stays current even while the
* stream is offloaded and userspace sees no packets */
uint16_t last = atomic_get_na(&so->stats->ext_seq);
so->seq_diff = last + 1 - seq;
}
so->fixed_in_ssrc = in_ssrc;
so->fixed_in_ssrc_set = true;
}
}
else if (phc->rtcp && phc->mp.rtcp) {
unkern = __stream_ssrc_out(phc->out_srtp, phc->mp.rtcp->ssrc, phc->mp.ssrc_in,
&phc->mp.ssrc_out, &phc->mp.media_out->ssrc_hash_out,
sh->attrs.transcoding ? true : false);
sh->attrs.transcoding || fixed_ssrc);
}

if (unkern)
Expand Down
16 changes: 16 additions & 0 deletions docs/ng_control_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,18 @@ Spaces in each string may be replaced by hyphens.
negotiated are removed from forwarded RTP. Once set, the flag remains
in effect for the lifetime of the call.

* `fixed egress SSRC`

Pick an SSRC for each media at signalling time and use it for all RTP sent
towards that media, instead of passing through the SSRC that the other side
happens to be sending. The chosen values are returned in the response, and
they stay in place for the lifetime of the call, so a source that changes
its own SSRC mid-call is still forwarded under the same one, with the
sequence numbering carried across the change.

Intended for feeding media into a system that has to bind a receiver to an
SSRC up front, such as a WebRTC selective forwarding unit.

* `strict source`

Normally, *rtpengine* attempts to learn the correct endpoint address for every stream during
Expand Down Expand Up @@ -1821,6 +1833,10 @@ SDP body that the SIP proxy should insert into the SIP message. If `supports`
requested a supported extension, the response can also contain a `supported`
list.

With `fixed egress SSRC` set, the response also contains `egress SSRC`, a list with
one entry per `m=` section of the returned SDP, in the same order. Each entry gives
the `index` and `type` of the media along with the `SSRC` chosen for it.

Example response:

{ "result": "ok", "sdp": "v=0\r\no=..." }
Expand Down
3 changes: 3 additions & 0 deletions include/call.h
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,9 @@ struct call_media {
struct ssrc_hash ssrc_hash_in;
struct ssrc_hash ssrc_hash_out;

/* fixed SSRC to use for RTP sent towards this media, 0 if unset */
uint32_t fixed_egress_ssrc;

struct codec_store codecs;
struct codec_store offered_codecs;
sdp_attr_q generic_attributes; /* sdp_attr_new() */
Expand Down
4 changes: 3 additions & 1 deletion include/call_flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,9 @@ RTPE_NG_FLAGS_STR_CASE_HT_PARAMS
/* prevents double MoH holds */
moh_double_hold:1,
/* process RTP header extensions even if none were negotiated */
force_strip_extmap:1;
force_strip_extmap:1,
/* pick a fixed egress SSRC per media and report it */
fixed_egress_ssrc:1;
};


Expand Down
5 changes: 5 additions & 0 deletions include/ssrc.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ struct ssrc_entry_call {
uint32_t jitter, transit;
// output only
uint16_t seq_diff;

/* for a fixed egress SSRC: the last ingress SSRC seen, so a change of
* source can be spotted and the sequence numbering carried across it */
uint32_t fixed_in_ssrc;
bool fixed_in_ssrc_set;
};

struct ssrc_time_item {
Expand Down
8 changes: 7 additions & 1 deletion perl/NGCP/Rtpengine/AutoTest.pm
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ BEGIN {
our @EXPORT = qw(autotest_start new_call new_call_nc offer answer ft tt cid snd snd_no srtp_snd rtp rcv srtp_rcv rcv_no rcv_maybe
srtp_dec escape rtpm rtpmre reverse_tags new_ft new_tt crlf sdp_split rtpe_req offer_answer
autotest_init subscribe_request subscribe_answer publish create create_answer
use_json rtpe_raw_req);
use_json rtpe_raw_req last_resp);
};


my $last_response;
sub last_resp {
return $last_response;
}

my $rtpe_stdout;
my $rtpe_stderr;
my $rtpe_pid;
Expand Down Expand Up @@ -183,6 +188,7 @@ sub offer_answer {
$req->{'from-tag'} //= $ft;
$req->{sdp} = $sdp_in;
my $resp = rtpe_req($cmd, $name, $req);
$last_response = $resp;
return sdp_match($cmd, $name, $resp->{sdp}, $exp_sdp_out);
}
sub offer {
Expand Down
7 changes: 5 additions & 2 deletions t/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ include ../lib/common.Makefile
daemon-tests-measure-rtp daemon-tests-mos-legacy daemon-tests-mos-fullband daemon-tests-config-file \
daemon-tests-templ-def daemon-tests-templ-def-offer daemon-tests-t38 daemon-tests-evs-dtx \
daemon-tests-transform daemon-tests-http daemon-tests-heuristic daemon-tests-asymmetric \
daemon-tests-dtx-no-shift daemon-tests-rtcp daemon-tests-redis-subscribe daemon-tests-rtp-ext \
daemon-tests-dtx-no-shift daemon-tests-rtcp daemon-tests-redis-subscribe daemon-tests-rtp-ext daemon-tests-ssrc \
daemon-tests-bundle daemon-tests-dtls daemon-tests-rollback \
daemon-tests-rollback-redis \
daemon-tests-recording daemon-tests-create daemon-tests-alias \
Expand Down Expand Up @@ -139,7 +139,7 @@ daemon-tests: daemon-tests-main daemon-tests-jb daemon-tests-pubsub daemon-tests
daemon-tests-sdp-manipulations daemon-tests-sdes-manipulations \
daemon-tests-sdp-orig-replacements daemon-tests-moh daemon-tests-evs-dtx daemon-tests-transform \
daemon-tests-transcode-config daemon-tests-codec-prefs daemon-tests-http daemon-tests-heuristic \
daemon-tests-asymmetric daemon-tests-rtcp daemon-tests-redis-subscribe daemon-tests-rtp-ext \
daemon-tests-asymmetric daemon-tests-rtcp daemon-tests-redis-subscribe daemon-tests-rtp-ext daemon-tests-ssrc \
daemon-tests-bundle daemon-tests-dtls daemon-tests-rollback daemon-tests-rollback-redis \
daemon-tests-recording daemon-tests-create \
daemon-tests-dtx daemon-tests-dtx-cn daemon-tests-dtx-no-shift \
Expand Down Expand Up @@ -295,6 +295,9 @@ daemon-tests-rtcp: daemon-test-deps
daemon-tests-rtp-ext: daemon-test-deps
./auto-test-helper "$@" perl -I../perl auto-daemon-tests-rtp-ext.pl

daemon-tests-ssrc: daemon-test-deps
./auto-test-helper "$@" perl -I../perl auto-daemon-tests-ssrc.pl

daemon-tests-bundle: daemon-test-deps
./auto-test-helper "$@" perl -I../perl auto-daemon-tests-bundle.pl

Expand Down
97 changes: 97 additions & 0 deletions t/auto-daemon-tests-ssrc.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/perl

use strict;
use warnings;
use NGCP::Rtpengine::Test;
use NGCP::Rtpengine::AutoTest;
use Test::More;
use POSIX;


autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -n 2223 -f -L 7 -E --log-level-internals=7))
or die;


my ($sock_a, $sock_ax, $sock_b, $sock_bx, $port_a, $port_ax, $port_b, $port_bx, $resp, $ssrcs);


# the chosen egress SSRC is reported per media in the offer response

($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call(
[qw(198.51.100.1 7000)],
[qw(198.51.100.1 7001)],
[qw(198.51.100.3 7002)],
[qw(198.51.100.3 7003)],
);

($port_a, $port_ax) = offer('egress SSRC reported', { flags => ['fixed egress SSRC'] }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 7000 RTP/AVP 8
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP

$resp = last_resp();
$ssrcs = $resp->{'egress SSRC'};

is ref($ssrcs), 'ARRAY', 'egress SSRC list present in offer response';
is scalar(@{$ssrcs // []}), 1, 'one egress SSRC entry per m= section';
is $ssrcs->[0]->{index}, 1, 'egress SSRC entry carries media index';
is $ssrcs->[0]->{type}, 'audio', 'egress SSRC entry carries media type';
ok(($ssrcs->[0]->{SSRC} // 0) > 0, 'egress SSRC entry carries a non-zero SSRC');


my $egress = $ssrcs->[0]->{SSRC};

($port_b, $port_bx) = answer('egress SSRC reported', { }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 7002 RTP/AVP 8
c=IN IP4 198.51.100.3
a=rtpmap:8 PCMA/8000
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP


# RTP towards the answerer carries the reported SSRC, not the one the sender used

snd($sock_a, $port_b, rtp(8, 1000, 3000, 0x1234, "\x00" x 160));
rcv($sock_b, $port_a, rtpm(8, 1000, 3000, $egress, "\x00" x 160));


# the egress SSRC and the sequence numbering survive a change of ingress SSRC

snd($sock_a, $port_b, rtp(8, 1001, 3160, 0x1234, "\x00" x 160));
rcv($sock_b, $port_a, rtpm(8, 1001, 3160, $egress, "\x00" x 160));

snd($sock_a, $port_b, rtp(8, 5000, 90000, 0x4321, "\x00" x 160));
rcv($sock_b, $port_a, rtpm(8, 1002, 90000, $egress, "\x00" x 160));


#done_testing;NGCP::Rtpengine::AutoTest::terminate('f00');exit;
done_testing();
Loading