<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/5981">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Kevin Harwell: Looks good to me, but someone else must approve
  Matthew Fredrickson: Looks good to me, approved
  Jenkins2: Approved for Submit

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

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I96c0920b9f9aca7382256484765a239017973c11 </div>
<div style="display:none"> Gerrit-Change-Number: 5981 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Matthew Fredrickson <creslin@digium.com> </div>