diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index 4881171351..7393d57af4 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -115,7 +115,9 @@ enum strict_rtp_state { STRICT_RTP_CLOSED, /*! Drop all RTP packets not coming from source that was learned */ }; -#define DEFAULT_STRICT_RTP STRICT_RTP_CLOSED +#define STRICT_RTP_LEARN_TIMEOUT 1500 /*!< milliseconds */ + +#define DEFAULT_STRICT_RTP -1 /*!< Enabled */ #define DEFAULT_ICESUPPORT 1 extern struct ast_srtp_res *res_srtp; @@ -199,9 +201,11 @@ static AST_LIST_HEAD_STATIC(ioqueues, ast_rtp_ioqueue_thread); /*! \brief RTP learning mode tracking information */ struct rtp_learning_info { + struct ast_sockaddr proposed_address; /*!< Proposed remote address for strict RTP */ + struct timeval start; /*!< The time learning mode was started */ + struct timeval received; /*!< The time of the last received packet */ int max_seq; /*!< The highest sequence number received */ int packets; /*!< The number of remaining packets before the source is accepted */ - struct timeval received; /*!< The time of the last received packet */ }; #ifdef HAVE_OPENSSL_SRTP @@ -223,7 +227,7 @@ struct ast_rtp { unsigned char rawdata[8192 + AST_FRIENDLY_OFFSET]; unsigned int ssrc; /*!< Synchronization source, RFC 3550, page 10. */ unsigned int themssrc; /*!< Their SSRC */ - unsigned int rxssrc; + unsigned int themssrc_valid; /*!< True if their SSRC is available. */ unsigned int lastts; unsigned int lastrxts; unsigned int lastividtimestamp; @@ -1655,8 +1659,6 @@ static struct ast_rtp_engine asterisk_rtp_engine = { #endif }; -static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq); - #ifdef HAVE_OPENSSL_SRTP static void dtls_perform_handshake(struct ast_rtp_instance *instance, struct dtls_details *dtls, int rtcp) { @@ -1685,6 +1687,8 @@ static void dtls_perform_handshake(struct ast_rtp_instance *instance, struct dtl #endif #ifdef USE_PJPROJECT +static void rtp_learning_start(struct ast_rtp *rtp); + static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status) { struct ast_rtp_instance *instance = ice->user_data; @@ -1721,8 +1725,8 @@ static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status) return; } - rtp->strict_rtp_state = STRICT_RTP_LEARN; - rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno); + ast_verb(4, "%p -- Strict RTP learning after ICE completion\n", rtp); + rtp_learning_start(rtp); } static void ast_rtp_on_ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, const pj_sockaddr_t *src_addr, unsigned src_addr_len) @@ -2355,7 +2359,7 @@ static int create_new_socket(const char *type, int af) */ static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq) { - info->max_seq = seq - 1; + info->max_seq = seq; info->packets = learning_min_sequential; memset(&info->received, 0, sizeof(info->received)); } @@ -2372,14 +2376,17 @@ static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq) */ static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t seq) { + /* + * During the learning mode the minimum amount of media we'll accept is + * 10ms so give a reasonable 5ms buffer just in case we get it sporadically. + */ if (!ast_tvzero(info->received) && ast_tvdiff_ms(ast_tvnow(), info->received) < 5) { - /* During the probation period the minimum amount of media we'll accept is - * 10ms so give a reasonable 5ms buffer just in case we get it sporadically. + /* + * Reject a flood of packets as acceptable for learning. + * Reset the needed packets. */ - return 1; - } - - if (seq == info->max_seq + 1) { + info->packets = learning_min_sequential - 1; + } else if (seq == (uint16_t) (info->max_seq + 1)) { /* packet is in sequence */ info->packets--; } else { @@ -2389,7 +2396,23 @@ static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t info->max_seq = seq; info->received = ast_tvnow(); - return (info->packets == 0); + return info->packets; +} + +/*! + * \brief Start the strictrtp learning mode. + * + * \param rtp RTP session description + * + * \return Nothing + */ +static void rtp_learning_start(struct ast_rtp *rtp) +{ + rtp->strict_rtp_state = STRICT_RTP_LEARN; + memset(&rtp->rtp_source_learn.proposed_address, 0, + sizeof(rtp->rtp_source_learn.proposed_address)); + rtp->rtp_source_learn.start = ast_tvnow(); + rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t) rtp->lastrxseqno); } #ifdef USE_PJPROJECT @@ -2546,10 +2569,7 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, /* Set default parameters on the newly created RTP structure */ rtp->ssrc = ast_random(); rtp->seqno = ast_random() & 0x7fff; - rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_LEARN : STRICT_RTP_OPEN); - if (strictrtp) { - rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno); - } + rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_CLOSED : STRICT_RTP_OPEN); /* Create a new socket for us to listen on and use */ if ((rtp->s = @@ -3867,13 +3887,86 @@ static struct ast_frame *process_cn_rfc3389(struct ast_rtp_instance *instance, u return &rtp->f; } +static const char *rtcp_payload_type2str(unsigned int pt) +{ + const char *str; + + switch (pt) { + case RTCP_PT_SR: + str = "Sender Report"; + break; + case RTCP_PT_RR: + str = "Receiver Report"; + break; + case RTCP_PT_FUR: + /* Full INTRA-frame Request / Fast Update Request */ + str = "H.261 FUR"; + break; + case RTCP_PT_SDES: + str = "Source Description"; + break; + case RTCP_PT_BYE: + str = "BYE"; + break; + default: + str = "Unknown"; + break; + } + return str; +} + +/* + * Unshifted RTCP header bit field masks + */ +#define RTCP_LENGTH_MASK 0xFFFF +#define RTCP_PAYLOAD_TYPE_MASK 0xFF +#define RTCP_REPORT_COUNT_MASK 0x1F +#define RTCP_PADDING_MASK 0x01 +#define RTCP_VERSION_MASK 0x03 + +/* + * RTCP header bit field shift offsets + */ +#define RTCP_LENGTH_SHIFT 0 +#define RTCP_PAYLOAD_TYPE_SHIFT 16 +#define RTCP_REPORT_COUNT_SHIFT 24 +#define RTCP_PADDING_SHIFT 29 +#define RTCP_VERSION_SHIFT 30 + +#define RTCP_VERSION 2U +#define RTCP_VERSION_SHIFTED (RTCP_VERSION << RTCP_VERSION_SHIFT) +#define RTCP_VERSION_MASK_SHIFTED (RTCP_VERSION_MASK << RTCP_VERSION_SHIFT) + +/* + * RTCP first packet record validity header mask and value. + * + * RFC3550 intentionally defines the encoding of RTCP_PT_SR and RTCP_PT_RR + * such that they differ in the least significant bit. Either of these two + * payload types MUST be the first RTCP packet record in a compound packet. + * + * RFC3550 checks the padding bit in the algorithm they use to check the + * RTCP packet for validity. However, we aren't masking the padding bit + * to check since we don't know if it is a compound RTCP packet or not. + */ +#define RTCP_VALID_MASK (RTCP_VERSION_MASK_SHIFTED | (((RTCP_PAYLOAD_TYPE_MASK & ~0x1)) << RTCP_PAYLOAD_TYPE_SHIFT)) +#define RTCP_VALID_VALUE (RTCP_VERSION_SHIFTED | (RTCP_PT_SR << RTCP_PAYLOAD_TYPE_SHIFT)) + +#define RTCP_SR_BLOCK_WORD_LENGTH 5 +#define RTCP_RR_BLOCK_WORD_LENGTH 6 +#define RTCP_HEADER_SSRC_LENGTH 2 + static struct ast_frame *ast_rtcp_read(struct ast_rtp_instance *instance) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_sockaddr addr; unsigned char rtcpdata[8192 + AST_FRIENDLY_OFFSET]; unsigned int *rtcpheader = (unsigned int *)(rtcpdata + AST_FRIENDLY_OFFSET); - int res, packetwords, position = 0; + int res; + unsigned int packetwords; + unsigned int position; + unsigned int first_word; + /*! True if we have seen an acceptable SSRC to learn the remote RTCP address */ + unsigned int ssrc_seen; struct ast_frame *f = &ast_null_frame; /* Read in RTCP data from the socket */ @@ -3918,56 +4011,170 @@ static struct ast_frame *ast_rtcp_read(struct ast_rtp_instance *instance) packetwords = res / 4; - ast_debug(1, "Got RTCP report of %d bytes\n", res); + ast_debug(1, "Got RTCP report of %d bytes from %s\n", + res, ast_sockaddr_stringify(&addr)); + /* + * Validate the RTCP packet according to an adapted and slightly + * modified RFC3550 validation algorithm. + */ + if (packetwords < RTCP_HEADER_SSRC_LENGTH) { + ast_debug(1, "%p -- RTCP from %s: Frame size (%u words) is too short\n", + rtp, ast_sockaddr_stringify(&addr), packetwords); + return &ast_null_frame; + } + position = 0; + first_word = ntohl(rtcpheader[position]); + if ((first_word & RTCP_VALID_MASK) != RTCP_VALID_VALUE) { + ast_debug(1, "%p -- RTCP from %s: Failed first packet validity check\n", + rtp, ast_sockaddr_stringify(&addr)); + return &ast_null_frame; + } + do { + position += ((first_word >> RTCP_LENGTH_SHIFT) & RTCP_LENGTH_MASK) + 1; + if (packetwords <= position) { + break; + } + first_word = ntohl(rtcpheader[position]); + } while ((first_word & RTCP_VERSION_MASK_SHIFTED) == RTCP_VERSION_SHIFTED); + if (position != packetwords) { + ast_debug(1, "%p -- RTCP from %s: Failed packet version or length check\n", + rtp, ast_sockaddr_stringify(&addr)); + return &ast_null_frame; + } + + /* + * Note: RFC3605 points out that true NAT (vs NAPT) can cause RTCP + * to have a different IP address and port than RTP. Otherwise, when + * strictrtp is enabled we could reject RTCP packets not coming from + * the learned RTP IP address if it is available. + */ + + /* + * strictrtp safety needs SSRC to match before we use the + * sender's address for symmetrical RTP to send our RTCP + * reports. + * + * If strictrtp is not enabled then claim to have already seen + * a matching SSRC so we'll accept this packet's address for + * symmetrical RTP. + */ + ssrc_seen = rtp->strict_rtp_state == STRICT_RTP_OPEN; + + position = 0; while (position < packetwords) { - int i, pt, rc; - unsigned int length, dlsr, lsr, msw, lsw, comp; + unsigned int i; + unsigned int pt; + unsigned int rc; + unsigned int ssrc; + /*! True if the ssrc value we have is valid and not garbage because it doesn't exist. */ + unsigned int ssrc_valid; + unsigned int length; + unsigned int min_length; + unsigned int dlsr, lsr, msw, lsw, comp; struct timeval now; double rttsec, reported_jitter, reported_normdev_jitter_current, normdevrtt_current, reported_lost, reported_normdev_lost_current; uint64_t rtt = 0; i = position; - length = ntohl(rtcpheader[i]); - pt = (length & 0xff0000) >> 16; - rc = (length & 0x1f000000) >> 24; - length &= 0xffff; - - if ((i + length) > packetwords) { - if (rtpdebug) - ast_debug(1, "RTCP Read too short\n"); + first_word = ntohl(rtcpheader[i]); + pt = (first_word >> RTCP_PAYLOAD_TYPE_SHIFT) & RTCP_PAYLOAD_TYPE_MASK; + rc = (first_word >> RTCP_REPORT_COUNT_SHIFT) & RTCP_REPORT_COUNT_MASK; + /* RFC3550 says 'length' is the number of words in the packet - 1 */ + length = ((first_word >> RTCP_LENGTH_SHIFT) & RTCP_LENGTH_MASK) + 1; + + /* Check expected RTCP packet record length */ + min_length = RTCP_HEADER_SSRC_LENGTH; + switch (pt) { + case RTCP_PT_SR: + min_length += RTCP_SR_BLOCK_WORD_LENGTH; + /* fall through */ + case RTCP_PT_RR: + min_length += (rc * RTCP_RR_BLOCK_WORD_LENGTH); + break; + case RTCP_PT_FUR: + break; + case RTCP_PT_SDES: + case RTCP_PT_BYE: + /* + * There may not be a SSRC/CSRC present. The packet is + * useless but still valid if it isn't present. + * + * We don't know what min_length should be so disable the check + */ + min_length = length; + break; + default: + ast_debug(1, "%p -- RTCP from %s: %u(%s) skipping record\n", + rtp, ast_sockaddr_stringify(&addr), pt, rtcp_payload_type2str(pt)); + if (rtcp_debug_test_addr(&addr)) { + ast_verbose("\n"); + ast_verbose("RTCP from %s: %u(%s) skipping record\n", + ast_sockaddr_stringify(&addr), pt, rtcp_payload_type2str(pt)); + } + position += length; + continue; + } + if (length < min_length) { + ast_debug(1, "%p -- RTCP from %s: %u(%s) length field less than expected minimum. Min:%u Got:%u\n", + rtp, ast_sockaddr_stringify(&addr), pt, rtcp_payload_type2str(pt), + min_length - 1, length - 1); return &ast_null_frame; } - if ((rtp->strict_rtp_state != STRICT_RTP_OPEN) && (ntohl(rtcpheader[i + 1]) != rtp->themssrc)) { - /* Skip over this RTCP record as it does not contain the correct SSRC */ - position += (length + 1); - ast_debug(1, "%p -- Received RTCP report from %s, dropping due to strict RTP protection. Received SSRC '%u' but expected '%u'\n", - rtp, ast_sockaddr_stringify(&addr), ntohl(rtcpheader[i + 1]), rtp->themssrc); - continue; + /* Get the RTCP record SSRC if defined for the record */ + ssrc_valid = 1; + switch (pt) { + case RTCP_PT_SR: + case RTCP_PT_RR: + case RTCP_PT_FUR: + ssrc = ntohl(rtcpheader[i + 1]); + break; + case RTCP_PT_SDES: + case RTCP_PT_BYE: + default: + ssrc = 0; + ssrc_valid = 0; + break; } - if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) { + if (rtcp_debug_test_addr(&addr)) { + ast_verbose("\n"); + ast_verbose("RTCP from %s\n", ast_sockaddr_stringify(&addr)); + ast_verbose("PT: %u(%s)\n", pt, rtcp_payload_type2str(pt)); + ast_verbose("Reception reports: %u\n", rc); + ast_verbose("SSRC of sender: %u\n", ssrc); + } + + if (ssrc_valid && rtp->themssrc_valid) { + if (ssrc != rtp->themssrc) { + /* + * Skip over this RTCP record as it does not contain the + * correct SSRC. We should not act upon RTCP records + * for a different stream. + */ + position += length; + ast_debug(1, "%p -- RTCP from %s: Skipping record, received SSRC '%u' != expected '%u'\n", + rtp, ast_sockaddr_stringify(&addr), ssrc, rtp->themssrc); + continue; + } + ssrc_seen = 1; + } + + if (ssrc_seen && ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) { /* Send to whoever sent to us */ if (ast_sockaddr_cmp(&rtp->rtcp->them, &addr)) { ast_sockaddr_copy(&rtp->rtcp->them, &addr); - if (rtpdebug) + if (rtpdebug) { ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_sockaddr_stringify(&addr)); + } } } - if (rtcp_debug_test_addr(&addr)) { - ast_verbose("\n\nGot RTCP from %s\n", - ast_sockaddr_stringify(&addr)); - ast_verbose("PT: %d(%s)\n", pt, (pt == 200) ? "Sender Report" : (pt == 201) ? "Receiver Report" : (pt == 192) ? "H.261 FUR" : "Unknown"); - ast_verbose("Reception reports: %d\n", rc); - ast_verbose("SSRC of sender: %u\n", rtcpheader[i + 1]); - } - - i += 2; /* Advance past header and ssrc */ + i += RTCP_HEADER_SSRC_LENGTH; /* Advance past header and ssrc */ if (rc == 0 && pt == RTCP_PT_RR) { /* We're receiving a receiver report with no reports, which is ok */ - position += (length + 1); + position += length; continue; } @@ -3983,7 +4190,7 @@ static struct ast_frame *ast_rtcp_read(struct ast_rtp_instance *instance) ast_verbose("RTP timestamp: %lu\n", (unsigned long) ntohl(rtcpheader[i + 2])); ast_verbose("SPC: %lu\tSOC: %lu\n", (unsigned long) ntohl(rtcpheader[i + 3]), (unsigned long) ntohl(rtcpheader[i + 4])); } - i += 5; + i += RTCP_SR_BLOCK_WORD_LENGTH; if (rc < 1) break; /* Intentional fall through */ @@ -4153,21 +4360,18 @@ static struct ast_frame *ast_rtcp_read(struct ast_rtp_instance *instance) case RTCP_PT_SDES: if (rtcp_debug_test_addr(&addr)) ast_verbose("Received an SDES from %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_sockaddr_stringify(&addr)); break; case RTCP_PT_BYE: if (rtcp_debug_test_addr(&addr)) ast_verbose("Received a BYE from %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_sockaddr_stringify(&addr)); break; default: - ast_debug(1, "Unknown RTCP packet (pt=%d) received from %s\n", - pt, ast_sockaddr_stringify(&rtp->rtcp->them)); break; } - position += (length + 1); + position += length; } - rtp->rtcp->rtcp_info = 1; return f; @@ -4344,39 +4548,156 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc return &ast_null_frame; } + /* If the version is not what we expected by this point then just drop the packet */ + if (version != 2) { + return &ast_null_frame; + } + /* If strict RTP protection is enabled see if we need to learn the remote address or if we need to drop the packet */ - if (rtp->strict_rtp_state == STRICT_RTP_LEARN) { - if (!ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { - /* We are learning a new address but have received traffic from the existing address, - * accept it but reset the current learning for the new source so it only takes over - * once sufficient traffic has been received. */ - rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); + switch (rtp->strict_rtp_state) { + case STRICT_RTP_LEARN: + /* + * Scenario setup: + * PartyA -- Ast1 -- Ast2 -- PartyB + * + * The learning timeout is necessary for Ast1 to handle the above + * setup where PartyA calls PartyB and Ast2 initiates direct media + * between Ast1 and PartyB. Ast1 may lock onto the Ast2 stream and + * never learn the PartyB stream when it starts. The timeout makes + * Ast1 stay in the learning state long enough to see and learn the + * RTP stream from PartyB. + * + * To mitigate against attack, the learning state cannot switch + * streams while there are competing streams. The competing streams + * interfere with each other's qualification. Once we accept a + * stream and reach the timeout, an attacker cannot interfere + * anymore. + * + * Here are a few scenarios and each one assumes that the streams + * are continuous: + * + * 1) We already have a known stream source address and the known + * stream wants to change to a new source address. An attacking + * stream will block learning the new stream source. After the + * timeout we re-lock onto the original stream source address which + * likely went away. The result is one way audio. + * + * 2) We already have a known stream source address and the known + * stream doesn't want to change source addresses. An attacking + * stream will not be able to replace the known stream. After the + * timeout we re-lock onto the known stream. The call is not + * affected. + * + * 3) We don't have a known stream source address. This presumably + * is the start of a call. Competing streams will result in staying + * in learning mode until a stream becomes the victor and we reach + * the timeout. We cannot exit learning if we have no known stream + * to lock onto. The result is one way audio until there is a victor. + * + * If we learn a stream source address before the timeout we will be + * in scenario 1) or 2) when a competing stream starts. + */ + if (!ast_sockaddr_isnull(&rtp->strict_rtp_address) + && STRICT_RTP_LEARN_TIMEOUT < ast_tvdiff_ms(ast_tvnow(), rtp->rtp_source_learn.start)) { + ast_verb(4, "%p -- Strict RTP learning complete - Locking on source address %s\n", + rtp, ast_sockaddr_stringify(&rtp->strict_rtp_address)); + rtp->strict_rtp_state = STRICT_RTP_CLOSED; + + /* + * Clear the alternate remote address after learning. + * + * We should not leave this address laying around. + * It gets set only on a chan_sip reINVITE glare. + * We don't want a stale address interfering with + * the next learning time. + */ + ast_sockaddr_setnull(&rtp->alt_rtp_address); } else { - /* Hmm, not the strict address. Perhaps we're getting audio from the alternate? */ - if (!ast_sockaddr_cmp(&rtp->alt_rtp_address, &addr)) { - /* ooh, we did! You're now the new expected address, son! */ - ast_sockaddr_copy(&rtp->strict_rtp_address, - &addr); - } else { - /* Start trying to learn from the new address. If we pass a probationary period with - * it, that means we've stopped getting RTP from the original source and we should - * switch to it. + if (!ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { + /* + * We are open to learning a new address but have received + * traffic from the current address, accept it and reset + * the learning counts for a new source. When no more + * current source packets arrive a new source can take over + * once sufficient traffic is received. */ - if (rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) { - ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Will switch to it in %d packets\n", - rtp, ast_sockaddr_stringify(&addr), rtp->rtp_source_learn.packets); - return &ast_null_frame; - } + rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); + break; + } + + /* + * We give preferential treatment to the requested remote address + * (negotiated SDP address) where we are to send our RTP. However, + * the other end has no obligation to send from that address even + * though it is practically a requirement when NAT is involved. + */ + if (!ast_sockaddr_cmp(&remote_address, &addr)) { + /* Accept the negotiated remote RTP stream as the source */ + ast_verb(4, "%p -- Strict RTP switching to RTP remote address %s as source\n", + rtp, ast_sockaddr_stringify(&addr)); + ast_sockaddr_copy(&rtp->strict_rtp_address, &addr); + rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); + break; + } + /* Treat the alternate remote address as another negotiated SDP address. */ + if (!ast_sockaddr_isnull(&rtp->alt_rtp_address) + && !ast_sockaddr_cmp(&rtp->alt_rtp_address, &addr)) { + /* ooh, we did! You're now the new expected address, son! */ + ast_verb(4, "%p -- Strict RTP switching to RTP alt remote address %s as source\n", + rtp, ast_sockaddr_stringify(&addr)); ast_sockaddr_copy(&rtp->strict_rtp_address, &addr); + rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); + break; } - ast_verb(4, "%p -- Probation passed - setting RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr)); - rtp->strict_rtp_state = STRICT_RTP_CLOSED; + /* + * Trying to learn a new address. If we pass a probationary period + * with it, that means we've stopped getting RTP from the original + * source and we should switch to it. + */ + if (!ast_sockaddr_cmp(&rtp->rtp_source_learn.proposed_address, &addr)) { + if (!rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) { + /* Accept the new RTP stream */ + ast_verb(4, "%p -- Strict RTP switching source address to %s\n", + rtp, ast_sockaddr_stringify(&addr)); + ast_sockaddr_copy(&rtp->strict_rtp_address, &addr); + rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); + break; + } + /* Not ready to accept the RTP stream candidate */ + ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Will switch to it in %d packets.\n", + rtp, ast_sockaddr_stringify(&addr), rtp->rtp_source_learn.packets); + } else { + /* + * This is either an attacking stream or + * the start of the expected new stream. + */ + ast_sockaddr_copy(&rtp->rtp_source_learn.proposed_address, &addr); + rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); + ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Qualifying new stream.\n", + rtp, ast_sockaddr_stringify(&addr)); + } + return &ast_null_frame; + } + /* Fall through */ + case STRICT_RTP_CLOSED: + /* + * We should not allow a stream address change if the SSRC matches + * once strictrtp learning is closed. Any kind of address change + * like this should have happened while we were in the learning + * state. We do not want to allow the possibility of an attacker + * interfering with the RTP stream after the learning period. + * An attacker could manage to get an RTCP packet redirected to + * them which can contain the SSRC value. + */ + if (!ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { + break; } - } else if (rtp->strict_rtp_state == STRICT_RTP_CLOSED && ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection.\n", rtp, ast_sockaddr_stringify(&addr)); return &ast_null_frame; + case STRICT_RTP_OPEN: + break; } /* If symmetric RTP is enabled see if the remote side is not what we expected and change where we are sending audio */ @@ -4401,11 +4722,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc return &ast_null_frame; } - /* If the version is not what we expected by this point then just drop the packet */ - if (version != 2) { - return &ast_null_frame; - } - /* Pull out the various other fields we will need */ payloadtype = (seqno & 0x7f0000) >> 16; padding = seqno & (1 << 29); @@ -4418,7 +4734,7 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc AST_LIST_HEAD_INIT_NOLOCK(&frames); /* Force a marker bit and change SSRC if the SSRC changes */ - if (rtp->rxssrc && rtp->rxssrc != ssrc) { + if (rtp->themssrc_valid && rtp->themssrc != ssrc) { struct ast_frame *f, srcupdate = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_SRCCHANGE, @@ -4445,8 +4761,8 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc rtp->rtcp->received_prior = 0; } } - - rtp->rxssrc = ssrc; + rtp->themssrc = ssrc; /* Record their SSRC to put in future RR */ + rtp->themssrc_valid = 1; /* Remove any padding bytes that may be present */ if (padding) { @@ -4499,10 +4815,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc prev_seqno = rtp->lastrxseqno; rtp->lastrxseqno = seqno; - if (!rtp->themssrc) { - rtp->themssrc = ntohl(rtpheader[2]); /* Record their SSRC to put in future RR */ - } - if (rtp_debug_test_addr(&addr)) { ast_verbose("Got RTP packet from %s (type %-2.2d, seq %-6.6u, ts %-6.6u, len %-6.6d)\n", ast_sockaddr_stringify(&addr), @@ -4771,13 +5083,14 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct rtp->rxseqno = 0; - if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN && !ast_sockaddr_isnull(addr) && - ast_sockaddr_cmp(addr, &rtp->strict_rtp_address)) { + if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN + && !ast_sockaddr_isnull(addr) && ast_sockaddr_cmp(addr, &rtp->strict_rtp_address)) { /* We only need to learn a new strict source address if we've been told the source is * changing to something different. */ - rtp->strict_rtp_state = STRICT_RTP_LEARN; - rtp_learning_seq_init(&rtp->rtp_source_learn, rtp->seqno); + ast_verb(4, "%p -- Strict RTP learning after remote address set to: %s\n", + rtp, ast_sockaddr_stringify(addr)); + rtp_learning_start(rtp); } #ifdef HAVE_OPENSSL_SRTP @@ -4805,7 +5118,23 @@ static void ast_rtp_alt_remote_address_set(struct ast_rtp_instance *instance, st */ ast_sockaddr_copy(&rtp->alt_rtp_address, addr); - return; + if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN + && !ast_sockaddr_isnull(addr) && ast_sockaddr_cmp(addr, &rtp->strict_rtp_address)) { + /* + * We only need to learn a new strict source address if we've been told the + * source may be changing to something different. + * + * XXX NOTE: The alternate source address is only set because of a reINVITE + * glare in chan_sip. A reINVITE glare is supposed to be retried after a + * backoff delay so it shouldn't be needed at all. However, I found this + * as the best description of why it was added: + * http://lists.digium.com/pipermail/asterisk-dev/2009-May/038348.html + * https://reviewboard.asterisk.org/r/252/ + */ + ast_verb(4, "%p -- Strict RTP learning after alternate remote address set to: %s\n", + rtp, ast_sockaddr_stringify(addr)); + rtp_learning_start(rtp); + } } /*! \brief Write t140 redundacy frame