diff options
-rw-r--r-- | channels/chan_sip.c | 72 | ||||
-rw-r--r-- | include/asterisk/rtp.h | 17 | ||||
-rw-r--r-- | main/rtp.c | 24 |
3 files changed, 109 insertions, 4 deletions
diff --git a/channels/chan_sip.c b/channels/chan_sip.c index f40a4302a..e198ca99a 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -7403,6 +7403,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. @@ -18882,6 +18939,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; /* must hold on to this seqno to process ack and retransmit correctly */ + 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_set_alt_peer(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_set_alt_peer(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.h b/include/asterisk/rtp.h index 19b98c30d..842b01d61 100644 --- a/include/asterisk/rtp.h +++ b/include/asterisk/rtp.h @@ -162,6 +162,23 @@ struct ast_rtp *ast_rtp_new_with_bindaddr(struct sched_context *sched, struct io void ast_rtp_set_peer(struct ast_rtp *rtp, struct sockaddr_in *them); +/*! + * \since 1.4.26 + * \brief set potential alternate source for RTP media + * + * This function may be used to give the RTP stack a hint that there is a potential + * second source of media. One case where this is used is when the SIP stack receives + * a REINVITE to which it will be replying with a 491. In such a scenario, the IP and + * port information in the SDP of that REINVITE lets us know that we may receive media + * from that source/those sources even though the SIP transaction was unable to be completed + * successfully + * + * \param rtp The RTP structure we wish to set up an alternate host/port on + * \param alt The address information for the alternate media source + * \retval void + */ +void ast_rtp_set_alt_peer(struct ast_rtp *rtp, struct sockaddr_in *alt); + /* Copies from rtp to them and returns 1 if there was a change or 0 if it was already the same */ int ast_rtp_get_peer(struct ast_rtp *rtp, struct sockaddr_in *them); diff --git a/main/rtp.c b/main/rtp.c index 6a806911c..1e6a438e0 100644 --- a/main/rtp.c +++ b/main/rtp.c @@ -146,6 +146,7 @@ struct ast_rtp { unsigned int flags; struct sockaddr_in us; /*!< Socket representation of the local endpoint. */ struct sockaddr_in them; /*!< Socket representation of the remote endpoint. */ + struct sockaddr_in altthem; /*!< Alternate source of remote media */ struct timeval rxcore; struct timeval txcore; double drxcore; /*!< The double representation of the first received packet */ @@ -230,6 +231,7 @@ struct ast_rtcp { int s; /*!< Socket */ struct sockaddr_in us; /*!< Socket representation of the local endpoint. */ struct sockaddr_in them; /*!< Socket representation of the remote endpoint. */ + struct sockaddr_in altthem; /*!< Alternate source for RTCP */ unsigned int soc; /*!< What they told us */ unsigned int spc; /*!< What they told us */ unsigned int themrxlsr; /*!< The middle 32 bits of the NTP timestamp in the last received SR*/ @@ -1207,8 +1209,10 @@ struct ast_frame *ast_rtcp_read(struct ast_rtp *rtp) if (rtp->nat) { /* Send to whoever sent to us */ - if ((rtp->rtcp->them.sin_addr.s_addr != sock_in.sin_addr.s_addr) || - (rtp->rtcp->them.sin_port != sock_in.sin_port)) { + if (((rtp->rtcp->them.sin_addr.s_addr != sock_in.sin_addr.s_addr) || + (rtp->rtcp->them.sin_port != sock_in.sin_port)) && + ((rtp->rtcp->altthem.sin_addr.s_addr != sock_in.sin_addr.s_addr) || + (rtp->rtcp->altthem.sin_port != sock_in.sin_port))) { memcpy(&rtp->rtcp->them, &sock_in, sizeof(rtp->rtcp->them)); if (option_debug || rtpdebug) ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s:%d\n", ast_inet_ntoa(rtp->rtcp->them.sin_addr), ntohs(rtp->rtcp->them.sin_port)); @@ -1639,8 +1643,10 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp) /* Send to whoever send to us if NAT is turned on */ if (rtp->nat) { - if ((rtp->them.sin_addr.s_addr != sock_in.sin_addr.s_addr) || - (rtp->them.sin_port != sock_in.sin_port)) { + if (((rtp->them.sin_addr.s_addr != sock_in.sin_addr.s_addr) || + (rtp->them.sin_port != sock_in.sin_port)) && + ((rtp->altthem.sin_addr.s_addr != sock_in.sin_addr.s_addr) || + (rtp->altthem.sin_port != sock_in.sin_port))) { rtp->them = sock_in; if (rtp->rtcp) { int h = 0; @@ -2650,6 +2656,16 @@ void ast_rtp_set_peer(struct ast_rtp *rtp, struct sockaddr_in *them) rtp->strict_rtp_state = STRICT_RTP_LEARN; } +void ast_rtp_set_alt_peer(struct ast_rtp *rtp, struct sockaddr_in *alt) +{ + rtp->altthem.sin_port = alt->sin_port; + rtp->altthem.sin_addr = alt->sin_addr; + if (rtp->rtcp) { + rtp->rtcp->altthem.sin_port = htons(ntohs(alt->sin_port) + 1); + rtp->rtcp->altthem.sin_addr = alt->sin_addr; + } +} + int ast_rtp_get_peer(struct ast_rtp *rtp, struct sockaddr_in *them) { if ((them->sin_family != AF_INET) || |