diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 58dd23acf5a98388219dd9cc0fa6eb9bdcc8b63c..d913b938d38480569eeb06aa08a4452e505e5ec8 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -5850,6 +5850,16 @@ static void sip_subscribe_mwi_destroy(struct sip_subscription_mwi *mwi) ast_free(mwi); } +/*! \brief Destroy SDP media offer list */ +static void offered_media_list_destroy(struct sip_pvt *p) +{ + struct offered_media *offer; + while ((offer = AST_LIST_REMOVE_HEAD(&p->offered_media, next))) { + ast_free(offer->decline_m_line); + ast_free(offer); + } +} + /*! \brief Execute destruction of SIP dialog structure, release memory */ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) { @@ -5953,6 +5963,8 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) ast_free(req); } + offered_media_list_destroy(p); + if (p->chanvars) { ast_variables_destroy(p->chanvars); p->chanvars = NULL; @@ -8033,6 +8045,7 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr, ast_string_field_set(p, engine, default_engine); AST_LIST_HEAD_INIT_NOLOCK(&p->request_queue); + AST_LIST_HEAD_INIT_NOLOCK(&p->offered_media); /* Add to active dialog list */ @@ -9119,6 +9132,18 @@ static int sockaddr_is_null_or_any(const struct ast_sockaddr *addr) return ast_sockaddr_isnull(addr) || ast_sockaddr_is_any(addr); } +/*! \brief Check the media stream list to see if the given type already exists */ +static int has_media_stream(struct sip_pvt *p, enum media_type m) +{ + struct offered_media *offer = NULL; + AST_LIST_TRAVERSE(&p->offered_media, offer, next) { + if (m == offer->type) { + return 1; + } + } + return 0; +} + /*! \brief Process SIP SDP offer, select formats and activate media channels If offer is rejected, we will not change any properties of the call Return 0 on success, a negative value on errors. @@ -9139,6 +9164,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action const char *m = NULL; /* SDP media offer */ const char *nextm = NULL; int len = -1; + struct offered_media *offer; /* Host information */ struct ast_sockaddr sessionsa; @@ -9178,7 +9204,6 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action int sendonly = -1; int vsendonly = -1; int numberofports; - int numberofmediastreams = 0; int last_rtpmap_codec = 0; int red_data_pt[10]; /* For T.140 RED */ int red_num_gen = 0; /* For T.140 RED */ @@ -9209,7 +9234,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action /* Update our last rtprx when we receive an SDP, too */ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */ - memset(p->offered_media, 0, sizeof(p->offered_media)); + offered_media_list_destroy(p); /* Scan for the first media stream (m=) line to limit scanning of globals */ nextm = get_sdp_iterate(&next, req, "m"); @@ -9281,10 +9306,29 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action iterator = next; nextm = get_sdp_iterate(&next, req, "m"); + if (!(offer = ast_calloc(1, sizeof(*offer)))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer list\n"); + res = -1; + goto process_sdp_cleanup; + } + AST_LIST_INSERT_TAIL(&p->offered_media, offer, next); + offer->type = SDP_UNKNOWN; + /* Check for 'audio' media offer */ if (strncmp(m, "audio ", 6) == 0) { if ((sscanf(m, "audio %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || (sscanf(m, "audio %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) { + codecs = m + len; + /* produce zero-port m-line since it may be needed later + * length is "m=audio 0 RTP/" + protocol + " " + codecs + "\0" */ + if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + goto process_sdp_cleanup; + } + /* guaranteed to be exactly the right length */ + sprintf(offer->decline_m_line, "m=audio 0 RTP/%s %s", protocol, codecs); + if (x == 0) { ast_log(LOG_WARNING, "Ignoring audio media offer because port number is zero\n"); continue; @@ -9299,23 +9343,19 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action secure_audio = 1; } else if (strcmp(protocol, "AVP")) { ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m); - res = -1; - goto process_sdp_cleanup; + continue; } - if (p->offered_media[SDP_AUDIO].order_offered) { - ast_log(LOG_WARNING, "Rejecting non-primary audio stream: %s\n", m); - res = -1; - goto process_sdp_cleanup; + if (has_media_stream(p, SDP_AUDIO)) { + ast_log(LOG_WARNING, "Declining non-primary audio stream: %s\n", m); + continue; } audio = TRUE; - p->offered_media[SDP_AUDIO].order_offered = ++numberofmediastreams; + offer->type = SDP_AUDIO; portno = x; /* Scan through the RTP payload types specified in a "m=" line: */ - codecs = m + len; - ast_copy_string(p->offered_media[SDP_AUDIO].codecs, codecs, sizeof(p->offered_media[SDP_AUDIO].codecs)); for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { ast_log(LOG_WARNING, "Invalid syntax in RTP audio format list: %s\n", codecs); @@ -9338,38 +9378,45 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (strncmp(m, "video ", 6) == 0) { if ((sscanf(m, "video %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || (sscanf(m, "video %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) { + codecs = m + len; + /* produce zero-port m-line since it may be needed later + * length is "m=video 0 RTP/" + protocol + " " + codecs + "\0" */ + if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + goto process_sdp_cleanup; + } + /* guaranteed to be exactly the right length */ + sprintf(offer->decline_m_line, "m=video 0 RTP/%s %s", protocol, codecs); + if (x == 0) { - ast_log(LOG_WARNING, "Ignoring video media offer because port number is zero\n"); + ast_log(LOG_WARNING, "Ignoring video stream offer because port number is zero\n"); continue; } /* Check number of ports offered for stream */ if (numberofports > 1) { - ast_log(LOG_WARNING, "%d ports offered for video media, not supported by Asterisk. Will try anyway...\n", numberofports); + ast_log(LOG_WARNING, "%d ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports); } if (!strcmp(protocol, "SAVP")) { secure_video = 1; } else if (strcmp(protocol, "AVP")) { ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m); - res = -1; - goto process_sdp_cleanup; + continue; } - if (p->offered_media[SDP_VIDEO].order_offered) { - ast_log(LOG_WARNING, "Rejecting non-primary video stream: %s\n", m); - res = -1; - goto process_sdp_cleanup; + if (has_media_stream(p, SDP_VIDEO)) { + ast_log(LOG_WARNING, "Declining non-primary video stream: %s\n", m); + continue; } video = TRUE; p->novideo = FALSE; - p->offered_media[SDP_VIDEO].order_offered = ++numberofmediastreams; + offer->type = SDP_VIDEO; vportno = x; /* Scan through the RTP payload types specified in a "m=" line: */ - codecs = m + len; - ast_copy_string(p->offered_media[SDP_VIDEO].codecs, codecs, sizeof(p->offered_media[SDP_VIDEO].codecs)); for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { ast_log(LOG_WARNING, "Invalid syntax in RTP video format list: %s\n", codecs); @@ -9391,30 +9438,38 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (strncmp(m, "text ", 5) == 0) { if ((sscanf(m, "text %30u/%30u RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) || (sscanf(m, "text %30u RTP/AVP %n", &x, &len) == 1 && len > 0)) { + codecs = m + len; + /* produce zero-port m-line since it may be needed later + * length is "m=text 0 RTP/AVP " + codecs + "\0" */ + if (!(offer->decline_m_line = ast_malloc(17 + strlen(codecs) + 1))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + goto process_sdp_cleanup; + } + /* guaranteed to be exactly the right length */ + sprintf(offer->decline_m_line, "m=text 0 RTP/AVP %s", codecs); + if (x == 0) { - ast_log(LOG_WARNING, "Ignoring text media offer because port number is zero\n"); + ast_log(LOG_WARNING, "Ignoring text stream offer because port number is zero\n"); continue; } /* Check number of ports offered for stream */ if (numberofports > 1) { - ast_log(LOG_WARNING, "%d ports offered for text media, not supported by Asterisk. Will try anyway...\n", numberofports); + ast_log(LOG_WARNING, "%d ports offered for text stream, not supported by Asterisk. Will try anyway...\n", numberofports); } - if (p->offered_media[SDP_TEXT].order_offered) { - ast_log(LOG_WARNING, "Rejecting non-primary text stream: %s\n", m); - res = -1; - goto process_sdp_cleanup; + if (has_media_stream(p, SDP_TEXT)) { + ast_log(LOG_WARNING, "Declining non-primary text stream: %s\n", m); + continue; } text = TRUE; p->notext = FALSE; - p->offered_media[SDP_TEXT].order_offered = ++numberofmediastreams; + offer->type = SDP_TEXT; tportno = x; /* Scan through the RTP payload types specified in a "m=" line: */ - codecs = m + len; - ast_copy_string(p->offered_media[SDP_TEXT].codecs, codecs, sizeof(p->offered_media[SDP_TEXT].codecs)); for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { ast_log(LOG_WARNING, "Invalid syntax in RTP video format list: %s\n", codecs); @@ -9427,7 +9482,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action ast_rtp_codecs_payloads_set_m_type(&newtextrtp, NULL, codec); } } else { - ast_log(LOG_WARNING, "Rejecting text media offer due to invalid or unsupported syntax: %s\n", m); + ast_log(LOG_WARNING, "Rejecting text stream offer due to invalid or unsupported syntax: %s\n", m); res = -1; goto process_sdp_cleanup; } @@ -9436,20 +9491,29 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (strncmp(m, "image ", 6) == 0) { if (((sscanf(m, "image %30u udptl t38%n", &x, &len) == 1 && len > 0) || (sscanf(m, "image %30u UDPTL t38%n", &x, &len) == 1 && len > 0))) { + /* produce zero-port m-line since it may be needed later + * length is "m=image 0 UDPTL t38" + "\0" */ + if (!(offer->decline_m_line = ast_malloc(20))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + goto process_sdp_cleanup; + } + /* guaranteed to be exactly the right length */ + strcpy(offer->decline_m_line, "m=image 0 UDPTL t38"); + if (x == 0) { - ast_log(LOG_WARNING, "Ignoring image media offer because port number is zero\n"); + ast_log(LOG_WARNING, "Ignoring image stream offer because port number is zero\n"); continue; } if (initialize_udptl(p)) { - res = -1; - goto process_sdp_cleanup; + ast_log(LOG_WARNING, "Failed to initialize UDPTL, declining image stream\n"); + continue; } - if (p->offered_media[SDP_IMAGE].order_offered) { - ast_log(LOG_WARNING, "Rejecting non-primary image stream: %s\n", m); - res = -1; - goto process_sdp_cleanup; + if (has_media_stream(p, SDP_IMAGE)) { + ast_log(LOG_WARNING, "Declining non-primary image stream: %s\n", m); + continue; } image = TRUE; @@ -9457,7 +9521,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action ast_verbose("Got T.38 offer in SDP in dialog %s\n", p->callid); } - p->offered_media[SDP_IMAGE].order_offered = ++numberofmediastreams; + offer->type = SDP_IMAGE; udptlportno = x; if (p->t38.state != T38_ENABLED) { @@ -9473,9 +9537,26 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action goto process_sdp_cleanup; } } else { - ast_log(LOG_WARNING, "Unsupported top-level media type in offer: %s\n", m); - res = -1; - goto process_sdp_cleanup; + char type[20] = {0,}; + char *typelen = strchr(m, ' '); + if (typelen && typelen - m < 20 && + ((sscanf(m, "%s %30u/%30u %n", type, &x, &numberofports, &len) == 2 && len > 0) || + (sscanf(m, "%s %30u %n", type, &x, &len) == 1 && len > 0))) { + /* produce zero-port m-line since it may be needed later + * length is "m=" + type + " 0 " + remainder + "\0" */ + if (!(offer->decline_m_line = ast_malloc(2 + strlen(type) + 3 + strlen(m + len) + 1))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + goto process_sdp_cleanup; + } + /* guaranteed to be long enough */ + sprintf(offer->decline_m_line, "m=%s 0 %s", type, m + len); + continue; + } else { + ast_log(LOG_WARNING, "Unsupported top-level media type in offer: %s\n", m); + res = -1; + goto process_sdp_cleanup; + } } /* Media stream specific parameters */ @@ -9852,6 +9933,9 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action } process_sdp_cleanup: + if (res) { + offered_media_list_destroy(p); + } ast_format_cap_destroy(peercapability); ast_format_cap_destroy(vpeercapability); ast_format_cap_destroy(tpeercapability); @@ -11817,6 +11901,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int struct ast_sockaddr udptldest = { {0,} }; /* SDP fields */ + struct offered_media *offer; char *version = "v=0\r\n"; /* Protocol version */ char subject[256]; /* Subject of the session */ char owner[256]; /* Session owner/creator */ @@ -11848,7 +11933,6 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int char codecbuf[SIPBUFSIZE]; char buf[SIPBUFSIZE]; - char dummy_answer[256]; /* Set the SDP session name */ snprintf(subject, sizeof(subject), "s=%s\r\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession); @@ -12153,14 +12237,10 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int } add_content(resp, session_time); /* if this is a response to an invite, order our offers properly */ - if (p->offered_media[SDP_AUDIO].order_offered || - p->offered_media[SDP_VIDEO].order_offered || - p->offered_media[SDP_TEXT].order_offered || - p->offered_media[SDP_IMAGE].order_offered) { - int i; - /* we have up to 3 streams as limited by process_sdp */ - for (i = 1; i <= 3; i++) { - if (p->offered_media[SDP_AUDIO].order_offered == i) { + if (!AST_LIST_EMPTY(&p->offered_media)) { + AST_LIST_TRAVERSE(&p->offered_media, offer, next) { + switch (offer->type) { + case SDP_AUDIO: if (needaudio) { add_content(resp, m_audio->str); add_content(resp, a_audio->str); @@ -12169,10 +12249,10 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int add_content(resp, a_crypto); } } else { - snprintf(dummy_answer, sizeof(dummy_answer), "m=audio 0 RTP/AVP %s\r\n", p->offered_media[SDP_AUDIO].codecs); - add_content(resp, dummy_answer); + add_content(resp, offer->decline_m_line); } - } else if (p->offered_media[SDP_VIDEO].order_offered == i) { + break; + case SDP_VIDEO: if (needvideo) { /* only if video response is appropriate */ add_content(resp, m_video->str); add_content(resp, a_video->str); @@ -12181,10 +12261,10 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int add_content(resp, v_a_crypto); } } else { - snprintf(dummy_answer, sizeof(dummy_answer), "m=video 0 RTP/AVP %s\r\n", p->offered_media[SDP_VIDEO].codecs); - add_content(resp, dummy_answer); + add_content(resp, offer->decline_m_line); } - } else if (p->offered_media[SDP_TEXT].order_offered == i) { + break; + case SDP_TEXT: if (needtext) { /* only if text response is appropriate */ add_content(resp, m_text->str); add_content(resp, a_text->str); @@ -12193,16 +12273,20 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int add_content(resp, t_a_crypto); } } else { - snprintf(dummy_answer, sizeof(dummy_answer), "m=text 0 RTP/AVP %s\r\n", p->offered_media[SDP_TEXT].codecs); - add_content(resp, dummy_answer); + add_content(resp, offer->decline_m_line); } - } else if (p->offered_media[SDP_IMAGE].order_offered == i) { + break; + case SDP_IMAGE: if (add_t38) { add_content(resp, m_modem->str); add_content(resp, a_modem->str); } else { - add_content(resp, "m=image 0 udptl t38\r\n"); + add_content(resp, offer->decline_m_line); } + break; + case SDP_UNKNOWN: + add_content(resp, offer->decline_m_line); + break; } } } else { @@ -12452,7 +12536,8 @@ static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int old if (p->do_history) { append_history(p, "ReInv", "Re-invite sent"); } - memset(p->offered_media, 0, sizeof(p->offered_media)); + + offered_media_list_destroy(p); try_suggested_sip_codec(p); if (t38version) { @@ -12913,7 +12998,7 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, add_diversion_header(&req, p); } if (sdp) { - memset(p->offered_media, 0, sizeof(p->offered_media)); + offered_media_list_destroy(p); if (p->udptl && p->t38.state == T38_LOCAL_REINVITE) { ast_debug(1, "T38 is in state %d on channel %s\n", p->t38.state, p->owner ? ast_channel_name(p->owner) : "<none>"); add_sdp(&req, p, FALSE, FALSE, TRUE); diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index e8f079df6deb12cc0fc10998a3459a625bbb9ca5..a9b7e02777a169b213989336e5b376cd3b0f9817 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -467,6 +467,7 @@ enum media_type { SDP_VIDEO, /*!< RTP/AVP Video */ SDP_IMAGE, /*!< Image udptl, not TCP or RTP */ SDP_TEXT, /*!< RTP/AVP Realtime Text */ + SDP_UNKNOWN, /*!< Unknown media type */ }; /*! \brief Authentication types - proxy or www authentication @@ -968,10 +969,11 @@ struct sip_st_cfg { }; /*! \brief Structure for remembering offered media in an INVITE, to make sure we reply - to all media streams. In theory. In practise, we try our best. */ + to all media streams. */ struct offered_media { - int order_offered; /*!< Order the media was offered in. Not offered is 0 */ - char codecs[128]; + enum media_type type; /*!< The type of media that was offered */ + char *decline_m_line; /*!< Used if the media type is unknown/unused or a media stream is declined */ + AST_LIST_ENTRY(offered_media) next; }; /*! Additional headers to send with MESSAGE method packet. */ @@ -1194,7 +1196,7 @@ struct sip_pvt { * * The large-scale changes would be a good idea for implementing during an SDP rewrite. */ - struct offered_media offered_media[OFFERED_MEDIA_COUNT]; + AST_LIST_HEAD_NOLOCK(, offered_media) offered_media; struct ast_cc_config_params *cc_params; struct sip_epa_entry *epa_entry; int fromdomainport; /*!< Domain port to show in from field */