diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index 0e4468cdcd9df981c7d7a71fa696f7174374809f..3de980a520bc1d1598df6dbe4bab9481e837067a 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -792,8 +792,6 @@ static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast) return f; } - f->stream_num = callback_state->session->stream_num; - if (f->frametype != AST_FRAME_VOICE || callback_state->session != session->active_media_state->default_session[callback_state->session->type]) { return f; diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 2cd27d37f33812baedfdc4bd0a19a37ba9329c40..d499d5514bfca5fa97e835adb1573724b809ea4b 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -688,6 +688,8 @@ struct ast_sip_endpoint_media_configuration { unsigned int max_audio_streams; /*! Maximum number of video streams to offer/accept */ unsigned int max_video_streams; + /*! Use BUNDLE */ + unsigned int bundle; }; /*! diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index e298e1f32da575710cbd3582baaab132b645c748..eae29de046753d9825377d48636be83e9ed93911 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -99,6 +99,12 @@ struct ast_sip_session_media { ast_sip_session_media_write_cb write_callback; /*! \brief The stream number to place into any resulting frames */ int stream_num; + /*! \brief Media identifier for this stream (may be shared across multiple streams) */ + char *mid; + /*! \brief The bundle group the stream belongs to */ + int bundle_group; + /*! \brief Whether this stream is currently bundled or not */ + unsigned int bundled; }; /*! @@ -833,6 +839,19 @@ int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, str int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, ast_sip_session_media_write_cb callback); +/*! + * \brief Retrieve the underlying media session that is acting as transport for a media session + * \since 15.0.0 + * + * \param session The session + * \param session_media The media session to retrieve the transport for + * + * \note This operates on the pending media state + * + * \note This function is guaranteed to return non-NULL + */ +struct ast_sip_session_media *ast_sip_session_media_get_transport(struct ast_sip_session *session, struct ast_sip_session_media *session_media); + /*! \brief Determines whether the res_pjsip_session module is loaded */ #define CHECK_PJSIP_SESSION_MODULE_LOADED() \ do { \ diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index 5f439163fe1760938f153ebe9d5a61cab8b72257..20ae959e96ce2deef6b1ec42cda42aa9a2771ed2 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -603,6 +603,12 @@ struct ast_rtp_engine { 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 bundle an RTP instance to another */ + int (*bundle)(struct ast_rtp_instance *child, struct ast_rtp_instance *parent); + /*! Callback to set remote SSRC information */ + void (*set_remote_ssrc)(struct ast_rtp_instance *instance, unsigned int ssrc); + /*! Callback to set the stream identifier */ + void (*set_stream_num)(struct ast_rtp_instance *instance, int stream_num); /*! Callback to pointer for optional ICE support */ struct ast_rtp_engine_ice *ice; /*! Callback to pointer for optional DTLS SRTP support */ @@ -1506,6 +1512,20 @@ void ast_rtp_codecs_payload_formats(struct ast_rtp_codecs *codecs, struct ast_fo */ int ast_rtp_codecs_payload_code(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code); +/*! + * \brief Set a payload code for use with a specific Asterisk format + * + * \param codecs Codecs structure to manipulate + * \param code The payload code + * \param format Asterisk format + * + * \retval 0 Payload was set to the given format + * \retval -1 Payload was in use or could not be set + * + * \since 15.0.0 + */ +int ast_rtp_codecs_payload_set_rx(struct ast_rtp_codecs *codecs, int code, struct ast_format *format); + /*! * \brief Retrieve a tx mapped payload type based on whether it is an Asterisk format and the code * \since 14.0.0 @@ -2266,6 +2286,8 @@ int ast_rtp_instance_sendcng(struct ast_rtp_instance *instance, int level); * * \retval 0 Success * \retval non-zero Failure + * + * \note If no remote policy is provided any existing SRTP policies are left and the new local policy is added */ int ast_rtp_instance_add_srtp_policy(struct ast_rtp_instance *instance, struct ast_srtp_policy* remote_policy, struct ast_srtp_policy *local_policy, int rtcp); @@ -2411,6 +2433,36 @@ unsigned int ast_rtp_instance_get_ssrc(struct ast_rtp_instance *rtp); */ const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp); +/*! + * \brief Request that an RTP instance be bundled with another + * \since 15.0.0 + * + * \param child The child RTP instance + * \param parent The parent RTP instance the child should be bundled with + * + * \retval 0 success + * \retval -1 failure + */ +int ast_rtp_instance_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent); + +/*! + * \brief Set the remote SSRC for an RTP instance + * \since 15.0.0 + * + * \param rtp The RTP instance + * \param ssrc The remote SSRC + */ +void ast_rtp_instance_set_remote_ssrc(struct ast_rtp_instance *rtp, unsigned int ssrc); + +/*! + * \brief Set the stream number for an RTP instance + * \since 15.0.0 + * + * \param rtp The RTP instance + * \param stream_num The stream identifier number + */ +void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *instance, int stream_num); + /*! \addtogroup StasisTopicsAndMessages * @{ */ diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 9cfae09f4379c44c6d02656ef67f632eedd8e2d7..abd4b1fcfcaa9ddcfbd48a3830451a44190524a9 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -1495,21 +1495,24 @@ static int rtp_codecs_find_non_primary_dynamic_rx(struct ast_rtp_codecs *codecs) * \param asterisk_format Non-zero if the given Asterisk format is present * \param format Asterisk format to look for * \param code The format to look for + * \param explicit Require the provided code to be explicitly used * * \note It is assumed that static_RTP_PT_lock is at least read locked before calling. * * \retval Numerical payload type * \retval -1 if could not assign. */ -static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code) +static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code, int explicit) { - int payload; + int payload = code; struct ast_rtp_payload_type *new_type; - payload = find_static_payload_type(asterisk_format, format, code); + if (!explicit) { + payload = find_static_payload_type(asterisk_format, format, code); - if (payload < 0 && (!asterisk_format || ast_option_rtpusedynamic)) { - return payload; + if (payload < 0 && (!asterisk_format || ast_option_rtpusedynamic)) { + return payload; + } } new_type = rtp_payload_type_alloc(format, payload, code, 1); @@ -1525,9 +1528,9 @@ static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int * The payload type is a static assignment * or our default dynamic position is available. */ - rtp_codecs_payload_replace_rx(codecs, payload, new_type); - } else if (-1 < (payload = find_unused_payload(codecs)) - || -1 < (payload = rtp_codecs_find_non_primary_dynamic_rx(codecs))) { + rtp_codecs_payload_replace_rx(codecs, payload, new_type); + } else if (!explicit && (-1 < (payload = find_unused_payload(codecs)) + || -1 < (payload = rtp_codecs_find_non_primary_dynamic_rx(codecs)))) { /* * We found the first available empty dynamic position * or we found a mapping that should no longer be @@ -1535,6 +1538,11 @@ static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int */ new_type->payload = payload; rtp_codecs_payload_replace_rx(codecs, payload, new_type); + } else if (explicit) { + /* + * They explicitly requested this payload number be used but it couldn't be + */ + payload = -1; } else { /* * There are no empty or non-primary dynamic positions @@ -1595,13 +1603,18 @@ int ast_rtp_codecs_payload_code(struct ast_rtp_codecs *codecs, int asterisk_form if (payload < 0) { payload = rtp_codecs_assign_payload_code_rx(codecs, asterisk_format, format, - code); + code, 0); } ast_rwlock_unlock(&static_RTP_PT_lock); return payload; } +int ast_rtp_codecs_payload_set_rx(struct ast_rtp_codecs *codecs, int code, struct ast_format *format) +{ + return rtp_codecs_assign_payload_code_rx(codecs, 1, format, code, 1); +} + int ast_rtp_codecs_payload_code_tx(struct ast_rtp_codecs *codecs, int asterisk_format, const struct ast_format *format, int code) { struct ast_rtp_payload_type *type; @@ -2424,7 +2437,7 @@ int ast_rtp_instance_add_srtp_policy(struct ast_rtp_instance *instance, struct a if (!*srtp) { res = res_srtp->create(srtp, instance, remote_policy); - } else { + } else if (remote_policy) { res = res_srtp->replace(srtp, instance, remote_policy); } if (!res) { @@ -3366,3 +3379,38 @@ const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp) return cname; } + +int ast_rtp_instance_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent) +{ + int res = -1; + + if (child->engine != parent->engine) { + return -1; + } + + ao2_lock(child); + if (child->engine->bundle) { + res = child->engine->bundle(child, parent); + } + ao2_unlock(child); + + return res; +} + +void ast_rtp_instance_set_remote_ssrc(struct ast_rtp_instance *rtp, unsigned int ssrc) +{ + ao2_lock(rtp); + if (rtp->engine->set_remote_ssrc) { + rtp->engine->set_remote_ssrc(rtp, ssrc); + } + ao2_unlock(rtp); +} + +void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *rtp, int stream_num) +{ + ao2_lock(rtp); + if (rtp->engine->set_stream_num) { + rtp->engine->set_stream_num(rtp, stream_num); + } + ao2_unlock(rtp); +} \ No newline at end of file diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 0cf034374512a9f1219b8b6590e16febdfc0679c..1b546eff92c47a9b3accdced81c35cac5ef4ace3 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -995,6 +995,14 @@ streams allowed for the endpoint. </para></description> </configOption> + <configOption name="bundle" default="no"> + <synopsis>Enable RTP bundling</synopsis> + <description><para> + With this option enabled, Asterisk will attempt to negotiate the use of bundle. + If negotiated this will result in multiple RTP streams being carried over the same + underlying transport. Note that enabling bundle will also enable the rtcp_mux option. + </para></description> + </configOption> </configObject> <configObject name="auth"> <synopsis>Authentication type</synopsis> diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 372b01bc8dd1f8c5e8e61536b8fd6a666d8fd14f..d56ff5d23e12e499de79e7c3fa7f0c6bab4b010e 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -1332,6 +1332,10 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o return -1; } + if (endpoint->media.bundle) { + endpoint->media.rtcp_mux = 1; + } + return 0; } @@ -1954,6 +1958,7 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bundle", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.bundle)); if (ast_sip_initialize_sorcery_transport()) { ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index a49130868203a35c2ad63a81aa7377752a0f7de8..4ec811528807ad0958a9ebe4ba84ea95a75d5cf6 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -317,6 +317,7 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp static int set_caps(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + struct ast_sip_session_media *session_media_transport, const struct pjmedia_sdp_media *stream, int is_offer, struct ast_stream *asterisk_stream) { @@ -376,6 +377,24 @@ static int set_caps(struct ast_sip_session *session, ast_stream_set_formats(asterisk_stream, joint); + /* If this is a bundled stream then apply the payloads to RTP instance acting as transport to prevent conflicts */ + if (session_media_transport != session_media && session_media->bundled) { + int index; + + for (index = 0; index < ast_format_cap_count(joint); ++index) { + struct ast_format *format = ast_format_cap_get_format(joint, index); + int rtp_code; + + /* Ensure this payload is in the bundle group transport codecs, this purposely doesn't check the return value for + * things as the format is guaranteed to have a payload already. + */ + rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0); + ast_rtp_codecs_payload_set_rx(ast_rtp_instance_get_codecs(session_media_transport->rtp), rtp_code, format); + + ao2_ref(format, -1); + } + } + if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) { ast_channel_lock(session->channel); ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN); @@ -496,7 +515,8 @@ static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format * } /*! \brief Function which adds ICE attributes to a media stream */ -static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media) +static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media, + unsigned int include_candidates) { struct ast_rtp_engine_ice *ice; struct ao2_container *candidates; @@ -506,8 +526,7 @@ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_se struct ao2_iterator it_candidates; struct ast_rtp_engine_ice_candidate *candidate; - if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp)) || - !(candidates = ice->get_local_candidates(session_media->rtp))) { + if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp))) { return; } @@ -521,6 +540,15 @@ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_se media->attr[media->attr_count++] = attr; } + if (!include_candidates) { + return; + } + + candidates = ice->get_local_candidates(session_media->rtp); + if (!candidates) { + return; + } + it_candidates = ao2_iterator_init(candidates, 0); for (; (candidate = ao2_iterator_next(&it_candidates)); ao2_ref(candidate, -1)) { struct ast_str *attr_candidate = ast_str_create(128); @@ -940,6 +968,63 @@ static void set_ice_components(struct ast_sip_session *session, struct ast_sip_s } } +/*! \brief Function which adds ssrc attributes to a media stream */ +static void add_ssrc_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media) +{ + pj_str_t stmp; + pjmedia_sdp_attr *attr; + char tmp[128]; + + if (!session->endpoint->media.bundle || session_media->bundle_group == -1) { + return; + } + + snprintf(tmp, sizeof(tmp), "%u cname:%s", ast_rtp_instance_get_ssrc(session_media->rtp), ast_rtp_instance_get_cname(session_media->rtp)); + attr = pjmedia_sdp_attr_create(pool, "ssrc", pj_cstr(&stmp, tmp)); + media->attr[media->attr_count++] = attr; +} + +/*! \brief Function which processes ssrc attributes in a stream */ +static void process_ssrc_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + const struct pjmedia_sdp_media *remote_stream) +{ + int index; + + if (!session->endpoint->media.bundle) { + return; + } + + for (index = 0; index < remote_stream->attr_count; ++index) { + pjmedia_sdp_attr *attr = remote_stream->attr[index]; + char attr_value[pj_strlen(&attr->value) + 1]; + char *ssrc_attribute_name, *ssrc_attribute_value = NULL; + unsigned int ssrc; + + /* We only care about ssrc attributes */ + if (pj_strcmp2(&attr->name, "ssrc")) { + continue; + } + + ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value)); + + if ((ssrc_attribute_name = strchr(attr_value, ' '))) { + /* This has an actual attribute */ + *ssrc_attribute_name++ = '\0'; + ssrc_attribute_value = strchr(ssrc_attribute_name, ':'); + if (ssrc_attribute_value) { + /* Values are actually optional according to the spec */ + *ssrc_attribute_value++ = '\0'; + } + } + + if (sscanf(attr_value, "%30u", &ssrc) < 1) { + continue; + } + + ast_rtp_instance_set_remote_ssrc(session_media->rtp, ssrc); + } +} + /*! \brief Function which negotiates an incoming media stream */ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp, @@ -948,6 +1033,7 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, char host[NI_MAXHOST]; RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); pjmedia_sdp_media *stream = sdp->media[index]; + struct ast_sip_session_media *session_media_transport; enum ast_media_type media_type = session_media->type; enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE; int res; @@ -981,38 +1067,51 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, return -1; } - session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL); - set_ice_components(session, session_media); + process_ssrc_attributes(session, session_media, stream); - enable_rtcp(session, session_media, stream); + session_media_transport = ast_sip_session_media_get_transport(session, session_media); - res = setup_media_encryption(session, session_media, sdp, stream); - if (res) { - if (!session->endpoint->media.rtp.encryption_optimistic || - !pj_strncmp2(&stream->desc.transport, "RTP/SAVP", 8)) { - /* If optimistic encryption is disabled and crypto should have been enabled - * but was not this session must fail. This must also fail if crypto was - * required in the offer but could not be set up. - */ - return -1; + if (session_media_transport == session_media || !session_media->bundled) { + /* If this media session is carrying actual traffic then set up those aspects */ + session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL); + set_ice_components(session, session_media); + + enable_rtcp(session, session_media, stream); + + res = setup_media_encryption(session, session_media, sdp, stream); + if (res) { + if (!session->endpoint->media.rtp.encryption_optimistic || + !pj_strncmp2(&stream->desc.transport, "RTP/SAVP", 8)) { + /* If optimistic encryption is disabled and crypto should have been enabled + * but was not this session must fail. This must also fail if crypto was + * required in the offer but could not be set up. + */ + return -1; + } + /* There is no encryption, sad. */ + session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE; } - /* There is no encryption, sad. */ - session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE; - } - /* If we've been explicitly configured to use the received transport OR if - * encryption is on and crypto is present use the received transport. - * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending - * on the configuration of the remote endpoint (optimistic themselves or mandatory). - */ - if ((session->endpoint->media.rtp.use_received_transport) || - ((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) { - pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport); - } + /* If we've been explicitly configured to use the received transport OR if + * encryption is on and crypto is present use the received transport. + * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending + * on the configuration of the remote endpoint (optimistic themselves or mandatory). + */ + if ((session->endpoint->media.rtp.use_received_transport) || + ((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) { + pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport); + } + } else { + /* This is bundled with another session, so mark it as such */ + ast_rtp_instance_bundle(session_media->rtp, session_media_transport->rtp); - if (set_caps(session, session_media, stream, 1, asterisk_stream)) { + enable_rtcp(session, session_media, stream); + } + + if (set_caps(session, session_media, session_media_transport, stream, 1, asterisk_stream)) { return 0; } + return 1; } @@ -1032,6 +1131,7 @@ static int add_crypto_to_stream(struct ast_sip_session *session, static const pj_str_t STR_PASSIVE = { "passive", 7 }; static const pj_str_t STR_ACTPASS = { "actpass", 7 }; static const pj_str_t STR_HOLDCONN = { "holdconn", 8 }; + enum ast_rtp_dtls_setup setup; switch (session_media->encryption) { case AST_SIP_MEDIA_ENCRYPT_NONE: @@ -1085,7 +1185,16 @@ static int add_crypto_to_stream(struct ast_sip_session *session, break; } - switch (dtls->get_setup(session_media->rtp)) { + /* If this is an answer we need to use our current state, if it's an offer we need to use + * the configured value. + */ + if (pjmedia_sdp_neg_get_state(session->inv_session->neg) != PJMEDIA_SDP_NEG_STATE_DONE) { + setup = dtls->get_setup(session_media->rtp); + } else { + setup = session->endpoint->media.rtp.dtls_cfg.default_setup; + } + + switch (setup) { case AST_RTP_DTLS_SETUP_ACTIVE: attr = pjmedia_sdp_attr_create(pool, "setup", &STR_ACTIVE); media->attr[media->attr_count++] = attr; @@ -1100,7 +1209,6 @@ static int add_crypto_to_stream(struct ast_sip_session *session, break; case AST_RTP_DTLS_SETUP_HOLDCONN: attr = pjmedia_sdp_attr_create(pool, "setup", &STR_HOLDCONN); - media->attr[media->attr_count++] = attr; break; default: break; @@ -1152,6 +1260,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as int rtp_code; RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup); enum ast_media_type media_type = session_media->type; + struct ast_sip_session_media *session_media_transport; int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) && ast_format_cap_count(session->direct_media_cap); @@ -1195,68 +1304,106 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as return -1; } - set_ice_components(session, session_media); - enable_rtcp(session, session_media, NULL); + /* If this stream has not been bundled already it is new and we need to ensure there is no SSRC conflict */ + if (session_media->bundle_group != -1 && !session_media->bundled) { + for (index = 0; index < sdp->media_count; ++index) { + struct ast_sip_session_media *other_session_media; - /* Crypto has to be added before setting the media transport so that SRTP is properly - * set up according to the configuration. This ends up changing the media transport. - */ - if (add_crypto_to_stream(session, session_media, pool, media)) { - return -1; - } + other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index); + if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) { + continue; + } - if (pj_strlen(&session_media->transport)) { - /* If a transport has already been specified use it */ - media->desc.transport = session_media->transport; - } else { - media->desc.transport = pj_str(ast_sdp_get_rtp_profile( - /* Optimistic encryption places crypto in the normal RTP/AVP profile */ - !session->endpoint->media.rtp.encryption_optimistic && - (session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES), - session_media->rtp, session->endpoint->media.rtp.use_avpf, - session->endpoint->media.rtp.force_avp)); + if (ast_rtp_instance_get_ssrc(session_media->rtp) == ast_rtp_instance_get_ssrc(other_session_media->rtp)) { + ast_rtp_instance_change_source(session_media->rtp); + /* Start the conflict check over again */ + index = -1; + continue; + } + } } - media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)); - if (!media->conn) { - return -1; - } + session_media_transport = ast_sip_session_media_get_transport(session, session_media); - /* Add connection level details */ - if (direct_media_enabled) { - hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR); - } else if (ast_strlen_zero(session->endpoint->media.address)) { - hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()); - } else { - hostip = session->endpoint->media.address; - } + if (session_media_transport == session_media || !session_media->bundled) { + set_ice_components(session, session_media); + enable_rtcp(session, session_media, NULL); - if (ast_strlen_zero(hostip)) { - ast_log(LOG_ERROR, "No local host IP available for stream %s\n", - ast_codec_media_type2str(session_media->type)); - return -1; - } + /* Crypto has to be added before setting the media transport so that SRTP is properly + * set up according to the configuration. This ends up changing the media transport. + */ + if (add_crypto_to_stream(session, session_media, pool, media)) { + return -1; + } - media->conn->net_type = STR_IN; - /* Assume that the connection will use IPv4 until proven otherwise */ - media->conn->addr_type = STR_IP4; - pj_strdup2(pool, &media->conn->addr, hostip); + if (pj_strlen(&session_media->transport)) { + /* If a transport has already been specified use it */ + media->desc.transport = session_media->transport; + } else { + media->desc.transport = pj_str(ast_sdp_get_rtp_profile( + /* Optimistic encryption places crypto in the normal RTP/AVP profile */ + !session->endpoint->media.rtp.encryption_optimistic && + (session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES), + session_media->rtp, session->endpoint->media.rtp.use_avpf, + session->endpoint->media.rtp.force_avp)); + } - if (!ast_strlen_zero(session->endpoint->media.address)) { - pj_sockaddr ip; + media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)); + if (!media->conn) { + return -1; + } - if ((pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &media->conn->addr, &ip) == PJ_SUCCESS) && - (ip.addr.sa_family == pj_AF_INET6())) { - media->conn->addr_type = STR_IP6; + /* Add connection level details */ + if (direct_media_enabled) { + hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR); + } else if (ast_strlen_zero(session->endpoint->media.address)) { + hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()); + } else { + hostip = session->endpoint->media.address; } - } - /* Add ICE attributes and candidates */ - add_ice_to_stream(session, session_media, pool, media); + if (ast_strlen_zero(hostip)) { + ast_log(LOG_ERROR, "No local host IP available for stream %s\n", + ast_codec_media_type2str(session_media->type)); + return -1; + } + + media->conn->net_type = STR_IN; + /* Assume that the connection will use IPv4 until proven otherwise */ + media->conn->addr_type = STR_IP4; + pj_strdup2(pool, &media->conn->addr, hostip); + + if (!ast_strlen_zero(session->endpoint->media.address)) { + pj_sockaddr ip; + + if ((pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &media->conn->addr, &ip) == PJ_SUCCESS) && + (ip.addr.sa_family == pj_AF_INET6())) { + media->conn->addr_type = STR_IP6; + } + } + + /* Add ICE attributes and candidates */ + add_ice_to_stream(session, session_media, pool, media, 1); + + ast_rtp_instance_get_local_address(session_media->rtp, &addr); + media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr); + media->desc.port_count = 1; + } else { + pjmedia_sdp_media *bundle_group_stream = sdp->media[session_media_transport->stream_num]; + + /* As this is in a bundle group it shares the same details as the group instance */ + media->desc.transport = bundle_group_stream->desc.transport; + media->conn = bundle_group_stream->conn; + media->desc.port = bundle_group_stream->desc.port; + + if (add_crypto_to_stream(session, session_media_transport, pool, media)) { + return -1; + } - ast_rtp_instance_get_local_address(session_media->rtp, &addr); - media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr); - media->desc.port_count = 1; + add_ice_to_stream(session, session_media_transport, pool, media, 0); + + enable_rtcp(session, session_media, NULL); + } if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", @@ -1278,10 +1425,23 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as continue; } - if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) { - ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format)); - ao2_ref(format, -1); - continue; + /* If this stream is not a transport we need to use the transport codecs structure for payload management to prevent + * conflicts. + */ + if (session_media_transport != session_media) { + if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media_transport->rtp), 1, format, 0)) == -1) { + ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format)); + ao2_ref(format, -1); + continue; + } + /* Our instance has to match the payload number though */ + ast_rtp_codecs_payload_set_rx(ast_rtp_instance_get_codecs(session_media->rtp), rtp_code, format); + } else { + if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) { + ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format)); + ao2_ref(format, -1); + continue; + } } if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) { @@ -1332,6 +1492,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as } } + /* If no formats were actually added to the media stream don't add it to the SDP */ if (!media->desc.fmt_count) { return 1; @@ -1365,6 +1526,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); } + add_ssrc_to_stream(session, session_media, pool, media); + /* Add the media stream to the SDP */ sdp->media[sdp->media_count++] = media; @@ -1425,6 +1588,7 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, enum ast_media_type media_type = session_media->type; char host[NI_MAXHOST]; int res; + struct ast_sip_session_media *session_media_transport; if (!session->channel) { return 1; @@ -1441,48 +1605,60 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, return -1; } - session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL); - set_ice_components(session, session_media); + process_ssrc_attributes(session, session_media, remote_stream); - enable_rtcp(session, session_media, remote_stream); + session_media_transport = ast_sip_session_media_get_transport(session, session_media); - res = setup_media_encryption(session, session_media, remote, remote_stream); - if (!session->endpoint->media.rtp.encryption_optimistic && res) { - /* If optimistic encryption is disabled and crypto should have been enabled but was not - * this session must fail. - */ - return -1; - } + if (session_media_transport == session_media || !session_media->bundled) { + session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL); + set_ice_components(session, session_media); - if (!remote_stream->conn && !remote->conn) { - return 1; - } + enable_rtcp(session, session_media, remote_stream); - ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host)); + res = setup_media_encryption(session, session_media, remote, remote_stream); + if (!session->endpoint->media.rtp.encryption_optimistic && res) { + /* If optimistic encryption is disabled and crypto should have been enabled but was not + * this session must fail. + */ + return -1; + } - /* Ensure that the address provided is valid */ - if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) { - /* The provided host was actually invalid so we error out this negotiation */ - return -1; - } + if (!remote_stream->conn && !remote->conn) { + return 1; + } - /* Apply connection information to the RTP instance */ - ast_sockaddr_set_port(addrs, remote_stream->desc.port); - ast_rtp_instance_set_remote_address(session_media->rtp, addrs); - if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) { - return 1; - } + ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host)); - ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback); - ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0), - media_session_rtp_read_callback); - if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) { - ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1), - media_session_rtcp_read_callback); + /* Ensure that the address provided is valid */ + if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) { + /* The provided host was actually invalid so we error out this negotiation */ + return -1; + } + + /* Apply connection information to the RTP instance */ + ast_sockaddr_set_port(addrs, remote_stream->desc.port); + ast_rtp_instance_set_remote_address(session_media->rtp, addrs); + + ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback); + ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0), + media_session_rtp_read_callback); + if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) { + ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1), + media_session_rtcp_read_callback); + } + + /* If ICE support is enabled find all the needed attributes */ + process_ice_attributes(session, session_media, remote, remote_stream); + } else { + /* This is bundled with another session, so mark it as such */ + ast_rtp_instance_bundle(session_media->rtp, session_media_transport->rtp); + ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback); + enable_rtcp(session, session_media, remote_stream); } - /* If ICE support is enabled find all the needed attributes */ - process_ice_attributes(session, session_media, remote, remote_stream); + if (set_caps(session, session_media, session_media_transport, remote_stream, 0, asterisk_stream)) { + return 1; + } /* Set the channel uniqueid on the RTP instance now that it is becoming active */ ast_channel_lock(session->channel); @@ -1490,6 +1666,7 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, ast_channel_unlock(session->channel); /* Ensure the RTP instance is active */ + ast_rtp_instance_set_stream_num(session_media->rtp, ast_stream_get_position(asterisk_stream)); ast_rtp_instance_activate(session_media->rtp); /* audio stream handles music on hold */ diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index ecda4990132bff2d77eb9e4fbea1dbb44417ddaf..315db6df5db1c30ded0b08fdd61fbe022c772b66 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -324,6 +324,28 @@ int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, st return 0; } +struct ast_sip_session_media *ast_sip_session_media_get_transport(struct ast_sip_session *session, struct ast_sip_session_media *session_media) +{ + int index; + + if (!session->endpoint->media.bundle || ast_strlen_zero(session_media->mid)) { + return session_media; + } + + for (index = 0; index < AST_VECTOR_SIZE(&session->pending_media_state->sessions); ++index) { + struct ast_sip_session_media *bundle_group_session_media; + + bundle_group_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index); + + /* The first session which is in the bundle group is considered the authoritative session for transport */ + if (bundle_group_session_media->bundle_group == session_media->bundle_group) { + return bundle_group_session_media; + } + } + + return session_media; +} + /*! * \brief Set an SDP stream handler for a corresponding session media. * @@ -371,6 +393,8 @@ static void session_media_dtor(void *obj) if (session_media->srtp) { ast_sdp_srtp_destroy(session_media->srtp); } + + ast_free(session_media->mid); } struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session, @@ -408,13 +432,25 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses session_media->timeout_sched_id = -1; session_media->type = type; session_media->stream_num = position; + + if (session->endpoint->media.bundle) { + /* This is a new stream so create a new mid based on media type and position, which makes it unique. + * If this is the result of an offer the mid will just end up getting replaced. + */ + if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) { + ao2_ref(session_media, -1); + return NULL; + } + session_media->bundle_group = 0; + } else { + session_media->bundle_group = -1; + } } AST_VECTOR_REPLACE(&media_state->sessions, position, session_media); /* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */ - if (!media_state->default_session[type] && - ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) { + if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) { media_state->default_session[type] = session_media; } @@ -441,6 +477,78 @@ static int is_stream_limitation_reached(enum ast_media_type type, const struct a } } +static int get_mid_bundle_group(const pjmedia_sdp_session *sdp, const char *mid) +{ + int bundle_group = 0; + int index; + + for (index = 0; index < sdp->attr_count; ++index) { + pjmedia_sdp_attr *attr = sdp->attr[index]; + char value[pj_strlen(&attr->value) + 1], *mids = value, *attr_mid; + + if (pj_strcmp2(&attr->name, "group") || pj_strncmp2(&attr->value, "BUNDLE", 6)) { + continue; + } + + ast_copy_pj_str(value, &attr->value, sizeof(value)); + + /* Skip the BUNDLE at the front */ + mids += 7; + + while ((attr_mid = strsep(&mids, " "))) { + if (!strcmp(attr_mid, mid)) { + /* The ordering of attributes determines our internal identification of the bundle group based on number, + * with -1 being not in a bundle group. Since this is only exposed internally for response purposes it's + * actually even fine if things move around. + */ + return bundle_group; + } + } + + bundle_group++; + } + + return -1; +} + +static int set_mid_and_bundle_group(struct ast_sip_session *session, + struct ast_sip_session_media *session_media, + const pjmedia_sdp_session *sdp, + const struct pjmedia_sdp_media *stream) +{ + pjmedia_sdp_attr *attr; + + if (!session->endpoint->media.bundle) { + return 0; + } + + /* By default on an incoming negotiation we assume no mid and bundle group is present */ + ast_free(session_media->mid); + session_media->mid = NULL; + session_media->bundle_group = -1; + session_media->bundled = 0; + + /* Grab the media identifier for the stream */ + attr = pjmedia_sdp_media_find_attr2(stream, "mid", NULL); + if (!attr) { + return 0; + } + + session_media->mid = ast_calloc(1, attr->value.slen + 1); + if (!session_media->mid) { + return 0; + } + ast_copy_pj_str(session_media->mid, &attr->value, attr->value.slen + 1); + + /* Determine what bundle group this is part of */ + session_media->bundle_group = get_mid_bundle_group(sdp, session_media->mid); + + /* If this is actually part of a bundle group then the other side requested or accepted the bundle request */ + session_media->bundled = session_media->bundle_group != -1; + + return 0; +} + static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp) { int i; @@ -497,9 +605,13 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n", ast_codec_media_type2str(type), i); ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + session_media->bundle_group = -1; + session_media->bundled = 0; continue; } + set_mid_and_bundle_group(session, session_media, sdp, remote_stream); + if (session_media->handler) { handler = session_media->handler; ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n", @@ -589,6 +701,8 @@ static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *ses /* We need a null-terminated version of the media string */ ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media)); + set_mid_and_bundle_group(session, session_media, remote, remote->media[index]); + handler = session_media->handler; if (handler) { ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", @@ -3443,6 +3557,82 @@ static int add_sdp_streams(struct ast_sip_session_media *session_media, return 0; } +/*! \brief Bundle group building structure */ +struct sip_session_media_bundle_group { + /*! \brief The media identifiers in this bundle group */ + char *mids[PJMEDIA_MAX_SDP_MEDIA]; + /*! \brief SDP attribute string */ + struct ast_str *attr_string; +}; + +static int add_bundle_groups(struct ast_sip_session *session, pj_pool_t *pool, pjmedia_sdp_session *answer) +{ + pj_str_t stmp; + pjmedia_sdp_attr *attr; + struct sip_session_media_bundle_group bundle_groups[PJMEDIA_MAX_SDP_MEDIA]; + int index, mid_id; + struct sip_session_media_bundle_group *bundle_group; + + if (!session->endpoint->media.bundle) { + return 0; + } + + memset(bundle_groups, 0, sizeof(bundle_groups)); + + attr = pjmedia_sdp_attr_create(pool, "msid-semantic", pj_cstr(&stmp, "WMS *")); + pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr); + + /* Build the bundle group layout so we can then add it to the SDP */ + for (index = 0; index < AST_VECTOR_SIZE(&session->pending_media_state->sessions); ++index) { + struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index); + + /* If this stream is not part of a bundle group we can't add it */ + if (session_media->bundle_group == -1) { + continue; + } + + bundle_group = &bundle_groups[session_media->bundle_group]; + + /* If this is the first mid then we need to allocate the attribute string and place BUNDLE in front */ + if (!bundle_group->mids[0]) { + bundle_group->mids[0] = session_media->mid; + bundle_group->attr_string = ast_str_create(64); + if (!bundle_group->attr_string) { + continue; + } + + ast_str_set(&bundle_group->attr_string, -1, "BUNDLE %s", session_media->mid); + continue; + } + + for (mid_id = 1; mid_id < PJMEDIA_MAX_SDP_MEDIA; ++mid_id) { + if (!bundle_group->mids[mid_id]) { + bundle_group->mids[mid_id] = session_media->mid; + ast_str_append(&bundle_group->attr_string, -1, " %s", session_media->mid); + break; + } else if (!strcmp(bundle_group->mids[mid_id], session_media->mid)) { + break; + } + } + } + + /* Add all bundle groups that have mids to the SDP */ + for (index = 0; index < PJMEDIA_MAX_SDP_MEDIA; ++index) { + bundle_group = &bundle_groups[index]; + + if (!bundle_group->attr_string) { + continue; + } + + attr = pjmedia_sdp_attr_create(pool, "group", pj_cstr(&stmp, ast_str_buffer(bundle_group->attr_string))); + pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr); + + ast_free(bundle_group->attr_string); + } + + return 0; +} + static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer) { static const pj_str_t STR_IN = { "IN", 2 }; @@ -3485,6 +3675,7 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) { struct ast_sip_session_media *session_media; struct ast_stream *stream; + unsigned int streams = local->media_count; /* This code does not enforce any maximum stream count limitations as that is done on either * the handling of an incoming SDP offer or on the handling of a session refresh. @@ -3501,12 +3692,30 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru return NULL; } + /* If a stream was actually added then add any additional details */ + if (streams != local->media_count) { + pjmedia_sdp_media *media = local->media[streams]; + pj_str_t stmp; + pjmedia_sdp_attr *attr; + + /* Add the media identifier if present */ + if (!ast_strlen_zero(session_media->mid)) { + attr = pjmedia_sdp_attr_create(inv->pool_prov, "mid", pj_cstr(&stmp, session_media->mid)); + pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); + } + } + /* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */ if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) { break; } } + /* Add any bundle groups that are present on the media state */ + if (add_bundle_groups(session, inv->pool_prov, local)) { + return NULL; + } + /* Use the connection details of an available media if possible for SDP level */ for (stream = 0; stream < local->media_count; stream++) { if (!local->media[stream]->conn) { diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c index a032bb12fb43a82e3d626dd37da5acf002bf30b9..877d48fb6d41c3ce1ab21ab4b4b7a1f48db3ed9c 100644 --- a/res/res_pjsip_t38.c +++ b/res/res_pjsip_t38.c @@ -880,11 +880,20 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as static struct ast_frame *media_session_udptl_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media) { + struct ast_frame *frame; + if (!session_media->udptl) { return &ast_null_frame; } - return ast_udptl_read(session_media->udptl); + frame = ast_udptl_read(session_media->udptl); + if (!frame) { + return NULL; + } + + frame->stream_num = session_media->stream_num; + + return frame; } static int media_session_udptl_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame) diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index 01dfe76f29d2ef40dbe441ed5cafa20d4a3ef10a..4bfbf9b087e9a552edbd0a53d927791e2c29b98e 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -68,6 +68,7 @@ #include "asterisk/module.h" #include "asterisk/rtp_engine.h" #include "asterisk/smoother.h" +#include "asterisk/uuid.h" #include "asterisk/test.h" #define MAX_TIMESTAMP_SKEW 640 @@ -238,6 +239,14 @@ struct ice_wrap { }; #endif +/*! \brief Structure used for mapping an incoming SSRC to an RTP instance */ +struct rtp_ssrc_mapping { + /*! \brief The received SSRC */ + unsigned int ssrc; + /*! \brief The RTP instance this SSRC belongs to*/ + struct ast_rtp_instance *instance; +}; + /*! \brief RTP session description */ struct ast_rtp { int s; @@ -245,6 +254,7 @@ struct ast_rtp { struct ast_frame f; unsigned char rawdata[8192 + AST_FRIENDLY_OFFSET]; unsigned int ssrc; /*!< Synchronization source, RFC 3550, page 10. */ + char cname[AST_UUID_STR_LEN]; /*!< Our local CNAME */ unsigned int themssrc; /*!< Their SSRC */ unsigned int rxssrc; unsigned int lastts; @@ -301,6 +311,11 @@ struct ast_rtp { struct ast_rtcp *rtcp; struct ast_rtp *bridged; /*!< Who we are Packet bridged to */ + struct ast_rtp_instance *bundled; /*!< The RTP instance we are bundled to */ + int stream_num; /*!< Stream num for this RTP instance */ + AST_VECTOR(, struct rtp_ssrc_mapping) ssrc_mapping; /*!< Mappings of SSRC to RTP instances */ + struct ast_sockaddr bind_address; /*!< Requested bind address for the sockets */ + enum strict_rtp_state strict_rtp_state; /*!< Current state that strict RTP protection is in */ struct ast_sockaddr strict_rtp_address; /*!< Remote address information for strict RTP purposes */ @@ -477,6 +492,9 @@ static int ast_rtp_qos_set(struct ast_rtp_instance *instance, int tos, int cos, 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); +static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc); +static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num); +static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent); #ifdef HAVE_OPENSSL_SRTP static int ast_rtp_activate(struct ast_rtp_instance *instance); @@ -1907,6 +1925,9 @@ static struct ast_rtp_engine asterisk_rtp_engine = { #endif .ssrc_get = ast_rtp_get_ssrc, .cname_get = ast_rtp_get_cname, + .set_remote_ssrc = ast_rtp_set_remote_ssrc, + .set_stream_num = ast_rtp_set_stream_num, + .bundle = ast_rtp_bundle, }; #ifdef HAVE_OPENSSL_SRTP @@ -1943,6 +1964,23 @@ static void dtls_perform_handshake(struct ast_rtp_instance *instance, struct dtl } #endif +#ifdef HAVE_OPENSSL_SRTP +static void dtls_perform_setup(struct dtls_details *dtls) +{ + if (!dtls->ssl || !SSL_is_init_finished(dtls->ssl)) { + return; + } + + SSL_clear(dtls->ssl); + if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) { + SSL_set_accept_state(dtls->ssl); + } else { + SSL_set_connect_state(dtls->ssl); + } + dtls->connection = AST_RTP_DTLS_CONNECTION_NEW; +} +#endif + #ifdef HAVE_PJPROJECT static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq); @@ -1971,9 +2009,12 @@ static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status) } #ifdef HAVE_OPENSSL_SRTP + + dtls_perform_setup(&rtp->dtls); dtls_perform_handshake(instance, &rtp->dtls, 0); if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) { + dtls_perform_setup(&rtp->rtcp->dtls); dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1); } #endif @@ -2241,59 +2282,14 @@ static int dtls_srtp_renegotiate(const void *data) return 0; } -static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp) +static int dtls_srtp_add_local_ssrc(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp, unsigned int ssrc, int set_remote_policy) { unsigned char material[SRTP_MASTER_LEN * 2]; unsigned char *local_key, *local_salt, *remote_key, *remote_salt; struct ast_srtp_policy *local_policy, *remote_policy = NULL; - struct ast_rtp_instance_stats stats = { 0, }; int res = -1; struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls; - /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ - if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) { - X509 *certificate; - - if (!(certificate = SSL_get_peer_certificate(dtls->ssl))) { - ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance); - return -1; - } - - /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ - if (rtp->remote_fingerprint[0]) { - const EVP_MD *type; - unsigned char fingerprint[EVP_MAX_MD_SIZE]; - unsigned int size; - - if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) { - type = EVP_sha1(); - } else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) { - type = EVP_sha256(); - } else { - ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance); - return -1; - } - - if (!X509_digest(certificate, type, fingerprint, &size) || - !size || - memcmp(fingerprint, rtp->remote_fingerprint, size)) { - X509_free(certificate); - ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n", - instance); - return -1; - } - } - - X509_free(certificate); - } - - /* Ensure that certificate verification was successful */ - if ((rtp->dtls_verify & AST_RTP_DTLS_VERIFY_CERTIFICATE) && SSL_get_verify_result(dtls->ssl) != X509_V_OK) { - ast_log(LOG_WARNING, "Peer certificate on RTP instance '%p' failed verification test\n", - instance); - return -1; - } - /* Produce key information and set up SRTP */ if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n", @@ -2328,41 +2324,31 @@ static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct as goto error; } - if (ast_rtp_instance_get_stats(instance, &stats, AST_RTP_INSTANCE_STAT_LOCAL_SSRC)) { - goto error; - } + res_srtp_policy->set_ssrc(local_policy, ssrc, 0); - res_srtp_policy->set_ssrc(local_policy, stats.local_ssrc, 0); + if (set_remote_policy) { + if (!(remote_policy = res_srtp_policy->alloc())) { + goto error; + } - if (!(remote_policy = res_srtp_policy->alloc())) { - goto error; - } + if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) { + ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp); + goto error; + } - if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) { - ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp); - goto error; - } + if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) { + ast_log(LOG_WARNING, "Could not set suite to '%u' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp); + goto error; + } - if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) { - ast_log(LOG_WARNING, "Could not set suite to '%u' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp); - goto error; + res_srtp_policy->set_ssrc(remote_policy, 0, 1); } - res_srtp_policy->set_ssrc(remote_policy, 0, 1); - if (ast_rtp_instance_add_srtp_policy(instance, remote_policy, local_policy, rtcp)) { ast_log(LOG_WARNING, "Could not set policies when setting up DTLS-SRTP on '%p'\n", rtp); goto error; } - if (rtp->rekey) { - ao2_ref(instance, +1); - if ((rtp->rekeyid = ast_sched_add(rtp->sched, rtp->rekey * 1000, dtls_srtp_renegotiate, instance)) < 0) { - ao2_ref(instance, -1); - goto error; - } - } - res = 0; error: @@ -2375,6 +2361,71 @@ error: return res; } + +static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp) +{ + struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls; + int index; + + /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ + if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) { + X509 *certificate; + + if (!(certificate = SSL_get_peer_certificate(dtls->ssl))) { + ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance); + return -1; + } + + /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ + if (rtp->remote_fingerprint[0]) { + const EVP_MD *type; + unsigned char fingerprint[EVP_MAX_MD_SIZE]; + unsigned int size; + + if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) { + type = EVP_sha1(); + } else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) { + type = EVP_sha256(); + } else { + ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance); + return -1; + } + + if (!X509_digest(certificate, type, fingerprint, &size) || + !size || + memcmp(fingerprint, rtp->remote_fingerprint, size)) { + X509_free(certificate); + ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n", + instance); + return -1; + } + } + + X509_free(certificate); + } + + if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(instance), 1)) { + return -1; + } + + for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) { + struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index); + + if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(mapping->instance), 0)) { + return -1; + } + } + + if (rtp->rekey) { + ao2_ref(instance, +1); + if ((rtp->rekeyid = ast_sched_add(rtp->sched, rtp->rekey * 1000, dtls_srtp_renegotiate, instance)) < 0) { + ao2_ref(instance, -1); + return -1; + } + } + + return 0; +} #endif static int rtcp_mux(struct ast_rtp *rtp, const unsigned char *packet) @@ -2569,7 +2620,9 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz int len = size; void *temp = buf; struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); - struct ast_srtp *srtp = ast_rtp_instance_get_srtp(instance, rtcp); + struct ast_rtp_instance *transport = rtp->bundled ? rtp->bundled : instance; + struct ast_rtp *transport_rtp = ast_rtp_instance_get_data(transport); + struct ast_srtp *srtp = ast_rtp_instance_get_srtp(transport, rtcp); int res; *via_ice = 0; @@ -2579,20 +2632,24 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz } #ifdef HAVE_PJPROJECT - if (rtp->ice) { + if (transport_rtp->ice) { pj_status_t status; struct ice_wrap *ice; pj_thread_register_check(); /* Release the instance lock to avoid deadlock with PJPROJECT group lock */ - ice = rtp->ice; + ice = transport_rtp->ice; ao2_ref(ice, +1); - ao2_unlock(instance); + if (instance == transport) { + ao2_unlock(instance); + } status = pj_ice_sess_send_data(ice->real_ice, rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP, temp, len); ao2_ref(ice, -1); - ao2_lock(instance); + if (instance == transport) { + ao2_lock(instance); + } if (status == PJ_SUCCESS) { *via_ice = 1; return len; @@ -2600,7 +2657,7 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz } #endif - res = ast_sendto(rtcp ? rtp->rtcp->s : rtp->s, temp, len, flags, sa); + res = ast_sendto(rtcp ? transport_rtp->rtcp->s : transport_rtp->s, temp, len, flags, sa); if (res > 0) { ast_rtp_instance_set_last_tx(instance, time(NULL)); } @@ -2990,22 +3047,10 @@ static int ice_create(struct ast_rtp_instance *instance, struct ast_sockaddr *ad } #endif -/*! \pre instance is locked */ -static int ast_rtp_new(struct ast_rtp_instance *instance, - struct ast_sched_context *sched, struct ast_sockaddr *addr, - void *data) +static int rtp_allocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp) { - struct ast_rtp *rtp = NULL; int x, startplace; - /* Create a new RTP structure to hold all of our data */ - if (!(rtp = ast_calloc(1, sizeof(*rtp)))) { - return -1; - } - - /* Set default parameters on the newly created RTP structure */ - rtp->ssrc = ast_random(); - rtp->seqno = ast_random() & 0x7fff; rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_LEARN : STRICT_RTP_OPEN); if (strictrtp) { rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno); @@ -3015,10 +3060,9 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, /* Create a new socket for us to listen on and use */ if ((rtp->s = create_new_socket("RTP", - ast_sockaddr_is_ipv4(addr) ? AF_INET : - ast_sockaddr_is_ipv6(addr) ? AF_INET6 : -1)) < 0) { + ast_sockaddr_is_ipv4(&rtp->bind_address) ? AF_INET : + ast_sockaddr_is_ipv6(&rtp->bind_address) ? AF_INET6 : -1)) < 0) { ast_log(LOG_WARNING, "Failed to create a new socket for RTP instance '%p'\n", instance); - ast_free(rtp); return -1; } @@ -3028,11 +3072,11 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, startplace = x; for (;;) { - ast_sockaddr_set_port(addr, x); + ast_sockaddr_set_port(&rtp->bind_address, x); /* Try to bind, this will tell us whether the port is available or not */ - if (!ast_bind(rtp->s, addr)) { + if (!ast_bind(rtp->s, &rtp->bind_address)) { ast_debug(1, "Allocated port %d for RTP instance '%p'\n", x, instance); - ast_rtp_instance_set_local_address(instance, addr); + ast_rtp_instance_set_local_address(instance, &rtp->bind_address); break; } @@ -3045,7 +3089,6 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, if (x == startplace || (errno != EADDRINUSE && errno != EACCES)) { ast_log(LOG_ERROR, "Oh dear... we couldn't allocate a port for RTP instance '%p'\n", instance); close(rtp->s); - ast_free(rtp); return -1; } } @@ -3056,40 +3099,30 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, generate_random_string(rtp->local_ufrag, sizeof(rtp->local_ufrag)); generate_random_string(rtp->local_passwd, sizeof(rtp->local_passwd)); -#endif - ast_rtp_instance_set_data(instance, rtp); -#ifdef HAVE_PJPROJECT + /* Create an ICE session for ICE negotiation */ if (icesupport) { rtp->ice_num_components = 2; - ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(addr), x, instance); - if (ice_create(instance, addr, x, 0)) { + ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->bind_address), x, instance); + if (ice_create(instance, &rtp->bind_address, x, 0)) { ast_log(LOG_NOTICE, "Failed to create ICE session\n"); } else { rtp->ice_port = x; - ast_sockaddr_copy(&rtp->ice_original_rtp_addr, addr); + ast_sockaddr_copy(&rtp->ice_original_rtp_addr, &rtp->bind_address); } } #endif - /* Record any information we may need */ - rtp->sched = sched; #ifdef HAVE_OPENSSL_SRTP rtp->rekeyid = -1; rtp->dtls.timeout_timer = -1; #endif - rtp->f.subclass.format = ao2_bump(ast_format_none); - rtp->lastrxformat = ao2_bump(ast_format_none); - rtp->lasttxformat = ao2_bump(ast_format_none); - return 0; } -/*! \pre instance is locked */ -static int ast_rtp_destroy(struct ast_rtp_instance *instance) +static void rtp_deallocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp) { - struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); #ifdef HAVE_PJPROJECT struct timeval wait = ast_tvadd(ast_tvnow(), ast_samp2tv(TURN_STATE_WAIT_TIME, 1000)); struct timespec ts = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000, }; @@ -3099,35 +3132,16 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) ast_rtp_dtls_stop(instance); #endif - /* Destroy the smoother that was smoothing out audio if present */ - if (rtp->smoother) { - ast_smoother_free(rtp->smoother); - } - /* Close our own socket so we no longer get packets */ if (rtp->s > -1) { close(rtp->s); + rtp->s = -1; } /* Destroy RTCP if it was being used */ - if (rtp->rtcp) { - /* - * It is not possible for there to be an active RTCP scheduler - * entry at this point since it holds a reference to the - * RTP instance while it's active. - */ + if (rtp->rtcp && rtp->rtcp->s > -1) { close(rtp->rtcp->s); - ast_free(rtp->rtcp->local_addr_str); - ast_free(rtp->rtcp); - } - - /* Destroy RED if it was being used */ - if (rtp->red) { - ao2_unlock(instance); - AST_SCHED_DEL(rtp->sched, rtp->red->schedid); - ao2_lock(instance); - ast_free(rtp->red); - rtp->red = NULL; + rtp->rtcp->s = -1; } #ifdef HAVE_PJPROJECT @@ -3148,6 +3162,7 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) { ast_cond_timedwait(&rtp->cond, ao2_object_get_lockaddr(instance), &ts); } + rtp->turn_rtp = NULL; } /* Destroy the RTCP TURN relay if being used */ @@ -3161,6 +3176,7 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) { ast_cond_timedwait(&rtp->cond, ao2_object_get_lockaddr(instance), &ts); } + rtp->turn_rtcp = NULL; } /* Destroy any ICE session */ @@ -3169,10 +3185,12 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) /* Destroy any candidates */ if (rtp->ice_local_candidates) { ao2_ref(rtp->ice_local_candidates, -1); + rtp->ice_local_candidates = NULL; } if (rtp->ice_active_remote_candidates) { ao2_ref(rtp->ice_active_remote_candidates, -1); + rtp->ice_active_remote_candidates = NULL; } if (rtp->ioqueue) { @@ -3184,17 +3202,109 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) ao2_unlock(instance); rtp_ioqueue_thread_remove(rtp->ioqueue); ao2_lock(instance); + rtp->ioqueue = NULL; } #endif +} + +/*! \pre instance is locked */ +static int ast_rtp_new(struct ast_rtp_instance *instance, + struct ast_sched_context *sched, struct ast_sockaddr *addr, + void *data) +{ + struct ast_rtp *rtp = NULL; + + /* Create a new RTP structure to hold all of our data */ + if (!(rtp = ast_calloc(1, sizeof(*rtp)))) { + return -1; + } + + /* Set default parameters on the newly created RTP structure */ + rtp->ssrc = ast_random(); + ast_uuid_generate_str(rtp->cname, sizeof(rtp->cname)); + rtp->seqno = ast_random() & 0x7fff; + rtp->sched = sched; + ast_sockaddr_copy(&rtp->bind_address, addr); + + /* Transport creation operations can grab the RTP data from the instance, so set it */ + ast_rtp_instance_set_data(instance, rtp); + + if (rtp_allocate_transport(instance, rtp)) { + ast_free(rtp); + return -1; + } + + rtp->f.subclass.format = ao2_bump(ast_format_none); + rtp->lastrxformat = ao2_bump(ast_format_none); + rtp->lasttxformat = ao2_bump(ast_format_none); + rtp->stream_num = -1; + AST_VECTOR_INIT(&rtp->ssrc_mapping, 1); + + return 0; +} + +/*! + * \brief SSRC mapping comparator for AST_VECTOR_REMOVE_CMP_UNORDERED() + * + * \param elem Element to compare against + * \param value Value to compare with the vector element. + * + * \return 0 if element does not match. + * \return Non-zero if element matches. + */ +#define SSRC_MAPPING_ELEM_CMP(elem, value) ((elem).ssrc == (value)) + +/*! \pre instance is locked */ +static int ast_rtp_destroy(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (rtp->bundled) { + struct ast_rtp *bundled_rtp; + + /* We can't hold our instance lock while removing ourselves from the parent */ + ao2_unlock(instance); + + ao2_lock(rtp->bundled); + bundled_rtp = ast_rtp_instance_get_data(rtp->bundled); + AST_VECTOR_REMOVE_CMP_UNORDERED(&bundled_rtp->ssrc_mapping, rtp->themssrc, SSRC_MAPPING_ELEM_CMP, AST_VECTOR_ELEM_CLEANUP_NOOP); + ao2_unlock(rtp->bundled); + + ao2_lock(instance); + ao2_ref(rtp->bundled, -1); + } + + rtp_deallocate_transport(instance, rtp); + + /* Destroy the smoother that was smoothing out audio if present */ + if (rtp->smoother) { + ast_smoother_free(rtp->smoother); + } + + /* Destroy RTCP if it was being used */ + if (rtp->rtcp) { + /* + * It is not possible for there to be an active RTCP scheduler + * entry at this point since it holds a reference to the + * RTP instance while it's active. + */ + ast_free(rtp->rtcp->local_addr_str); + ast_free(rtp->rtcp); + } + + /* Destroy RED if it was being used */ + if (rtp->red) { + ao2_unlock(instance); + AST_SCHED_DEL(rtp->sched, rtp->red->schedid); + ao2_lock(instance); + ast_free(rtp->red); + rtp->red = NULL; + } ao2_cleanup(rtp->lasttxformat); ao2_cleanup(rtp->lastrxformat); ao2_cleanup(rtp->f.subclass.format); - -#ifdef HAVE_PJPROJECT - /* Destroy synchronization items */ - ast_cond_destroy(&rtp->cond); -#endif + AST_VECTOR_FREE(&rtp->ssrc_mapping); /* Finally destroy ourselves */ ast_free(rtp); @@ -3444,21 +3554,18 @@ static void ast_rtp_change_source(struct ast_rtp_instance *instance) struct ast_srtp *rtcp_srtp = ast_rtp_instance_get_srtp(instance, 1); unsigned int ssrc = ast_random(); - if (!rtp->lastts) { - ast_debug(3, "Not changing SSRC since we haven't sent any RTP yet\n"); - return; - } - - /* We simply set this bit so that the next packet sent will have the marker bit turned on */ - ast_set_flag(rtp, FLAG_NEED_MARKER_BIT); + if (rtp->lastts) { + /* We simply set this bit so that the next packet sent will have the marker bit turned on */ + ast_set_flag(rtp, FLAG_NEED_MARKER_BIT); - ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc); + ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc); - if (srtp) { - ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc); - res_srtp->change_source(srtp, rtp->ssrc, ssrc); - if (rtcp_srtp != srtp) { - res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc); + if (srtp) { + ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc); + res_srtp->change_source(srtp, rtp->ssrc, ssrc); + if (rtcp_srtp != srtp) { + res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc); + } } } @@ -3573,14 +3680,13 @@ static int ast_rtcp_write_report(struct ast_rtp_instance *instance, int sr) struct timeval now; unsigned int now_lsw; unsigned int now_msw; - unsigned int *rtcpheader; + unsigned char *rtcpheader; unsigned int lost_packets; int fraction_lost; struct timeval dlsr = { 0, }; - char bdata[512]; + unsigned char bdata[512] = ""; int rate = rtp_get_rate(rtp->f.subclass.format); int ice; - int header_offset = 0; struct ast_sockaddr remote_address = { { 0, } }; struct ast_rtp_rtcp_report_block *report_block = NULL; RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report, @@ -3634,38 +3740,42 @@ static int ast_rtcp_write_report(struct ast_rtp_instance *instance, int sr) } } timeval2ntp(rtcp_report->sender_information.ntp_timestamp, &now_msw, &now_lsw); - rtcpheader = (unsigned int *)bdata; - rtcpheader[1] = htonl(rtcp_report->ssrc); /* Our SSRC */ + rtcpheader = bdata; + put_unaligned_uint32(rtcpheader + 4, htonl(rtcp_report->ssrc)); /* Our SSRC */ len += 8; if (sr) { - header_offset = 5; - rtcpheader[2] = htonl(now_msw); /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/ - rtcpheader[3] = htonl(now_lsw); /* now, LSW */ - rtcpheader[4] = htonl(rtcp_report->sender_information.rtp_timestamp); - rtcpheader[5] = htonl(rtcp_report->sender_information.packet_count); - rtcpheader[6] = htonl(rtcp_report->sender_information.octet_count); + put_unaligned_uint32(rtcpheader + len, htonl(now_msw)); /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/ + put_unaligned_uint32(rtcpheader + len + 4, htonl(now_lsw)); /* now, LSW */ + put_unaligned_uint32(rtcpheader + len + 8, htonl(rtcp_report->sender_information.rtp_timestamp)); + put_unaligned_uint32(rtcpheader + len + 12, htonl(rtcp_report->sender_information.packet_count)); + put_unaligned_uint32(rtcpheader + len + 16, htonl(rtcp_report->sender_information.octet_count)); len += 20; } if (report_block) { - rtcpheader[2 + header_offset] = htonl(report_block->source_ssrc); /* Their SSRC */ - rtcpheader[3 + header_offset] = htonl((report_block->lost_count.fraction << 24) | report_block->lost_count.packets); - rtcpheader[4 + header_offset] = htonl(report_block->highest_seq_no); - rtcpheader[5 + header_offset] = htonl(report_block->ia_jitter); - rtcpheader[6 + header_offset] = htonl(report_block->lsr); - rtcpheader[7 + header_offset] = htonl(report_block->dlsr); + put_unaligned_uint32(rtcpheader + len, htonl(report_block->source_ssrc)); /* Their SSRC */ + put_unaligned_uint32(rtcpheader + len + 4, htonl((report_block->lost_count.fraction << 24) | report_block->lost_count.packets)); + put_unaligned_uint32(rtcpheader + len + 8, htonl(report_block->highest_seq_no)); + put_unaligned_uint32(rtcpheader + len + 12, htonl(report_block->ia_jitter)); + put_unaligned_uint32(rtcpheader + len + 16, htonl(report_block->lsr)); + put_unaligned_uint32(rtcpheader + len + 20, htonl(report_block->dlsr)); len += 24; } - rtcpheader[0] = htonl((2 << 30) | (rtcp_report->reception_report_count << 24) - | ((sr ? RTCP_PT_SR : RTCP_PT_RR) << 16) | ((len/4)-1)); - /* Insert SDES here. Probably should make SDES text equal to mimetypes[code].type (not subtype 'cos */ - /* it can change mid call, and SDES can't) */ - rtcpheader[len/4] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | 2); - rtcpheader[(len/4)+1] = htonl(rtcp_report->ssrc); - rtcpheader[(len/4)+2] = htonl(0x01 << 24); - len += 12; + put_unaligned_uint32(rtcpheader, htonl((2 << 30) | (rtcp_report->reception_report_count << 24) + | ((sr ? RTCP_PT_SR : RTCP_PT_RR) << 16) | ((len/4)-1))); + + put_unaligned_uint32(rtcpheader + len, htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | (2 + (AST_UUID_STR_LEN / 4)))); + put_unaligned_uint32(rtcpheader + len + 4, htonl(rtcp_report->ssrc)); + put_unaligned_uint16(rtcpheader + len + 8, htonl(0x01 << 24)); + put_unaligned_uint16(rtcpheader + len + 9, htonl(AST_UUID_STR_LEN << 24)); + memcpy(rtcpheader + len + 10, rtp->cname, AST_UUID_STR_LEN); + len += 12 + AST_UUID_STR_LEN; - ast_sockaddr_copy(&remote_address, &rtp->rtcp->them); + if (rtp->bundled) { + ast_rtp_instance_get_remote_address(instance, &remote_address); + } else { + ast_sockaddr_copy(&remote_address, &rtp->rtcp->them); + } res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &remote_address, &ice); if (res < 0) { ast_log(LOG_ERROR, "RTCP %s transmission error to %s, rtcp halted %s\n", @@ -3942,7 +4052,6 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr /* VP8: is this a request to send a RTCP FIR? */ if (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_VIDUPDATE) { - struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); unsigned int *rtcpheader; char bdata[1024]; int len = 20; @@ -3972,7 +4081,7 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr rtcpheader[2] = htonl(rtp->themssrc); rtcpheader[3] = htonl(rtp->themssrc); /* FCI: SSRC */ rtcpheader[4] = htonl(rtp->rtcp->firseq << 24); /* FCI: Sequence number */ - res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &rtp->rtcp->them, &ice); + res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, rtp->bundled ? &remote_address : &rtp->rtcp->them, &ice); if (res < 0) { ast_log(LOG_ERROR, "RTCP FIR transmission error: %s\n", strerror(errno)); } @@ -4537,9 +4646,29 @@ static void update_lost_stats(struct ast_rtp *rtp, unsigned int lost_packets) rtp->rtcp->reported_normdev_lost = reported_normdev_lost_current; } +/*! \pre instance is locked */ +static struct ast_rtp_instance *rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance, + struct ast_rtp *rtp, unsigned int ssrc) +{ + int index; + struct ast_rtp_instance *found = instance; + + for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) { + struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index); + + if (mapping->ssrc == ssrc) { + found = mapping->instance; + break; + } + } + + return found; +} + static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, const unsigned char *rtcpdata, size_t size, struct ast_sockaddr *addr) { - struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + struct ast_rtp_instance *transport = instance; + struct ast_rtp *transport_rtp = ast_rtp_instance_get_data(instance); unsigned int *rtcpheader = (unsigned int *)(rtcpdata); int packetwords, position = 0; int report_counter = 0; @@ -4548,13 +4677,13 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c packetwords = size / 4; - if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) { + if (ast_rtp_instance_get_prop(transport, AST_RTP_PROPERTY_NAT)) { /* Send to whoever sent to us */ - if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) { - ast_sockaddr_copy(&rtp->rtcp->them, addr); + if (ast_sockaddr_cmp(&transport_rtp->rtcp->them, addr)) { + ast_sockaddr_copy(&transport_rtp->rtcp->them, addr); if (rtpdebug) { ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_sockaddr_stringify(&transport_rtp->rtcp->them)); } } } @@ -4566,6 +4695,8 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c unsigned int length; struct ast_json *message_blob; RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report, NULL, ao2_cleanup); + struct ast_rtp_instance *child; + struct ast_rtp *rtp; i = position; length = ntohl(rtcpheader[i]); @@ -4597,6 +4728,21 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c ast_verbose("SSRC of sender: %u\n", rtcp_report->ssrc); } + /* Determine the appropriate instance for this */ + child = rtp_find_instance_by_ssrc(transport, transport_rtp, rtcp_report->ssrc); + if (child != transport) { + /* It is safe to hold the child lock while holding the parent lock, we guarantee that the locking order + * is always parent->child or that the child lock is not held when acquiring the parent lock. + */ + ao2_lock(child); + instance = child; + rtp = ast_rtp_instance_get_data(instance); + } else { + /* The child is the parent! We don't need to unlock it. */ + child = NULL; + rtp = transport_rtp; + } + i += 2; /* Advance past header and ssrc */ switch (pt) { case RTCP_PT_SR: @@ -4632,6 +4778,9 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c /* Don't handle multiple reception reports (rc > 1) yet */ report_block = ast_calloc(1, sizeof(*report_block)); if (!report_block) { + if (child) { + ao2_unlock(child); + } return &ast_null_frame; } rtcp_report->report_block[report_counter] = report_block; @@ -4678,8 +4827,8 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c */ message_blob = ast_json_pack("{s: s, s: s, s: f}", - "from", ast_sockaddr_stringify(&rtp->rtcp->them), - "to", rtp->rtcp->local_addr_str, + "from", ast_sockaddr_stringify(&transport_rtp->rtcp->them), + "to", transport_rtp->rtcp->local_addr_str, "rtt", rtp->rtcp->rtt); ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_received_type(), rtcp_report, @@ -4688,26 +4837,26 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c /* Return an AST_FRAME_RTCP frame with the ast_rtp_rtcp_report * object as a its data */ - rtp->f.frametype = AST_FRAME_RTCP; - rtp->f.data.ptr = rtp->rtcp->frame_buf + AST_FRIENDLY_OFFSET; - memcpy(rtp->f.data.ptr, rtcp_report, sizeof(struct ast_rtp_rtcp_report)); - rtp->f.datalen = sizeof(struct ast_rtp_rtcp_report); + transport_rtp->f.frametype = AST_FRAME_RTCP; + transport_rtp->f.data.ptr = rtp->rtcp->frame_buf + AST_FRIENDLY_OFFSET; + memcpy(transport_rtp->f.data.ptr, rtcp_report, sizeof(struct ast_rtp_rtcp_report)); + transport_rtp->f.datalen = sizeof(struct ast_rtp_rtcp_report); if (rc > 0) { /* There's always a single report block stored, here */ struct ast_rtp_rtcp_report *rtcp_report2; - report_block = rtp->f.data.ptr + rtp->f.datalen + sizeof(struct ast_rtp_rtcp_report_block *); + report_block = transport_rtp->f.data.ptr + transport_rtp->f.datalen + sizeof(struct ast_rtp_rtcp_report_block *); memcpy(report_block, rtcp_report->report_block[report_counter-1], sizeof(struct ast_rtp_rtcp_report_block)); - rtcp_report2 = (struct ast_rtp_rtcp_report *)rtp->f.data.ptr; + rtcp_report2 = (struct ast_rtp_rtcp_report *)transport_rtp->f.data.ptr; rtcp_report2->report_block[report_counter-1] = report_block; - rtp->f.datalen += sizeof(struct ast_rtp_rtcp_report_block); + transport_rtp->f.datalen += sizeof(struct ast_rtp_rtcp_report_block); } - rtp->f.offset = AST_FRIENDLY_OFFSET; - rtp->f.samples = 0; - rtp->f.mallocd = 0; - rtp->f.delivery.tv_sec = 0; - rtp->f.delivery.tv_usec = 0; - rtp->f.src = "RTP"; - f = &rtp->f; + transport_rtp->f.offset = AST_FRIENDLY_OFFSET; + transport_rtp->f.samples = 0; + transport_rtp->f.mallocd = 0; + transport_rtp->f.delivery.tv_sec = 0; + transport_rtp->f.delivery.tv_usec = 0; + transport_rtp->f.src = "RTP"; + f = &transport_rtp->f; break; case RTCP_PT_FUR: /* Handle RTCP FIR as FUR */ @@ -4715,34 +4864,38 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c if (rtcp_debug_test_addr(addr)) { ast_verbose("Received an RTCP Fast Update Request\n"); } - rtp->f.frametype = AST_FRAME_CONTROL; - rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE; - rtp->f.datalen = 0; - rtp->f.samples = 0; - rtp->f.mallocd = 0; - rtp->f.src = "RTP"; - f = &rtp->f; + transport_rtp->f.frametype = AST_FRAME_CONTROL; + transport_rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE; + transport_rtp->f.datalen = 0; + transport_rtp->f.samples = 0; + transport_rtp->f.mallocd = 0; + transport_rtp->f.src = "RTP"; + f = &transport_rtp->f; break; case RTCP_PT_SDES: if (rtcp_debug_test_addr(addr)) { ast_verbose("Received an SDES from %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_sockaddr_stringify(&transport_rtp->rtcp->them)); } break; case RTCP_PT_BYE: if (rtcp_debug_test_addr(addr)) { ast_verbose("Received a BYE from %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_sockaddr_stringify(&transport_rtp->rtcp->them)); } break; default: ast_debug(1, "Unknown RTCP packet (pt=%d) received from %s\n", - pt, ast_sockaddr_stringify(&rtp->rtcp->them)); + pt, ast_sockaddr_stringify(&transport_rtp->rtcp->them)); break; } position += (length + 1); + rtp->rtcp->rtcp_info = 1; + + if (child) { + ao2_unlock(child); + } } - rtp->rtcp->rtcp_info = 1; return f; @@ -4928,11 +5081,19 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance, return 0; } +static void rtp_instance_unlock(struct ast_rtp_instance *instance) +{ + if (instance) { + ao2_unlock(instance); + } +} + /*! \pre instance is locked */ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtcp) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp_instance *instance1; + RAII_VAR(struct ast_rtp_instance *, child, NULL, rtp_instance_unlock); struct ast_sockaddr addr; int res, hdrlen = 12, version, payloadtype, padding, mark, ext, cc, prev_seqno; unsigned char *read_area = rtp->rawdata + AST_FRIENDLY_OFFSET; @@ -4950,11 +5111,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc return &ast_null_frame; } - /* If we are currently sending DTMF to the remote party send a continuation packet */ - if (rtp->sending_digit) { - ast_rtp_dtmf_continuation(instance); - } - /* Actually read in the data from the socket */ if ((res = rtp_recvfrom(instance, read_area, read_area_size, 0, &addr)) < 0) { @@ -5070,6 +5226,33 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc } } + /* If the version is not what we expected by this point then just drop the packet */ + if (version != 2) { + return &ast_null_frame; + } + + /* We use the SSRC to determine what RTP instance this packet is actually for */ + ssrc = ntohl(rtpheader[2]); + + /* Determine the appropriate instance for this */ + child = rtp_find_instance_by_ssrc(instance, rtp, ssrc); + if (child != instance) { + /* It is safe to hold the child lock while holding the parent lock, we guarantee that the locking order + * is always parent->child or that the child lock is not held when acquiring the parent lock. + */ + ao2_lock(child); + instance = child; + rtp = ast_rtp_instance_get_data(instance); + } else { + /* The child is the parent! We don't need to unlock it. */ + child = NULL; + } + + /* If we are currently sending DTMF to the remote party send a continuation packet */ + if (rtp->sending_digit) { + ast_rtp_dtmf_continuation(instance); + } + /* If we are directly bridged to another instance send the audio directly out */ instance1 = ast_rtp_instance_get_bridged(instance); if (instance1 @@ -5077,11 +5260,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc return &ast_null_frame; } - /* If the version is not what we expected by this point then just drop the packet */ - if (version != 2) { - return &ast_null_frame; - } - /* Pull out the various other fields we will need */ payloadtype = (seqno & 0x7f0000) >> 16; padding = seqno & (1 << 29); @@ -5090,7 +5268,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc cc = (seqno & 0xF000000) >> 24; seqno &= 0xffff; timestamp = ntohl(rtpheader[1]); - ssrc = ntohl(rtpheader[2]); AST_LIST_HEAD_INIT_NOLOCK(&frames); /* Force a marker bit and change SSRC if the SSRC changes */ @@ -5264,6 +5441,7 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc rtp->f.data.ptr = read_area + hdrlen; rtp->f.offset = hdrlen + AST_FRIENDLY_OFFSET; rtp->f.seqno = seqno; + rtp->f.stream_num = rtp->stream_num; if ((ast_format_cmp(rtp->f.subclass.format, ast_format_t140) == AST_FORMAT_CMP_EQUAL) && ((int)seqno - (prev_seqno + 1) > 0) @@ -5525,6 +5703,7 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_sockaddr local; + int index; ast_rtp_instance_get_local_address(instance, &local); if (!ast_sockaddr_isnull(addr)) { @@ -5553,6 +5732,13 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct rtp->rtcp->local_addr_str = ast_strdup(ast_sockaddr_stringify(&local)); } + /* Update any bundled RTP instances */ + for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) { + struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index); + + ast_rtp_instance_set_remote_address(mapping->instance, addr); + } + rtp->rxseqno = 0; if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN) { @@ -5836,42 +6022,104 @@ static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance) /*! \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 ""; + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + return rtp->cname; } -#ifdef HAVE_OPENSSL_SRTP -static void dtls_perform_setup(struct dtls_details *dtls) +static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc) { - if (!dtls->ssl || !SSL_is_init_finished(dtls->ssl)) { + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (rtp->themssrc) { return; } - SSL_clear(dtls->ssl); - if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) { - SSL_set_accept_state(dtls->ssl); - } else { - SSL_set_connect_state(dtls->ssl); - } - dtls->connection = AST_RTP_DTLS_CONNECTION_NEW; + rtp->themssrc = ssrc; } -/*! \pre instance is locked */ -static int ast_rtp_activate(struct ast_rtp_instance *instance) +static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); - dtls_perform_setup(&rtp->dtls); + rtp->stream_num = stream_num; +} - if (rtp->rtcp) { - dtls_perform_setup(&rtp->rtcp->dtls); +static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent) +{ + struct ast_rtp *child_rtp = ast_rtp_instance_get_data(child); + struct ast_rtp *parent_rtp = ast_rtp_instance_get_data(parent); + struct rtp_ssrc_mapping mapping; + struct ast_sockaddr them = { { 0, } }; + + if (child_rtp->bundled == parent) { + return 0; + } + + /* If this instance was already bundled then remove the SSRC mapping */ + if (child_rtp->bundled) { + struct ast_rtp *bundled_rtp; + + ao2_unlock(child); + + /* The child lock can't be held while accessing the parent */ + ao2_lock(child_rtp->bundled); + bundled_rtp = ast_rtp_instance_get_data(child_rtp->bundled); + AST_VECTOR_REMOVE_CMP_UNORDERED(&bundled_rtp->ssrc_mapping, child_rtp->themssrc, SSRC_MAPPING_ELEM_CMP, AST_VECTOR_ELEM_CLEANUP_NOOP); + ao2_unlock(child_rtp->bundled); + + ao2_lock(child); + ao2_ref(child_rtp->bundled, -1); + child_rtp->bundled = NULL; + } + + if (!parent) { + /* We transitioned away from bundle so we need our own transport resources once again */ + rtp_allocate_transport(child, child_rtp); + return 0; + } + + /* We no longer need any transport related resources as we will use our parent RTP instance instead */ + rtp_deallocate_transport(child, child_rtp); + + /* Children maintain a reference to the parent to guarantee that the transport doesn't go away on them */ + child_rtp->bundled = ao2_bump(parent); + + mapping.ssrc = child_rtp->themssrc; + mapping.instance = child; + + ao2_unlock(child); + + ao2_lock(parent); + + AST_VECTOR_APPEND(&parent_rtp->ssrc_mapping, mapping); + +#ifdef HAVE_OPENSSL_SRTP + /* If DTLS-SRTP is already in use then add the local SSRC to it, otherwise it will get added once DTLS + * negotiation has been completed. + */ + if (parent_rtp->dtls.connection == AST_RTP_DTLS_CONNECTION_EXISTING) { + dtls_srtp_add_local_ssrc(parent_rtp, ast_rtp_instance_get_srtp(parent, 0), parent, 0, child_rtp->ssrc, 0); } +#endif + + /* Bundle requires that RTCP-MUX be in use so only the main remote address needs to match */ + ast_rtp_instance_get_remote_address(parent, &them); + + ao2_unlock(parent); + + ao2_lock(child); + + ast_rtp_instance_set_remote_address(child, &them); + + return 0; +} + +#ifdef HAVE_OPENSSL_SRTP +/*! \pre instance is locked */ +static int ast_rtp_activate(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); /* If ICE negotiation is enabled the DTLS Handshake will be performed upon completion of it */ #ifdef HAVE_PJPROJECT @@ -5880,9 +6128,11 @@ static int ast_rtp_activate(struct ast_rtp_instance *instance) } #endif + dtls_perform_setup(&rtp->dtls); dtls_perform_handshake(instance, &rtp->dtls, 0); if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) { + dtls_perform_setup(&rtp->rtcp->dtls); dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1); }