res_rtp_asterisk / res_pjsip: Add support for BUNDLE.
authorJoshua Colp <jcolp@digium.com>
Fri, 30 Jun 2017 18:55:57 +0000 (18:55 +0000)
committerJoshua Colp <jcolp@digium.com>
Thu, 13 Jul 2017 14:47:50 +0000 (14:47 +0000)
BUNDLE is a specification used in WebRTC to allow multiple
streams to use the same underlying transport. This reduces
the number of ICE and DTLS negotiations that has to occur
to 1 normally.

This change implements this by adding support for it to
the RTP SDP module in PJSIP. BUNDLE can be turned on using
the "bundle" option and on an offer we will offer to
bundle streams together. On an answer we will accept any
bundle groups provided. Once accepted each stream is bundled
to another RTP instance for transport.

For the res_rtp_asterisk changes the ability to bundle
an RTP instance to another based on the SSRC received
from the remote side has been added. For outgoing traffic
if an RTP instance is bundled to another we will use the
other RTP instance for any transport related things. For
incoming traffic received from the transport instance we
look up the correct instance based on the SSRC and use it
for any non-transport related data.

ASTERISK-27118

Change-Id: I96c0920b9f9aca7382256484765a239017973c11

channels/chan_pjsip.c
include/asterisk/res_pjsip.h
include/asterisk/res_pjsip_session.h
include/asterisk/rtp_engine.h
main/rtp_engine.c
res/res_pjsip.c
res/res_pjsip/pjsip_configuration.c
res/res_pjsip_sdp_rtp.c
res/res_pjsip_session.c
res/res_pjsip_t38.c
res/res_rtp_asterisk.c

index 0e4468c..3de980a 100644 (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;
index 2cd27d3..d499d55 100644 (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;
 };
 
 /*!
index e298e1f..eae29de 100644 (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 {                                                            \
index 5f43916..20ae959 100644 (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 */
@@ -1507,6 +1513,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
  * @{
  */
index 9cfae09..abd4b1f 100644 (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);
+}
\ No newline at end of file
index 0cf0343..1b546ef 100644 (file)
                                                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>
index 372b01b..d56ff5d 100644 (file)
@@ -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");
index a491308..4ec8115 100644 (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));
+                       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 */
index ecda499..315db6d 100644 (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) {
index a032bb1..877d48f 100644 (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)
index 01dfe76..4bfbf9b 100644 (file)
@@ -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);
        }