[Asterisk-code-review] Add rtcp-mux support (asterisk[13])
Mark Michelson
asteriskteam at digium.com
Tue Mar 7 14:24:10 CST 2017
Mark Michelson has uploaded a new change for review. ( https://gerrit.asterisk.org/5138 )
Change subject: Add rtcp-mux support
......................................................................
Add rtcp-mux support
This commit adds support for RFC 5761: Multiplexing RTP Data and Control
Packets on a Single Port. Specifically, it enables the feature when
using chan_pjsip.
A new option, "rtcp_mux" has been added to endpoint configuration in
pjsip.conf. If set, then Asterisk will attempt to use rtcp-mux with
whatever it communicates with. Asterisk follows the rules set forth in
RFC 5761 with regards to falling back to standard RTCP behavior if the
far end does not indicate support for rtcp-mux.
The lion's share of the changes in this commit are in
res_rtp_asterisk.c. This is because it was pretty much hard wired to
have an RTP and an RTCP transport. The strategy used here is that when
rtcp-mux is enabled, the current RTCP transport and its trappings (such
as DTLS SSL session) are freed, and the RTCP session instead just
mooches off the RTP session. This leads to a lot of specialized if
statements throughout.
ASTERISK-26732 #close
Reported by Dan Jenkins
Change-Id: If46a93ba1282418d2803e3fd7869374da8b77ab5
---
M include/asterisk/res_pjsip.h
M include/asterisk/res_pjsip_session.h
M include/asterisk/rtp_engine.h
M res/res_pjsip.c
M res/res_pjsip/pjsip_configuration.c
M res/res_pjsip_sdp_rtp.c
M res/res_rtp_asterisk.c
7 files changed, 327 insertions(+), 114 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/38/5138/1
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index d8e172f..328ecdf 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -672,6 +672,8 @@
unsigned int g726_non_standard;
/*! Bind the RTP instance to the media_address */
unsigned int bind_rtp_to_media_address;
+ /*! Use RTCP-MUX */
+ unsigned int rtcp_mux;
};
/*!
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index 7e65e6d..c41cc3a 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -83,6 +83,8 @@
int timeout_sched_id;
/*! \brief Stream is on hold */
unsigned int held:1;
+ /*! \brief Does remote support rtcp_mux */
+ unsigned int remote_rtcp_mux:1;
/*! \brief Stream type this session media handles */
char stream_type[1];
};
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index c4a5b6b..e8f3d78 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -237,6 +237,15 @@
AST_RTP_INSTANCE_STAT_RXOCTETCOUNT,
};
+enum ast_rtp_instance_rtcp {
+ /*! RTCP should not be sent/received */
+ AST_RTP_INSTANCE_RTCP_DISABLED = 0,
+ /*! RTCP should be sent/received based on standard port rules */
+ AST_RTP_INSTANCE_RTCP_STANDARD,
+ /*! RTCP should be sent/received on the same port as RTP */
+ AST_RTP_INSTANCE_RTCP_MUX,
+};
+
/* Codes for RTP-specific data - not defined by our AST_FORMAT codes */
/*! DTMF (RFC2833) */
#define AST_RTP_DTMF (1 << 0)
@@ -443,6 +452,8 @@
void (*turn_request)(struct ast_rtp_instance *instance, enum ast_rtp_ice_component_type component,
enum ast_transport transport, const char *server, unsigned int port,
const char *username, const char *password);
+ /*! Callback to alter the number of ICE components on a session */
+ void (*change_components)(struct ast_rtp_instance *instance, int num_components);
};
/*! \brief DTLS setup types */
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 54a0a5f..719de55 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -944,6 +944,16 @@
to the receiving one.
</para></description>
</configOption>
+ <configOption name="rtcp_mux" default="no">
+ <synopsis>Enable RFC 5761 RTCP multiplexing on the RTP port</synopsis>
+ <description><para>
+ With this option enabled, Asterisk will attempt to negotiate the use of the "rtcp-mux"
+ attribute on all media streams. This will result in RTP and RTCP being sent and received
+ on the same port. This leaves the demultiplexing logic in the application rather than
+ at the transport layer. This option is useful when interoperating with WebRTC endpoints
+ since they mandate this option's use.
+ </para></description>
+ </configOption>
</configObject>
<configObject name="auth">
<synopsis>Authentication type</synopsis>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index bfaf750..4915d80 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1938,6 +1938,7 @@
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "subscribe_context", "", OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct ast_sip_endpoint, subscription.context));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "contact_user", "", contact_user_handler, contact_user_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "asymmetric_rtp_codec", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, asymmetric_rtp_codec));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtcp_mux", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtcp_mux));
if (ast_sip_initialize_sorcery_transport()) {
ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index b27050e..dec6ecf 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -169,6 +169,23 @@
return 0;
}
+/*!
+ * \brief Enable RTCP on an RTP session.
+ */
+static void enable_rtcp(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_media *remote_media)
+{
+ enum ast_rtp_instance_rtcp rtcp_type;
+
+ if (session->endpoint->media.rtcp_mux && session_media->remote_rtcp_mux) {
+ rtcp_type = AST_RTP_INSTANCE_RTCP_MUX;
+ } else {
+ rtcp_type = AST_RTP_INSTANCE_RTCP_STANDARD;
+ }
+
+ ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, rtcp_type);
+}
+
/*! \brief Internal function which creates an RTP instance */
static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
{
@@ -186,7 +203,6 @@
return -1;
}
- ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, 1);
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_NAT, session->endpoint->media.rtp.symmetric);
if (!session->endpoint->media.rtp.ice_support && (ice = ast_rtp_instance_get_ice(session_media->rtp))) {
@@ -555,6 +571,13 @@
continue;
}
+ if (session->endpoint->media.rtcp_mux && session_media->remote_rtcp_mux && candidate.id > 1) {
+ /* Remote side may have offered RTP and RTCP candidates. However, if we're using RTCP MUX,
+ * then we should ignore RTCP candidates.
+ */
+ continue;
+ }
+
candidate.foundation = foundation;
candidate.transport = transport;
@@ -851,6 +874,26 @@
return 0;
}
+static void set_ice_components(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ struct ast_rtp_engine_ice *ice;
+
+ ast_assert(session_media->rtp != NULL);
+
+ ice = ast_rtp_instance_get_ice(session_media->rtp);
+ if (!session->endpoint->media.rtp.ice_support || !ice) {
+ return;
+ }
+
+ if (session->endpoint->media.rtcp_mux && session_media->remote_rtcp_mux) {
+ /* We both support RTCP mux. Only one ICE component necessary */
+ ice->change_components(session_media->rtp, 1);
+ } else {
+ /* They either don't support RTCP mux or we don't know if they do yet. */
+ ice->change_components(session_media->rtp, 2);
+ }
+}
+
/*! \brief Function which negotiates an incoming media stream */
static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
@@ -894,6 +937,11 @@
if (!session_media->rtp && create_rtp(session, session_media)) {
return -1;
}
+
+ session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL);
+ set_ice_components(session, session_media);
+
+ enable_rtcp(session, session_media, stream);
res = setup_media_encryption(session, session_media, sdp, stream);
if (res) {
@@ -1065,6 +1113,9 @@
return -1;
}
+ set_ice_components(session, session_media);
+ enable_rtcp(session, session_media, NULL);
+
if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) ||
!(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) {
return -1;
@@ -1228,6 +1279,12 @@
attr->name = STR_SENDRECV;
media->attr[media->attr_count++] = attr;
+ /* If we've got rtcp-mux enabled, just unconditionally offer it in all SDPs */
+ if (session->endpoint->media.rtcp_mux) {
+ attr = pjmedia_sdp_attr_create(pool, "rtcp-mux", NULL);
+ pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+ }
+
/* Add the media stream to the SDP */
sdp->media[sdp->media_count++] = media;
@@ -1262,6 +1319,11 @@
return -1;
}
+ session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL);
+ set_ice_components(session, session_media);
+
+ enable_rtcp(session, session_media, remote_stream);
+
res = setup_media_encryption(session, session_media, remote, remote_stream);
if (!session->endpoint->media.rtp.encryption_optimistic && res) {
/* If optimistic encryption is disabled and crypto should have been enabled but was not
@@ -1293,7 +1355,9 @@
return -1;
}
ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0));
- ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));
+ if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
+ ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));
+ }
/* If ICE support is enabled find all the needed attributes */
process_ice_attributes(session, session_media, remote, remote_stream);
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 346db60..4506d8c 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -331,6 +331,7 @@
struct ao2_container *ice_active_remote_candidates; /*!< The remote ICE candidates */
struct ao2_container *ice_proposed_remote_candidates; /*!< Incoming remote ICE candidates for new session */
struct ast_sockaddr ice_original_rtp_addr; /*!< rtp address that ICE started on first session */
+ int ice_num_components; /*!< The number of ICE components */
#endif
#ifdef HAVE_OPENSSL_SRTP
@@ -419,6 +420,7 @@
* own address every time
*/
char *local_addr_str;
+ enum ast_rtp_instance_rtcp type;
};
struct rtp_red {
@@ -660,6 +662,21 @@
pj_ice_sess_change_role(rtp->ice, role);
}
+ /* If we only have one component now, and we previously set up TURN for RTCP,
+ * we need to destroy that TURN socket.
+ */
+ if (rtp->ice_num_components == 1 && rtp->turn_rtcp) {
+ struct timeval wait = ast_tvadd(ast_tvnow(), ast_samp2tv(TURN_STATE_WAIT_TIME, 1000));
+ struct timespec ts = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000, };
+
+ ast_mutex_lock(&rtp->lock);
+ pj_turn_sock_destroy(rtp->turn_rtcp);
+ rtp->turn_state = PJ_TURN_STATE_NULL;
+ while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) {
+ ast_cond_timedwait(&rtp->cond, &rtp->lock, &ts);
+ }
+ }
+
return res;
}
@@ -775,11 +792,12 @@
ast_log(LOG_WARNING, "No RTP candidates; skipping ICE checklist (%p)\n", instance);
}
- if (!has_rtcp) {
+ /* If we're only dealing with one ICE component, then we don't care about the lack of RTCP candidates */
+ if (!has_rtcp && rtp->ice_num_components > 1) {
ast_log(LOG_WARNING, "No RTCP candidates; skipping ICE checklist (%p)\n", instance);
}
- if (has_rtp && has_rtcp) {
+ if (has_rtp && (has_rtcp || rtp->ice_num_components == 1)) {
pj_status_t res = pj_ice_sess_create_check_list(rtp->ice, &ufrag, &passwd, cand_cnt, &candidates[0]);
char reason[80];
@@ -1271,6 +1289,21 @@
return buf;
}
+static void ast_rtp_ice_change_components(struct ast_rtp_instance *instance, int num_components)
+{
+ struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+ /* Don't do anything if ICE is unsupported or if we're not changing the
+ * number of components
+ */
+ if (!icesupport || !rtp->ice || rtp->ice_num_components == num_components) {
+ return;
+ }
+
+ rtp->ice_num_components = num_components;
+ ice_reset_session(instance);
+}
+
/* ICE RTP Engine interface declaration */
static struct ast_rtp_engine_ice ast_rtp_ice = {
.set_authentication = ast_rtp_ice_set_authentication,
@@ -1283,6 +1316,7 @@
.ice_lite = ast_rtp_ice_lite,
.set_role = ast_rtp_ice_set_role,
.turn_request = ast_rtp_ice_turn_request,
+ .change_components = ast_rtp_ice_change_components,
};
#endif
@@ -1542,6 +1576,7 @@
static void ast_rtp_dtls_stop(struct ast_rtp_instance *instance)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+ int rtcp_dtls_unique = (rtp->dtls.ssl != rtp->rtcp->dtls.ssl);
dtls_srtp_stop_timeout_timer(instance, rtp, 0);
@@ -1559,7 +1594,7 @@
if (rtp->rtcp) {
dtls_srtp_stop_timeout_timer(instance, rtp, 1);
- if (rtp->rtcp->dtls.ssl) {
+ if (rtp->rtcp->dtls.ssl && rtcp_dtls_unique) {
SSL_free(rtp->rtcp->dtls.ssl);
rtp->rtcp->dtls.ssl = NULL;
ast_mutex_destroy(&rtp->rtcp->dtls.lock);
@@ -1787,7 +1822,7 @@
#ifdef HAVE_OPENSSL_SRTP
dtls_perform_handshake(instance, &rtp->dtls, 0);
- if (rtp->rtcp) {
+ if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
}
#endif
@@ -2027,7 +2062,7 @@
SSL_do_handshake(rtp->dtls.ssl);
dtls_srtp_check_pending(instance, rtp, 0);
- if (rtp->rtcp && rtp->rtcp->dtls.ssl) {
+ if (rtp->rtcp && rtp->rtcp->dtls.ssl && rtp->rtcp->dtls.ssl != rtp->dtls.ssl) {
SSL_renegotiate(rtp->rtcp->dtls.ssl);
SSL_do_handshake(rtp->rtcp->dtls.ssl);
dtls_srtp_check_pending(instance, rtp, 1);
@@ -2618,7 +2653,7 @@
passwd = pj_str(rtp->local_passwd);
/* Create an ICE session for ICE negotiation */
- if (pj_ice_sess_create(&stun_config, NULL, PJ_ICE_SESS_ROLE_UNKNOWN, 2,
+ if (pj_ice_sess_create(&stun_config, NULL, PJ_ICE_SESS_ROLE_UNKNOWN, rtp->ice_num_components,
&ast_rtp_ice_sess_cb, &ufrag, &passwd, NULL, &rtp->ice) == PJ_SUCCESS) {
/* Make this available for the callbacks */
rtp->ice->user_data = instance;
@@ -2627,9 +2662,10 @@
rtp_add_candidates_to_ice(instance, rtp, addr, port, AST_RTP_ICE_COMPONENT_RTP,
TRANSPORT_SOCKET_RTP);
- /* Only add the RTCP candidates to ICE when replacing the session. New sessions
+ /* Only add the RTCP candidates to ICE when replacing the session and if
+ * the ICE session contains more than just an RTP component. New sessions
* handle this in a separate part of the setup phase */
- if (replace && rtp->rtcp) {
+ if (replace && rtp->rtcp && rtp->ice_num_components > 1) {
rtp_add_candidates_to_ice(instance, rtp, &rtp->rtcp->us,
ast_sockaddr_port(&rtp->rtcp->us), AST_RTP_ICE_COMPONENT_RTCP,
TRANSPORT_SOCKET_RTCP);
@@ -2714,6 +2750,7 @@
#ifdef HAVE_PJPROJECT
/* Create an ICE session for ICE negotiation */
if (icesupport) {
+ rtp->ice_num_components = 2;
ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(addr), x, instance);
if (ice_create(instance, addr, x, 0)) {
ast_log(LOG_NOTICE, "Failed to start ICE session\n");
@@ -2723,7 +2760,6 @@
}
}
#endif
-
/* Record any information we may need */
rtp->sched = sched;
@@ -4154,58 +4190,17 @@
rtp->rtcp->reported_normdev_lost = reported_normdev_lost_current;
}
-static struct ast_frame *ast_rtcp_read(struct ast_rtp_instance *instance)
+static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, const unsigned char *rtcpdata, size_t size)
{
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;
+ unsigned int *rtcpheader = (unsigned int *)(rtcpdata);
+ int packetwords, position = 0;
int report_counter = 0;
struct ast_rtp_rtcp_report_block *report_block;
struct ast_frame *f = &ast_null_frame;
- /* Read in RTCP data from the socket */
- if ((res = rtcp_recvfrom(instance, rtcpdata + AST_FRIENDLY_OFFSET,
- sizeof(rtcpdata) - AST_FRIENDLY_OFFSET,
- 0, &addr)) < 0) {
- ast_assert(errno != EBADF);
- if (errno != EAGAIN) {
- ast_log(LOG_WARNING, "RTCP Read error: %s. Hanging up.\n",
- (errno) ? strerror(errno) : "Unspecified");
- return NULL;
- }
- return &ast_null_frame;
- }
-
- /* If this was handled by the ICE session don't do anything further */
- if (!res) {
- return &ast_null_frame;
- }
-
- if (!*(rtcpdata + AST_FRIENDLY_OFFSET)) {
- struct sockaddr_in addr_tmp;
- struct ast_sockaddr addr_v4;
-
- if (ast_sockaddr_is_ipv4(&addr)) {
- ast_sockaddr_to_sin(&addr, &addr_tmp);
- } else if (ast_sockaddr_ipv4_mapped(&addr, &addr_v4)) {
- ast_debug(1, "Using IPv6 mapped address %s for STUN\n",
- ast_sockaddr_stringify(&addr));
- ast_sockaddr_to_sin(&addr_v4, &addr_tmp);
- } else {
- ast_debug(1, "Cannot do STUN for non IPv4 address %s\n",
- ast_sockaddr_stringify(&addr));
- return &ast_null_frame;
- }
- if ((ast_stun_handle_packet(rtp->rtcp->s, &addr_tmp, rtcpdata + AST_FRIENDLY_OFFSET, res, NULL, NULL) == AST_STUN_ACCEPT)) {
- ast_sockaddr_from_sin(&addr, &addr_tmp);
- ast_sockaddr_copy(&rtp->rtcp->them, &addr);
- }
- return &ast_null_frame;
- }
-
- packetwords = res / 4;
+ packetwords = size / 4;
if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) {
/* Send to whoever sent to us */
@@ -4218,7 +4213,7 @@
}
}
- ast_debug(1, "Got RTCP report of %d bytes\n", res);
+ ast_debug(1, "Got RTCP report of %zu bytes\n", size);
while (position < packetwords) {
int i, pt, rc;
@@ -4381,6 +4376,57 @@
rtp->rtcp->rtcp_info = 1;
return f;
+
+}
+
+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];
+ int res;
+
+ /* Read in RTCP data from the socket */
+ if ((res = rtcp_recvfrom(instance, rtcpdata + AST_FRIENDLY_OFFSET,
+ sizeof(rtcpdata) - AST_FRIENDLY_OFFSET,
+ 0, &addr)) < 0) {
+ ast_assert(errno != EBADF);
+ if (errno != EAGAIN) {
+ ast_log(LOG_WARNING, "RTCP Read error: %s. Hanging up.\n",
+ (errno) ? strerror(errno) : "Unspecified");
+ return NULL;
+ }
+ return &ast_null_frame;
+ }
+
+ /* If this was handled by the ICE session don't do anything further */
+ if (!res) {
+ return &ast_null_frame;
+ }
+
+ if (!*(rtcpdata + AST_FRIENDLY_OFFSET)) {
+ struct sockaddr_in addr_tmp;
+ struct ast_sockaddr addr_v4;
+
+ if (ast_sockaddr_is_ipv4(&addr)) {
+ ast_sockaddr_to_sin(&addr, &addr_tmp);
+ } else if (ast_sockaddr_ipv4_mapped(&addr, &addr_v4)) {
+ ast_debug(1, "Using IPv6 mapped address %s for STUN\n",
+ ast_sockaddr_stringify(&addr));
+ ast_sockaddr_to_sin(&addr_v4, &addr_tmp);
+ } else {
+ ast_debug(1, "Cannot do STUN for non IPv4 address %s\n",
+ ast_sockaddr_stringify(&addr));
+ return &ast_null_frame;
+ }
+ if ((ast_stun_handle_packet(rtp->rtcp->s, &addr_tmp, rtcpdata + AST_FRIENDLY_OFFSET, res, NULL, NULL) == AST_STUN_ACCEPT)) {
+ ast_sockaddr_from_sin(&addr, &addr_tmp);
+ ast_sockaddr_copy(&rtp->rtcp->them, &addr);
+ }
+ return &ast_null_frame;
+ }
+
+ return ast_rtcp_interpret(instance, rtcpdata + AST_FRIENDLY_OFFSET, sizeof(rtcpdata) - AST_FRIENDLY_OFFSET);
}
static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance, unsigned int *rtpheader, int len, int hdrlen)
@@ -4487,19 +4533,51 @@
return 0;
}
+static int rtcp_mux(struct ast_rtp *rtp, const unsigned char *packet)
+{
+ uint8_t version;
+ uint8_t pt;
+
+ if (!rtp->rtcp || rtp->rtcp->type != AST_RTP_INSTANCE_RTCP_MUX) {
+ return 0;
+ }
+
+ version = (packet[0] & 0XC0) >> 6;
+ if (version == 0) {
+ /* version 0 indicates this is a STUN packet and shouldn't
+ * be interpreted as a possible RTCP packet
+ */
+ return 0;
+ }
+
+ /* The second octet of a packet will be one of the following:
+ * For RTP: The marker bit (1 bit) and the RTP payload type (7 bits)
+ * For RTCP: The payload type (8)
+ *
+ * RTP has a forbidden range of payload types (64-95) since these
+ * will conflict with RTCP payload numbers if the marker bit is set.
+ */
+ pt = packet[1] & 0x7F;
+ if (pt >= 64 && pt <= 95) {
+ return 1;
+ }
+ return 0;
+}
+
static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtcp)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
struct ast_sockaddr addr;
int res, hdrlen = 12, version, payloadtype, padding, mark, ext, cc, prev_seqno;
- unsigned int *rtpheader = (unsigned int*)(rtp->rawdata + AST_FRIENDLY_OFFSET), seqno, ssrc, timestamp;
+ unsigned char rtpdata[sizeof(rtp->rawdata)];
+ unsigned int *rtpheader = (unsigned int*)(rtpdata + AST_FRIENDLY_OFFSET), seqno, ssrc, timestamp;
RAII_VAR(struct ast_rtp_payload_type *, payload, NULL, ao2_cleanup);
struct ast_sockaddr remote_address = { {0,} };
struct frame_list frames;
/* If this is actually RTCP let's hop on over and handle it */
if (rtcp) {
- if (rtp->rtcp) {
+ if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
return ast_rtcp_read(instance);
}
return &ast_null_frame;
@@ -4511,8 +4589,8 @@
}
/* Actually read in the data from the socket */
- if ((res = rtp_recvfrom(instance, rtp->rawdata + AST_FRIENDLY_OFFSET,
- sizeof(rtp->rawdata) - AST_FRIENDLY_OFFSET, 0,
+ if ((res = rtp_recvfrom(instance, rtpdata + AST_FRIENDLY_OFFSET,
+ sizeof(rtpdata) - AST_FRIENDLY_OFFSET, 0,
&addr)) < 0) {
ast_assert(errno != EBADF);
if (errno != EAGAIN) {
@@ -4528,12 +4606,17 @@
return &ast_null_frame;
}
+ /* This could be a multiplexed RTCP packet. If so, be sure to interpret it correctly */
+ if (rtcp_mux(rtp, rtpdata + AST_FRIENDLY_OFFSET)) {
+ return ast_rtcp_interpret(instance, rtpdata + AST_FRIENDLY_OFFSET, sizeof(rtpdata) - AST_FRIENDLY_OFFSET);
+ }
+
/* Make sure the data that was read in is actually enough to make up an RTP packet */
if (res < hdrlen) {
/* If this is a keepalive containing only nulls, don't bother with a warning */
int i;
for (i = 0; i < res; ++i) {
- if (rtp->rawdata[AST_FRIENDLY_OFFSET + i] != '\0') {
+ if (rtpdata[AST_FRIENDLY_OFFSET + i] != '\0') {
ast_log(LOG_WARNING, "RTP Read too short\n");
return &ast_null_frame;
}
@@ -4560,7 +4643,7 @@
ast_sockaddr_stringify(&addr));
return &ast_null_frame;
}
- if ((ast_stun_handle_packet(rtp->s, &addr_tmp, rtp->rawdata + AST_FRIENDLY_OFFSET, res, NULL, NULL) == AST_STUN_ACCEPT) &&
+ if ((ast_stun_handle_packet(rtp->s, &addr_tmp, rtpdata + AST_FRIENDLY_OFFSET, res, NULL, NULL) == AST_STUN_ACCEPT) &&
ast_sockaddr_isnull(&remote_address)) {
ast_sockaddr_from_sin(&addr, &addr_tmp);
ast_rtp_instance_set_remote_address(instance, &addr);
@@ -4609,7 +4692,7 @@
/* do not update the originally given address, but only the remote */
ast_rtp_instance_set_incoming_source_address(instance, &addr);
ast_sockaddr_copy(&remote_address, &addr);
- if (rtp->rtcp) {
+ if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
ast_sockaddr_copy(&rtp->rtcp->them, &addr);
ast_sockaddr_set_port(&rtp->rtcp->them, ast_sockaddr_port(&addr) + 1);
}
@@ -4676,7 +4759,7 @@
/* Remove any padding bytes that may be present */
if (padding) {
- res -= rtp->rawdata[AST_FRIENDLY_OFFSET + res - 1];
+ res -= rtpdata[AST_FRIENDLY_OFFSET + res - 1];
}
/* Skip over any CSRC fields */
@@ -4750,11 +4833,11 @@
* by passing the pointer to the frame list to it so that the method
* can append frames to the list as needed.
*/
- process_dtmf_rfc2833(instance, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen, seqno, timestamp, &addr, payloadtype, mark, &frames);
+ process_dtmf_rfc2833(instance, rtpdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen, seqno, timestamp, &addr, payloadtype, mark, &frames);
} else if (payload->rtp_code == AST_RTP_CISCO_DTMF) {
- f = process_dtmf_cisco(instance, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen, seqno, timestamp, &addr, payloadtype, mark);
+ f = process_dtmf_cisco(instance, rtpdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen, seqno, timestamp, &addr, payloadtype, mark);
} else if (payload->rtp_code == AST_RTP_CN) {
- f = process_cn_rfc3389(instance, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen, seqno, timestamp, &addr, payloadtype, mark);
+ f = process_cn_rfc3389(instance, rtpdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen, seqno, timestamp, &addr, payloadtype, mark);
} else {
ast_log(LOG_NOTICE, "Unknown RTP codec %d received from '%s'\n",
payloadtype,
@@ -4810,7 +4893,8 @@
rtp->f.src = "RTP";
rtp->f.mallocd = 0;
rtp->f.datalen = res - hdrlen;
- rtp->f.data.ptr = rtp->rawdata + hdrlen + AST_FRIENDLY_OFFSET;
+ rtp->f.data.ptr = rtp->rawdata + AST_FRIENDLY_OFFSET;
+ memcpy(rtp->f.data.ptr, rtpdata + hdrlen + AST_FRIENDLY_OFFSET, rtp->f.datalen);
rtp->f.offset = hdrlen + AST_FRIENDLY_OFFSET;
rtp->f.seqno = seqno;
@@ -4921,19 +5005,29 @@
if (value) {
struct ast_sockaddr local_addr;
- if (rtp->rtcp) {
+ if (rtp->rtcp && rtp->rtcp->type == value) {
ast_debug(1, "Ignoring duplicate RTCP property on RTP instance '%p'\n", instance);
return;
}
- /* Setup RTCP to be activated on the next RTP write */
- if (!(rtp->rtcp = ast_calloc(1, sizeof(*rtp->rtcp)))) {
- return;
+
+ if (!rtp->rtcp) {
+ rtp->rtcp = ast_calloc(1, sizeof(*rtp->rtcp));
+ if (!rtp->rtcp) {
+ return;
+ }
+ rtp->rtcp->s = -1;
+ rtp->rtcp->dtls.timeout_timer = -1;
+ rtp->rtcp->schedid = -1;
}
+
+ rtp->rtcp->type = value;
/* Grab the IP address and port we are going to use */
ast_rtp_instance_get_local_address(instance, &rtp->rtcp->us);
- ast_sockaddr_set_port(&rtp->rtcp->us,
- ast_sockaddr_port(&rtp->rtcp->us) + 1);
+ if (value == AST_RTP_INSTANCE_RTCP_STANDARD) {
+ ast_sockaddr_set_port(&rtp->rtcp->us,
+ ast_sockaddr_port(&rtp->rtcp->us) + 1);
+ }
ast_sockaddr_copy(&local_addr, &rtp->rtcp->us);
if (!ast_find_ourip(&local_addr, &rtp->rtcp->us, 0)) {
@@ -4943,6 +5037,7 @@
ast_sockaddr_copy(&local_addr, &rtp->rtcp->us);
}
+ ast_free(rtp->rtcp->local_addr_str);
rtp->rtcp->local_addr_str = ast_strdup(ast_sockaddr_stringify(&local_addr));
if (!rtp->rtcp->local_addr_str) {
ast_free(rtp->rtcp);
@@ -4950,43 +5045,67 @@
return;
}
- if ((rtp->rtcp->s =
- create_new_socket("RTCP",
- ast_sockaddr_is_ipv4(&rtp->rtcp->us) ?
- AF_INET :
- ast_sockaddr_is_ipv6(&rtp->rtcp->us) ?
- AF_INET6 : -1)) < 0) {
- ast_debug(1, "Failed to create a new socket for RTCP on instance '%p'\n", instance);
- ast_free(rtp->rtcp->local_addr_str);
- ast_free(rtp->rtcp);
- rtp->rtcp = NULL;
- return;
- }
+ if (value == AST_RTP_INSTANCE_RTCP_STANDARD) {
+ /* We're either setting up RTCP from scratch or
+ * switching from MUX. Either way, we won't have
+ * a socket set up, and we need to set it up
+ */
+ if ((rtp->rtcp->s =
+ create_new_socket("RTCP",
+ ast_sockaddr_is_ipv4(&rtp->rtcp->us) ?
+ AF_INET :
+ ast_sockaddr_is_ipv6(&rtp->rtcp->us) ?
+ AF_INET6 : -1)) < 0) {
+ ast_debug(1, "Failed to create a new socket for RTCP on instance '%p'\n", instance);
+ ast_free(rtp->rtcp->local_addr_str);
+ ast_free(rtp->rtcp);
+ rtp->rtcp = NULL;
+ return;
+ }
- /* Try to actually bind to the IP address and port we are going to use for RTCP, if this fails we have to bail out */
- if (ast_bind(rtp->rtcp->s, &rtp->rtcp->us)) {
- ast_debug(1, "Failed to setup RTCP on RTP instance '%p'\n", instance);
- close(rtp->rtcp->s);
- ast_free(rtp->rtcp->local_addr_str);
- ast_free(rtp->rtcp);
- rtp->rtcp = NULL;
- return;
+ /* Try to actually bind to the IP address and port we are going to use for RTCP, if this fails we have to bail out */
+ if (ast_bind(rtp->rtcp->s, &rtp->rtcp->us)) {
+ ast_debug(1, "Failed to setup RTCP on RTP instance '%p'\n", instance);
+ close(rtp->rtcp->s);
+ ast_free(rtp->rtcp->local_addr_str);
+ ast_free(rtp->rtcp);
+ rtp->rtcp = NULL;
+ return;
+ }
+#ifdef HAVE_PJPROJECT
+ if (rtp->ice) {
+ rtp_add_candidates_to_ice(instance, rtp, &rtp->rtcp->us, ast_sockaddr_port(&rtp->rtcp->us), AST_RTP_ICE_COMPONENT_RTCP, TRANSPORT_SOCKET_RTCP);
+ }
+#endif
+#ifdef HAVE_OPENSSL_SRTP
+ dtls_setup_rtcp(instance);
+#endif
+ } else {
+ struct ast_sockaddr addr;
+ /* RTCPMUX uses the same socket as RTP. If we were previously using standard RTCP
+ * then close the socket we previously created.
+ *
+ * It may seem as though there is a possible race condition here where we might try
+ * to close the RTCP socket while it is being used to send data. However, this is not
+ * a problem in practice since setting and adjusting of RTCP properties happens prior
+ * to activating RTP. It is not until RTP is activated that timers start for RTCP
+ * transmission
+ */
+ if (rtp->rtcp->s > -1) {
+ close(rtp->rtcp->s);
+ }
+ rtp->rtcp->s = rtp->s;
+ ast_rtp_instance_get_remote_address(instance, &addr);
+ ast_sockaddr_copy(&rtp->rtcp->them, &addr);
+#ifdef HAVE_OPENSSL_SRTP
+ if (rtp->rtcp->dtls.ssl) {
+ SSL_free(rtp->rtcp->dtls.ssl);
+ }
+ rtp->rtcp->dtls.ssl = rtp->dtls.ssl;
+#endif
}
ast_debug(1, "Setup RTCP on RTP instance '%p'\n", instance);
- rtp->rtcp->schedid = -1;
-
-#ifdef HAVE_PJPROJECT
- if (rtp->ice) {
- rtp_add_candidates_to_ice(instance, rtp, &rtp->rtcp->us, ast_sockaddr_port(&rtp->rtcp->us), AST_RTP_ICE_COMPONENT_RTCP, TRANSPORT_SOCKET_RTCP);
- }
-#endif
-
-#ifdef HAVE_OPENSSL_SRTP
- rtp->rtcp->dtls.timeout_timer = -1;
- dtls_setup_rtcp(instance);
-#endif
-
return;
} else {
if (rtp->rtcp) {
@@ -5001,9 +5120,11 @@
}
rtp->rtcp->schedid = -1;
}
- close(rtp->rtcp->s);
+ if (rtp->rtcp->s > -1 && rtp->rtcp->s != rtp->s) {
+ close(rtp->rtcp->s);
+ }
#ifdef HAVE_OPENSSL_SRTP
- if (rtp->rtcp->dtls.ssl) {
+ if (rtp->rtcp->dtls.ssl && rtp->rtcp->dtls.ssl != rtp->dtls.ssl) {
SSL_free(rtp->rtcp->dtls.ssl);
}
#endif
@@ -5045,10 +5166,12 @@
ast_debug(1, "Setting RTCP address on RTP instance '%p'\n", instance);
ast_sockaddr_copy(&rtp->rtcp->them, addr);
if (!ast_sockaddr_isnull(addr)) {
- ast_sockaddr_set_port(&rtp->rtcp->them, ast_sockaddr_port(addr) + 1);
+ if (rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
+ ast_sockaddr_set_port(&rtp->rtcp->them, ast_sockaddr_port(addr) + 1);
- /* Update the local RTCP address with what is being used */
- ast_sockaddr_set_port(&local, ast_sockaddr_port(&local) + 1);
+ /* Update the local RTCP address with what is being used */
+ ast_sockaddr_set_port(&local, ast_sockaddr_port(&local) + 1);
+ }
ast_sockaddr_copy(&rtp->rtcp->us, &local);
ast_free(rtp->rtcp->local_addr_str);
@@ -5336,7 +5459,7 @@
dtls_perform_handshake(instance, &rtp->dtls, 0);
- if (rtp->rtcp) {
+ if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
}
--
To view, visit https://gerrit.asterisk.org/5138
To unsubscribe, visit https://gerrit.asterisk.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If46a93ba1282418d2803e3fd7869374da8b77ab5
Gerrit-PatchSet: 1
Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-Owner: Mark Michelson <mmichelson at digium.com>
More information about the asterisk-code-review
mailing list