diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index 55acf6529187ef0a3981ec5d730bad1515f4c3f9..5f439163fe1760938f153ebe9d5a61cab8b72257 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -599,6 +599,10 @@ struct ast_rtp_engine { void (*available_formats)(struct ast_rtp_instance *instance, struct ast_format_cap *to_endpoint, struct ast_format_cap *to_asterisk, struct ast_format_cap *result); /*! Callback to send CNG */ int (*sendcng)(struct ast_rtp_instance *instance, int level); + /*! Callback to retrieve local SSRC */ + unsigned int (*ssrc_get)(struct ast_rtp_instance *instance); + /*! Callback to retrieve RTCP SDES CNAME */ + const char *(*cname_get)(struct ast_rtp_instance *instance); /*! Callback to pointer for optional ICE support */ struct ast_rtp_engine_ice *ice; /*! Callback to pointer for optional DTLS SRTP support */ @@ -2389,6 +2393,24 @@ time_t ast_rtp_instance_get_last_rx(const struct ast_rtp_instance *rtp); */ void ast_rtp_instance_set_last_rx(struct ast_rtp_instance *rtp, time_t time); +/*! + * \brief Retrieve the local SSRC value that we will be using + * + * \param rtp The RTP instance + * \return The SSRC value + */ +unsigned int ast_rtp_instance_get_ssrc(struct ast_rtp_instance *rtp); + +/*! + * \brief Retrieve the CNAME used in RTCP SDES items + * + * This is a pointer directly into the RTP struct, not a copy. + * + * \param rtp The RTP instance + * \return the CNAME + */ +const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp); + /*! \addtogroup StasisTopicsAndMessages * @{ */ diff --git a/include/asterisk/sdp_options.h b/include/asterisk/sdp_options.h index af694cd140eab8c72b9bd529cd87c38620d46814..3a1add37f7aea27ace0c9354669a569d93ed81a4 100644 --- a/include/asterisk/sdp_options.h +++ b/include/asterisk/sdp_options.h @@ -509,4 +509,23 @@ void ast_sdp_options_set_bind_udptl_to_media_address(struct ast_sdp_options *opt */ unsigned int ast_sdp_options_get_bind_udptl_to_media_address(const struct ast_sdp_options *options); +/*! + * \since 15.0.0 + * \brief Enable setting SSRC level attributes on SDPs + * + * \param options SDP Options + * \param ssrc Boolean indicating if SSRC attributes should be included in generated SDPs + */ +void ast_sdp_options_set_ssrc(struct ast_sdp_options *options, unsigned int ssrc); + +/*! + * \since 15.0.0 + * \brief Get SDP Options ssrc + * + * \param options SDP Options + * + * \returns Whether SSRC-level attributes will be added to our SDP. + */ +unsigned int ast_sdp_options_get_ssrc(const struct ast_sdp_options *options); + #endif /* _ASTERISK_SDP_OPTIONS_H */ diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 82298901d8d5c386d5ea53e351c6747265b607aa..9cfae09f4379c44c6d02656ef67f632eedd8e2d7 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -3340,3 +3340,29 @@ void ast_rtp_instance_set_last_rx(struct ast_rtp_instance *rtp, time_t time) { rtp->last_rx = time; } + +unsigned int ast_rtp_instance_get_ssrc(struct ast_rtp_instance *rtp) +{ + unsigned int ssrc = 0; + + ao2_lock(rtp); + if (rtp->engine->ssrc_get) { + ssrc = rtp->engine->ssrc_get(rtp); + } + ao2_unlock(rtp); + + return ssrc; +} + +const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp) +{ + const char *cname = ""; + + ao2_lock(rtp); + if (rtp->engine->cname_get) { + cname = rtp->engine->cname_get(rtp); + } + ao2_unlock(rtp); + + return cname; +} diff --git a/main/sdp_options.c b/main/sdp_options.c index ef056a1907512f147ef5834fc28629050a330819..3f25e432620d98e4a8e1b649bb680cb6ba9b6c5f 100644 --- a/main/sdp_options.c +++ b/main/sdp_options.c @@ -76,6 +76,7 @@ DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_video); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_ice, ice); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_impl, impl); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_encryption, encryption); +DEFINE_GETTERS_SETTERS_FOR(unsigned int, ssrc); static void set_defaults(struct ast_sdp_options *options) { diff --git a/main/sdp_private.h b/main/sdp_private.h index f2efe86e53003ce7a8ac0e7ccdba668d42b50d83..f80cefb5feaa1295fbbc2eaf0b72883c0246a6e5 100644 --- a/main/sdp_private.h +++ b/main/sdp_private.h @@ -43,6 +43,7 @@ struct ast_sdp_options { unsigned int g726_non_standard : 1; unsigned int locally_held : 1; unsigned int rtcp_mux: 1; + unsigned int ssrc: 1; }; struct { unsigned int tos_audio; diff --git a/main/sdp_state.c b/main/sdp_state.c index 5aee567d3b32994b6a60eff7fb66001eab9b9351..98803491227ceba647681455c7aa019e3eaca43f 100644 --- a/main/sdp_state.c +++ b/main/sdp_state.c @@ -1182,6 +1182,37 @@ void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, stream_state->t38_local_params = *params; } +/*! + * \brief Add SSRC-level attributes if appropriate. + * + * This function does nothing if the SDP options indicate not to add SSRC-level attributes. + * + * Currently, the only attribute added is cname, which is retrieved from the RTP instance. + * + * \param m_line The m_line on which to add the SSRC attributes + * \param options Options that indicate what, if any, SSRC attributes to add + * \param rtp RTP instance from which we get SSRC-level information + */ +static void add_ssrc_attributes(struct ast_sdp_m_line *m_line, const struct ast_sdp_options *options, + struct ast_rtp_instance *rtp) +{ + struct ast_sdp_a_line *a_line; + char attr_buffer[128]; + + if (!ast_sdp_options_get_ssrc(options)) { + return; + } + + snprintf(attr_buffer, sizeof(attr_buffer), "%u cname:%s", ast_rtp_instance_get_ssrc(rtp), + ast_rtp_instance_get_cname(rtp)); + + a_line = ast_sdp_a_alloc("ssrc", attr_buffer); + if (!a_line) { + return; + } + ast_sdp_m_add_a(m_line, a_line); +} + static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state, const struct ast_sdp_options *options, const struct sdp_state_capabilities *capabilities, int stream_index) { @@ -1312,6 +1343,8 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s return -1; } + add_ssrc_attributes(m_line, options, rtp); + if (ast_sdp_add_m(sdp, m_line)) { ast_sdp_m_free(m_line); return -1; diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index d0d79593991491fd3856e35f0d96c48d8879f58c..85e2425cbfde97207677beb0cc9003454fbf3064 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -474,6 +474,8 @@ static void ast_rtp_stun_request(struct ast_rtp_instance *instance, struct ast_s static void ast_rtp_stop(struct ast_rtp_instance *instance); static int ast_rtp_qos_set(struct ast_rtp_instance *instance, int tos, int cos, const char* desc); static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level); +static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance); +static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance); #ifdef HAVE_OPENSSL_SRTP static int ast_rtp_activate(struct ast_rtp_instance *instance); @@ -1903,6 +1905,8 @@ static struct ast_rtp_engine asterisk_rtp_engine = { .dtls = &ast_rtp_dtls, .activate = ast_rtp_activate, #endif + .ssrc_get = ast_rtp_get_ssrc, + .cname_get = ast_rtp_get_cname, }; #ifdef HAVE_OPENSSL_SRTP @@ -5815,6 +5819,27 @@ static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level) return res; } +/*! \pre instance is locked */ +static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + return rtp->ssrc; +} + +/*! \pre instance is locked */ +static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance) +{ + /* XXX + * + * Asterisk currently puts a zero-length CNAME value in RTCP SDES items, + * meaning our CNAME will always be an empty string. In future, should + * Asterisk actually start using meaningful CNAMEs, this function will + * need to return that instead of an empty string + */ + return ""; +} + #ifdef HAVE_OPENSSL_SRTP static void dtls_perform_setup(struct dtls_details *dtls) { diff --git a/tests/test_sdp.c b/tests/test_sdp.c index 33a6a2892adb8ddda4c9b1b7445194cb8d9a8991..a5d3710d899b644ca1ef3531c42fe6975cfa6e4d 100644 --- a/tests/test_sdp.c +++ b/tests/test_sdp.c @@ -29,6 +29,7 @@ #include "asterisk/format.h" #include "asterisk/format_cache.h" #include "asterisk/format_cap.h" +#include "asterisk/rtp_engine.h" static int validate_o_line(struct ast_test *test, const struct ast_sdp_o_line *o_line, const char *sdpowner, const char *address_type, const char *address) @@ -353,26 +354,56 @@ end: return res; } +static struct ast_sdp_options *sdp_options_common(void) +{ + struct ast_sdp_options *options; + + options = ast_sdp_options_alloc(); + if (!options) { + return NULL; + } + ast_sdp_options_set_media_address(options, "127.0.0.1"); + ast_sdp_options_set_sdpowner(options, "me"); + ast_sdp_options_set_rtp_engine(options, "asterisk"); + ast_sdp_options_set_impl(options, AST_SDP_IMPL_PJMEDIA); + + return options; +} + struct sdp_format { enum ast_media_type type; const char *formats; }; -static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats) +/*! + * \brief Common method to build an SDP state for a test. + * + * This uses the passed-in formats to create a stream topology, which is then used to create the SDP + * state. + * + * There is an optional test_options field you can use if your test has specific options you need to + * set. If your test does not require anything special, it can just pass NULL for this parameter. If + * you do pass in test_options, this function steals ownership of those options. + * + * \param num_streams The number of elements in the formats array. + * \param formats Array of media types and formats that will be in the state. + * \param test_options Optional SDP options. + */ +static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, struct ast_sdp_options *test_options) { struct ast_stream_topology *topology = NULL; struct ast_sdp_state *state = NULL; struct ast_sdp_options *options; int i; - options = ast_sdp_options_alloc(); - if (!options) { - goto end; + if (!test_options) { + options = sdp_options_common(); + if (!options) { + goto end; + } + } else { + options = test_options; } - ast_sdp_options_set_media_address(options, "127.0.0.1"); - ast_sdp_options_set_sdpowner(options, "me"); - ast_sdp_options_set_rtp_engine(options, "asterisk"); - ast_sdp_options_set_impl(options, AST_SDP_IMPL_PJMEDIA); topology = ast_stream_topology_alloc(); if (!topology) { @@ -436,7 +467,7 @@ AST_TEST_DEFINE(topology_to_sdp) break; } - sdp_state = build_sdp_state(ARRAY_LEN(formats), formats); + sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, NULL); if (!sdp_state) { goto end; } @@ -580,7 +611,7 @@ AST_TEST_DEFINE(sdp_to_topology) break; } - sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats); + sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, NULL); if (!sdp_state) { res = AST_TEST_FAIL; goto end; @@ -704,13 +735,13 @@ AST_TEST_DEFINE(sdp_merge_symmetric) break; } - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats); + sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); if (!sdp_state_offerer) { res = AST_TEST_FAIL; goto end; } - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats); + sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); if (!sdp_state_answerer) { res = AST_TEST_FAIL; goto end; @@ -782,13 +813,13 @@ AST_TEST_DEFINE(sdp_merge_crisscross) break; } - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats); + sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); if (!sdp_state_offerer) { res = AST_TEST_FAIL; goto end; } - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats); + sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); if (!sdp_state_answerer) { res = AST_TEST_FAIL; goto end; @@ -827,6 +858,111 @@ end: return res; } +static int validate_ssrc(struct ast_test *test, struct ast_sdp_m_line *m_line, + struct ast_rtp_instance *rtp) +{ + unsigned int ssrc; + const char *cname; + struct ast_sdp_a_line *a_line; + char attr_value[128]; + + ssrc = ast_rtp_instance_get_ssrc(rtp); + cname = ast_rtp_instance_get_cname(rtp); + + snprintf(attr_value, sizeof(attr_value), "%u cname:%s", ssrc, cname); + + a_line = ast_sdp_m_find_attribute(m_line, "ssrc", -1); + if (!a_line) { + ast_test_status_update(test, "Could not find 'ssrc' attribute\n"); + return -1; + } + + if (strcmp(a_line->value, attr_value)) { + ast_test_status_update(test, "SDP attribute '%s' did not match expected attribute '%s'\n", + a_line->value, attr_value); + return -1; + } + + return 0; +} + +AST_TEST_DEFINE(sdp_ssrc_attributes) +{ + enum ast_test_result_state res; + struct ast_sdp_state *test_state = NULL; + struct ast_sdp_options *options; + struct sdp_format formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, + }; + const struct ast_sdp *sdp; + struct ast_sdp_m_line *m_line; + struct ast_rtp_instance *rtp; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_ssrc_attributes"; + info->category = "/main/sdp/"; + info->summary = "Ensure SSRC-level attributes are added to local SDPs"; + info->description = + "An SDP is created and is instructed to include SSRC-level attributes.\n" + "This test ensures that the CNAME SSRC-level attribute is present and\n" + "that the values match what the RTP instance reports"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + res = AST_TEST_FAIL; + + options = sdp_options_common(); + if (!options) { + ast_test_status_update(test, "Failed to allocate SDP options\n"); + goto end; + } + ast_sdp_options_set_ssrc(options, 1); + + test_state = build_sdp_state(ARRAY_LEN(formats), formats, options); + if (!test_state) { + ast_test_status_update(test, "Failed to create SDP state\n"); + goto end; + } + + sdp = ast_sdp_state_get_local_sdp(test_state); + if (!sdp) { + ast_test_status_update(test, "Failed to get local SDP\n"); + goto end; + } + + /* Need a couple of sanity checks */ + if (ast_sdp_get_m_count(sdp) != ARRAY_LEN(formats)) { + ast_test_status_update(test, "SDP m count is %d instead of %zu\n", + ast_sdp_get_m_count(sdp), ARRAY_LEN(formats)); + goto end; + } + + m_line = ast_sdp_get_m(sdp, 0); + if (!m_line) { + ast_test_status_update(test, "Failed to get SDP m-line\n"); + goto end; + } + + rtp = ast_sdp_state_get_rtp_instance(test_state, 0); + if (!rtp) { + ast_test_status_update(test, "Failed to get the RTP instance\n"); + goto end; + } + + if (validate_ssrc(test, m_line, rtp)) { + goto end; + } + + res = AST_TEST_PASS; + +end: + ast_sdp_state_free(test_state); + return res; +} + static int unload_module(void) { AST_TEST_UNREGISTER(invalid_rtpmap); @@ -836,6 +972,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(sdp_to_topology); AST_TEST_UNREGISTER(sdp_merge_symmetric); AST_TEST_UNREGISTER(sdp_merge_crisscross); + AST_TEST_UNREGISTER(sdp_ssrc_attributes); return 0; } @@ -849,6 +986,7 @@ static int load_module(void) AST_TEST_REGISTER(sdp_to_topology); AST_TEST_REGISTER(sdp_merge_symmetric); AST_TEST_REGISTER(sdp_merge_crisscross); + AST_TEST_REGISTER(sdp_ssrc_attributes); return AST_MODULE_LOAD_SUCCESS; }