Merge "res_rtp_asterisk / res_pjsip: Add support for BUNDLE."

This commit is contained in:
Jenkins2 2017-07-13 14:40:11 -05:00 committed by Gerrit Code Review
commit 0f45c979a3
11 changed files with 1170 additions and 393 deletions

View File

@ -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;

View File

@ -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;
};
/*!

View File

@ -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 { \

View File

@ -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 */
@ -1511,6 +1517,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
@ -2271,6 +2291,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);
@ -2416,6 +2438,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
* @{
*/

View File

@ -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);
}

View File

@ -1002,6 +1002,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>

View File

@ -1363,6 +1363,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;
}
@ -1985,6 +1989,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");

View File

@ -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));
}
media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
if (!media->conn) {
return -1;
}
/* 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 (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;
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;
}
}
}
/* Add ICE attributes and candidates */
add_ice_to_stream(session, session_media, pool, media);
session_media_transport = ast_sip_session_media_get_transport(session, session_media);
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;
if (session_media_transport == session_media || !session_media->bundled) {
set_ice_components(session, session_media);
enable_rtcp(session, session_media, NULL);
/* 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;
}
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));
}
media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
if (!media->conn) {
return -1;
}
/* 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 (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;
}
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,55 +1605,68 @@ 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);
enable_rtcp(session, session_media, remote_stream);
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 (!remote_stream->conn && !remote->conn) {
return 1;
}
ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
/* 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 (!remote_stream->conn && !remote->conn) {
if (set_caps(session, session_media, session_media_transport, remote_stream, 0, asterisk_stream)) {
return 1;
}
ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
/* 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);
if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) {
return 1;
}
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);
/* Set the channel uniqueid on the RTP instance now that it is becoming active */
ast_channel_lock(session->channel);
ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel));
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 */

View File

@ -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) {

View File

@ -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)

File diff suppressed because it is too large Load Diff