diff --git a/channels/chan_sip.c b/channels/chan_sip.c index ec3616a22722d522431c1857aa782035b31b8a34..6795ce2eeea7e16997dc0ef1cb3d9e8c4f7d6ce6 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -7781,6 +7781,63 @@ static int find_sdp(struct sip_request *req) return FALSE; } +enum media_type { + SDP_AUDIO, + SDP_VIDEO, +}; + +static int get_ip_and_port_from_sdp(struct sip_request *req, const enum media_type media, struct sockaddr_in *sin) +{ + const char *m; + const char *c; + int miterator = req->sdp_start; + int citerator = req->sdp_start; + int x = 0; + int numberofports; + int len; + char host[258] = ""; /*Initialize to empty so we will know if we have any input */ + struct ast_hostent audiohp; + struct hostent *hp; + + c = get_sdp_iterate(&citerator, req, "c"); + if (sscanf(c, "IN IP4 %256s", host) != 1) { + ast_log(LOG_WARNING, "Invalid host in c= line, '%s'\n", c); + /* Continue since there may be a valid host in a c= line specific to the audio stream */ + } + /* We only want the m and c lines for audio */ + while ((m = get_sdp_iterate(&miterator, req, "m"))) { + if ((media == SDP_AUDIO && ((sscanf(m, "audio %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) || + (sscanf(m, "audio %d RTP/AVP %n", &x, &len) == 1 && len > 0))) || + (media == SDP_VIDEO && ((sscanf(m, "video %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) || + (sscanf(m, "video %d RTP/AVP %n", &x, &len) == 1 && len > 0)))) { + /* See if there's a c= line for this media stream. + * XXX There is no guarantee that we'll be grabbing the c= line for this + * particular media stream here. However, this is the same logic used in process_sdp. + */ + c = get_sdp_iterate(&citerator, req, "c"); + if (!ast_strlen_zero(c)) { + sscanf(c, "IN IP4 %256s", host); + } + break; + } + } + + if (ast_strlen_zero(host) || x == 0) { + ast_log(LOG_WARNING, "Failed to read an alternate host or port in SDP. Expect %s problems\n", media == SDP_AUDIO ? "audio" : "video"); + return -1; + } + + hp = ast_gethostbyname(host, &audiohp); + if (!hp) { + ast_log(LOG_WARNING, "Could not look up IP address of alternate hostname. Expect %s problems\n", media == SDP_AUDIO? "audio" : "video"); + return -1; + } + + memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr)); + sin->sin_port = htons(x); + return 0; +} + /*! \brief Process SIP SDP offer, select formats and activate RTP channels If offer is rejected, we will not change any properties of the call Return 0 on success, a negative value on errors. @@ -19907,6 +19964,21 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int } else { /* We already have a pending invite. Sorry. You are on hold. */ p->glareinvite = seqno; + if (p->rtp && find_sdp(req)) { + struct sockaddr_in sin; + if (get_ip_and_port_from_sdp(req, SDP_AUDIO, &sin)) { + ast_log(LOG_WARNING, "Failed to set an alternate media source on glared reinvite. Audio may not work properly on this call.\n"); + } else { + ast_rtp_instance_set_alt_remote_address(p->rtp, &sin); + } + if (p->vrtp) { + if (get_ip_and_port_from_sdp(req, SDP_VIDEO, &sin)) { + ast_log(LOG_WARNING, "Failed to set an alternate media source on glared reinvite. Video may not work properly on this call.\n"); + } else { + ast_rtp_instance_set_alt_remote_address(p->vrtp, &sin); + } + } + } transmit_response_reliable(p, "491 Request Pending", req); ast_debug(1, "Got INVITE on call where we already have pending INVITE, deferring that - %s\n", p->callid); /* Don't destroy dialog here */ diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index edd7d1c47cfeae7d4a19913a128e5754806fadf1..3fcc129892192e88163ebd8fc2cb72f40f788b75 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -323,6 +323,8 @@ struct ast_rtp_engine { void (*packetization_set)(struct ast_rtp_instance *instance, struct ast_codec_pref *pref); /*! Callback for setting the remote address that RTP is to be sent to */ void (*remote_address_set)(struct ast_rtp_instance *instance, struct sockaddr_in *sin); + /*! Callback for setting an alternate remote address */ + void (*alt_remote_address_set)(struct ast_rtp_instance *instance, struct sockaddr_in *sin); /*! Callback for changing DTMF mode */ int (*dtmf_mode_set)(struct ast_rtp_instance *instance, enum ast_rtp_dtmf_mode dtmf_mode); /*! Callback for retrieving statistics */ @@ -638,6 +640,28 @@ struct ast_frame *ast_rtp_instance_read(struct ast_rtp_instance *instance, int r */ int ast_rtp_instance_set_remote_address(struct ast_rtp_instance *instance, struct sockaddr_in *address); +/*! + * \brief Set the address of an an alternate RTP address to receive from + * + * \param instance The RTP instance to change the address on + * \param address Address to set it to + * + * \retval 0 success + * \retval -1 failure + * + * Example usage: + * + * \code + * ast_rtp_instance_set_alt_remote_address(instance, &sin); + * \endcode + * + * This changes the alternate remote address that RTP will be sent to on instance to the address given in the sin + * structure. + * + * \since 1.6.3 + */ +int ast_rtp_instance_set_alt_remote_address(struct ast_rtp_instance *instance, struct sockaddr_in *address); + /*! * \brief Set the address that we are expecting to receive RTP on * diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 85ee9b69fa50bf8add628f2dcbe46f70941345a4..cb4caafdd106a5d59f9f45d7dfc1bc369e7e4e2c 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -50,6 +50,8 @@ struct ast_rtp_instance { struct sockaddr_in local_address; /*! Address that we are sending RTP to */ struct sockaddr_in remote_address; + /*! Alternate address that we are receiving RTP from */ + struct sockaddr_in alt_remote_address; /*! Instance that we are bridged to if doing remote or local bridging */ struct ast_rtp_instance *bridged; /*! Payload and packetization information */ @@ -373,6 +375,20 @@ int ast_rtp_instance_set_remote_address(struct ast_rtp_instance *instance, struc return 0; } +int ast_rtp_instance_set_alt_remote_address(struct ast_rtp_instance *instance, struct sockaddr_in *address) +{ + instance->alt_remote_address.sin_addr = address->sin_addr; + instance->alt_remote_address.sin_port = address->sin_port; + + /* oink */ + + if (instance->engine->alt_remote_address_set) { + instance->engine->alt_remote_address_set(instance, &instance->alt_remote_address); + } + + return 0; +} + int ast_rtp_instance_get_local_address(struct ast_rtp_instance *instance, struct sockaddr_in *address) { if ((address->sin_family != AF_INET) || diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index ed01a834f75386f31fc71451c14017e8b1899ea0..43f8ae7d0edd64bad6bfc86ceee91cf9f073f53b 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -166,6 +166,7 @@ struct ast_rtp { enum strict_rtp_state strict_rtp_state; /*!< Current state that strict RTP protection is in */ struct sockaddr_in strict_rtp_address; /*!< Remote address information for strict RTP purposes */ + struct sockaddr_in alt_rtp_address; /*!<Alternate remote address information */ struct rtp_red *red; }; @@ -257,6 +258,7 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_property property, int value); static int ast_rtp_fd(struct ast_rtp_instance *instance, int rtcp); static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct sockaddr_in *sin); +static void ast_rtp_alt_remote_address_set(struct ast_rtp_instance *instance, struct sockaddr_in *sin); static int rtp_red_init(struct ast_rtp_instance *instance, int buffer_time, int *payloads, int generations); static int rtp_red_buffer(struct ast_rtp_instance *instance, struct ast_frame *frame); static int ast_rtp_local_bridge(struct ast_rtp_instance *instance0, struct ast_rtp_instance *instance1); @@ -278,6 +280,7 @@ static struct ast_rtp_engine asterisk_rtp_engine = { .prop_set = ast_rtp_prop_set, .fd = ast_rtp_fd, .remote_address_set = ast_rtp_remote_address_set, + .alt_remote_address_set = ast_rtp_alt_remote_address_set, .red_init = rtp_red_init, .red_buffer = rtp_red_buffer, .local_bridge = ast_rtp_local_bridge, @@ -1878,8 +1881,14 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc rtp->strict_rtp_state = STRICT_RTP_CLOSED; } else if (rtp->strict_rtp_state == STRICT_RTP_CLOSED) { if ((rtp->strict_rtp_address.sin_addr.s_addr != sin.sin_addr.s_addr) || (rtp->strict_rtp_address.sin_port != sin.sin_port)) { - ast_debug(1, "Received RTP packet from %s:%d, dropping due to strict RTP protection. Expected it to be from %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), ast_inet_ntoa(rtp->strict_rtp_address.sin_addr), ntohs(rtp->strict_rtp_address.sin_port)); - return &ast_null_frame; + /* Hmm, not the strict addres. Perhaps we're getting audio from the alternate? */ + if ((rtp->alt_rtp_address.sin_addr.s_addr == sin.sin_addr.s_addr) && (rtp->alt_rtp_address.sin_port == sin.sin_port)) { + /* ooh, we did! You're now the new expected address, son! */ + rtp->strict_rtp_address = sin; + } else { + ast_debug(1, "Received RTP packet from %s:%d, dropping due to strict RTP protection. Expected it to be from %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), ast_inet_ntoa(rtp->strict_rtp_address.sin_addr), ntohs(rtp->strict_rtp_address.sin_port)); + return &ast_null_frame; + } } } @@ -2200,6 +2209,18 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct return; } +static void ast_rtp_alt_remote_address_set(struct ast_rtp_instance *instance, struct sockaddr_in *sin) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + /* No need to futz with rtp->rtcp here because ast_rtcp_read is already able to adjust if receiving + * RTCP from an "unexpected" source + */ + rtp->alt_rtp_address = *sin; + + return; +} + /*! \brief Write t140 redundacy frame * \param data primary data to be buffered */