........
res_rtp_asterisk: Add SHA-256 support for DTLS and perform DTLS negotiation on RTCP.

This change fixes up DTLS support in res_rtp_asterisk so it can accept and provide
a SHA-256 fingerprint, so it occurs on RTCP, and so it occurs after ICE negotiation
completes. Configuration options to chan_sip and chan_pjsip have also been added to
allow behavior to be tweaked (such as forcing the AVP type media transports in SDP).

ASTERISK-22961 #close
Reported by: Jay Jideliov

Review: https://reviewboard.asterisk.org/r/3679/
Review: https://reviewboard.asterisk.org/r/3686/
........

Merged revisions 417678 from http://svn.asterisk.org/svn/asterisk/branches/12


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@417679 65c4cc65-6c06-0410-ace0-fbb531ad65f3
changes/97/197/1
Joshua Colp 11 years ago
parent 688bb204dc
commit 6e60f5d317

@ -1251,7 +1251,7 @@ static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_c
static int process_sdp_a_image(const char *a, struct sip_pvt *p); static int process_sdp_a_image(const char *a, struct sip_pvt *p);
static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf);
static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf);
static void start_ice(struct ast_rtp_instance *instance); static void start_ice(struct ast_rtp_instance *instance, int offer);
static void add_codec_to_sdp(const struct sip_pvt *p, struct ast_format *codec, static void add_codec_to_sdp(const struct sip_pvt *p, struct ast_format *codec,
struct ast_str **m_buf, struct ast_str **a_buf, struct ast_str **m_buf, struct ast_str **a_buf,
int debug, int *min_packet_size, int *max_packet_size); int debug, int *min_packet_size, int *max_packet_size);
@ -10163,12 +10163,21 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
if (process_sdp_a_dtls(value, p, p->rtp)) { if (process_sdp_a_dtls(value, p, p->rtp)) {
processed = TRUE; processed = TRUE;
if (p->srtp) {
ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} }
if (process_sdp_a_dtls(value, p, p->vrtp)) { if (process_sdp_a_dtls(value, p, p->vrtp)) {
processed = TRUE; processed = TRUE;
if (p->vsrtp) {
ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} }
if (process_sdp_a_dtls(value, p, p->trtp)) { if (process_sdp_a_dtls(value, p, p->trtp)) {
processed = TRUE; processed = TRUE;
if (p->tsrtp) {
ast_set_flag(p->tsrtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} }
break; break;
@ -10576,7 +10585,11 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
if (process_sdp_a_ice(value, p, p->rtp)) { if (process_sdp_a_ice(value, p, p->rtp)) {
processed = TRUE; processed = TRUE;
} else if (process_sdp_a_dtls(value, p, p->rtp)) { } else if (process_sdp_a_dtls(value, p, p->rtp)) {
processed_crypto = TRUE;
processed = TRUE; processed = TRUE;
if (p->srtp) {
ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} else if (process_sdp_a_sendonly(value, &sendonly)) { } else if (process_sdp_a_sendonly(value, &sendonly)) {
processed = TRUE; processed = TRUE;
} else if (!processed_crypto && process_crypto(p, p->rtp, &p->srtp, value)) { } else if (!processed_crypto && process_crypto(p, p->rtp, &p->srtp, value)) {
@ -10591,7 +10604,11 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
if (process_sdp_a_ice(value, p, p->vrtp)) { if (process_sdp_a_ice(value, p, p->vrtp)) {
processed = TRUE; processed = TRUE;
} else if (process_sdp_a_dtls(value, p, p->vrtp)) { } else if (process_sdp_a_dtls(value, p, p->vrtp)) {
processed_crypto = TRUE;
processed = TRUE; processed = TRUE;
if (p->vsrtp) {
ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} else if (!processed_crypto && process_crypto(p, p->vrtp, &p->vsrtp, value)) { } else if (!processed_crypto && process_crypto(p, p->vrtp, &p->vsrtp, value)) {
processed_crypto = TRUE; processed_crypto = TRUE;
processed = TRUE; processed = TRUE;
@ -10742,7 +10759,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
/* Setup audio address and port */ /* Setup audio address and port */
if (p->rtp) { if (p->rtp) {
if (sa && portno > 0) { if (sa && portno > 0) {
start_ice(p->rtp); start_ice(p->rtp, (req->method != SIP_RESPONSE) ? 0 : 1);
ast_sockaddr_set_port(sa, portno); ast_sockaddr_set_port(sa, portno);
ast_rtp_instance_set_remote_address(p->rtp, sa); ast_rtp_instance_set_remote_address(p->rtp, sa);
if (debug) { if (debug) {
@ -10790,7 +10807,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
/* Setup video address and port */ /* Setup video address and port */
if (p->vrtp) { if (p->vrtp) {
if (vsa && vportno > 0) { if (vsa && vportno > 0) {
start_ice(p->vrtp); start_ice(p->vrtp, (req->method != SIP_RESPONSE) ? 0 : 1);
ast_sockaddr_set_port(vsa, vportno); ast_sockaddr_set_port(vsa, vportno);
ast_rtp_instance_set_remote_address(p->vrtp, vsa); ast_rtp_instance_set_remote_address(p->vrtp, vsa);
if (debug) { if (debug) {
@ -10808,7 +10825,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
/* Setup text address and port */ /* Setup text address and port */
if (p->trtp) { if (p->trtp) {
if (tsa && tportno > 0) { if (tsa && tportno > 0) {
start_ice(p->trtp); start_ice(p->trtp, (req->method != SIP_RESPONSE) ? 0 : 1);
ast_sockaddr_set_port(tsa, tportno); ast_sockaddr_set_port(tsa, tportno);
ast_rtp_instance_set_remote_address(p->trtp, tsa); ast_rtp_instance_set_remote_address(p->trtp, tsa);
if (debug) { if (debug) {
@ -11137,7 +11154,7 @@ static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_i
{ {
struct ast_rtp_engine_dtls *dtls; struct ast_rtp_engine_dtls *dtls;
int found = FALSE; int found = FALSE;
char value[256], hash[6]; char value[256], hash[32];
if (!instance || !p->dtls_cfg.enabled || !(dtls = ast_rtp_instance_get_dtls(instance))) { if (!instance || !p->dtls_cfg.enabled || !(dtls = ast_rtp_instance_get_dtls(instance))) {
return found; return found;
@ -11169,11 +11186,13 @@ static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_i
ast_log(LOG_WARNING, "Unsupported connection attribute value '%s' received on dialog '%s'\n", ast_log(LOG_WARNING, "Unsupported connection attribute value '%s' received on dialog '%s'\n",
value, p->callid); value, p->callid);
} }
} else if (sscanf(a, "fingerprint: %5s %255s", hash, value) == 2) { } else if (sscanf(a, "fingerprint: %31s %255s", hash, value) == 2) {
found = TRUE; found = TRUE;
if (!strcasecmp(hash, "sha-1")) { if (!strcasecmp(hash, "sha-1")) {
dtls->set_fingerprint(instance, AST_RTP_DTLS_HASH_SHA1, value); dtls->set_fingerprint(instance, AST_RTP_DTLS_HASH_SHA1, value);
} else if (!strcasecmp(hash, "sha-256")) {
dtls->set_fingerprint(instance, AST_RTP_DTLS_HASH_SHA256, value);
} else { } else {
ast_log(LOG_WARNING, "Unsupported fingerprint hash type '%s' received on dialog '%s'\n", ast_log(LOG_WARNING, "Unsupported fingerprint hash type '%s' received on dialog '%s'\n",
hash, p->callid); hash, p->callid);
@ -12820,7 +12839,7 @@ static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a
} }
/*! \brief Start ICE negotiation on an RTP instance */ /*! \brief Start ICE negotiation on an RTP instance */
static void start_ice(struct ast_rtp_instance *instance) static void start_ice(struct ast_rtp_instance *instance, int offer)
{ {
struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(instance); struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(instance);
@ -12828,6 +12847,8 @@ static void start_ice(struct ast_rtp_instance *instance)
return; return;
} }
/* If we are the offerer then we are the controlling agent, otherwise they are */
ice->set_role(instance, offer ? AST_RTP_ICE_ROLE_CONTROLLING : AST_RTP_ICE_ROLE_CONTROLLED);
ice->start(instance); ice->start(instance);
} }
@ -12835,6 +12856,7 @@ static void start_ice(struct ast_rtp_instance *instance)
static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf) static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf)
{ {
struct ast_rtp_engine_dtls *dtls; struct ast_rtp_engine_dtls *dtls;
enum ast_rtp_dtls_hash hash;
const char *fingerprint; const char *fingerprint;
if (!instance || !(dtls = ast_rtp_instance_get_dtls(instance)) || !dtls->active(instance)) { if (!instance || !(dtls = ast_rtp_instance_get_dtls(instance)) || !dtls->active(instance)) {
@ -12869,8 +12891,11 @@ static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **
break; break;
} }
if ((fingerprint = dtls->get_fingerprint(instance, AST_RTP_DTLS_HASH_SHA1))) { hash = dtls->get_fingerprint_hash(instance);
ast_str_append(a_buf, 0, "a=fingerprint:SHA-1 %s\r\n", fingerprint); fingerprint = dtls->get_fingerprint(instance);
if (fingerprint && (hash == AST_RTP_DTLS_HASH_SHA1 || hash == AST_RTP_DTLS_HASH_SHA256)) {
ast_str_append(a_buf, 0, "a=fingerprint:%s %s\r\n", hash == AST_RTP_DTLS_HASH_SHA1 ? "SHA-1" : "SHA-256",
fingerprint);
} }
} }
@ -13343,7 +13368,8 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32)); ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
ast_str_append(&m_video, 0, "m=video %d %s", ast_sockaddr_port(&vdest), ast_str_append(&m_video, 0, "m=video %d %s", ast_sockaddr_port(&vdest),
ast_sdp_get_rtp_profile(v_a_crypto ? 1 : 0, p->vrtp, ast_sdp_get_rtp_profile(v_a_crypto ? 1 : 0, p->vrtp,
ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF))); ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF),
ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP)));
/* Build max bitrate string */ /* Build max bitrate string */
if (p->maxcallbitrate) if (p->maxcallbitrate)
@ -13370,7 +13396,8 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32)); ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
ast_str_append(&m_text, 0, "m=text %d %s", ast_sockaddr_port(&tdest), ast_str_append(&m_text, 0, "m=text %d %s", ast_sockaddr_port(&tdest),
ast_sdp_get_rtp_profile(t_a_crypto ? 1 : 0, p->trtp, ast_sdp_get_rtp_profile(t_a_crypto ? 1 : 0, p->trtp,
ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF))); ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF),
ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP)));
if (debug) { /* XXX should I use tdest below ? */ if (debug) { /* XXX should I use tdest below ? */
ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr)); ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr));
} }
@ -13393,7 +13420,8 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32)); ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest), ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest),
ast_sdp_get_rtp_profile(a_crypto ? 1 : 0, p->rtp, ast_sdp_get_rtp_profile(a_crypto ? 1 : 0, p->rtp,
ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF))); ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF),
ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP)));
/* Now, start adding audio codecs. These are added in this order: /* Now, start adding audio codecs. These are added in this order:
- First what was requested by the calling channel - First what was requested by the calling channel
@ -30870,6 +30898,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_IGNORE_PREFCAPS); ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_IGNORE_PREFCAPS);
} else if (!strcasecmp(v->name, "discard_remote_hold_retrieval")) { } else if (!strcasecmp(v->name, "discard_remote_hold_retrieval")) {
ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL); ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL);
} else if (!strcasecmp(v->name, "force_avp")) {
ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_FORCE_AVP);
} else { } else {
ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value); ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value);
} }

@ -384,11 +384,12 @@
#define SIP_PAGE3_ICE_SUPPORT (1 << 6) /*!< DGP: Enable ICE support */ #define SIP_PAGE3_ICE_SUPPORT (1 << 6) /*!< DGP: Enable ICE support */
#define SIP_PAGE3_IGNORE_PREFCAPS (1 << 7) /*!< DP: Ignore prefcaps when setting up an outgoing call leg */ #define SIP_PAGE3_IGNORE_PREFCAPS (1 << 7) /*!< DP: Ignore prefcaps when setting up an outgoing call leg */
#define SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL (1 << 8) /*!< DGP: Stop telling the peer to start music on hold */ #define SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL (1 << 8) /*!< DGP: Stop telling the peer to start music on hold */
#define SIP_PAGE3_FORCE_AVP (1 << 9) /*!< DGP: Force 'RTP/AVP' for all streams, even DTLS */
#define SIP_PAGE3_FLAGS_TO_COPY \ #define SIP_PAGE3_FLAGS_TO_COPY \
(SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \ (SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \
SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF | SIP_PAGE3_ICE_SUPPORT | SIP_PAGE3_IGNORE_PREFCAPS | \ SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF | SIP_PAGE3_ICE_SUPPORT | SIP_PAGE3_IGNORE_PREFCAPS | \
SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL) SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP)
#define CHECK_AUTH_BUF_INITLEN 256 #define CHECK_AUTH_BUF_INITLEN 256

@ -1046,6 +1046,8 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
;avpf=yes ; Enable inter-operability with media streams using the AVPF RTP profile. ;avpf=yes ; Enable inter-operability with media streams using the AVPF RTP profile.
; This will cause all offers and answers to use AVPF (or SAVPF). This ; This will cause all offers and answers to use AVPF (or SAVPF). This
; option may be specified at the global or peer scope. ; option may be specified at the global or peer scope.
;force_avp=yes ; Force 'RTP/AVP', 'RTP/AVPF', 'RTP/SAVP', and 'RTP/SAVPF' to be used for
; media streams when appropriate, even if a DTLS stream is present.
;----------------------------------------- REALTIME SUPPORT ------------------------ ;----------------------------------------- REALTIME SUPPORT ------------------------
; For additional information on ARA, the Asterisk Realtime Architecture, ; For additional information on ARA, the Asterisk Realtime Architecture,
; please read https://wiki.asterisk.org/wiki/display/AST/Realtime+Database+Configuration ; please read https://wiki.asterisk.org/wiki/display/AST/Realtime+Database+Configuration
@ -1302,6 +1304,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; dtlscafile ; dtlscafile
; dtlscapath ; dtlscapath
; dtlssetup ; dtlssetup
; dtlsfingerprint
; ignore_requested_pref ; Ignore the requested codec and determine the preferred codec ; ignore_requested_pref ; Ignore the requested codec and determine the preferred codec
; ; from the peer's configuration. ; ; from the peer's configuration.
; ;
@ -1312,7 +1315,11 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; DTLS-SRTP support is available if the underlying RTP engine in use supports it. ; DTLS-SRTP support is available if the underlying RTP engine in use supports it.
; ;
; dtlsenable = yes ; Enable or disable DTLS-SRTP support ; dtlsenable = yes ; Enable or disable DTLS-SRTP support
; dtlsverify = yes ; Verify that the provided peer certificate is valid ; dtlsverify = yes ; Verify that provided peer certificate and fingerprint are valid
; ; A value of 'yes' will perform both certificate and fingerprint verification
; ; A value of 'no' will perform no certificate or fingerprint verification
; ; A value of 'fingerprint' will perform ONLY fingerprint verification
; ; A value of 'certificate' will perform ONLY certficiate verification
; dtlsrekey = 60 ; Interval at which to renegotiate the TLS session and rekey the SRTP session ; dtlsrekey = 60 ; Interval at which to renegotiate the TLS session and rekey the SRTP session
; ; If this is not set or the value provided is 0 rekeying will be disabled ; ; If this is not set or the value provided is 0 rekeying will be disabled
; dtlscertfile = file ; Path to certificate file to present ; dtlscertfile = file ; Path to certificate file to present
@ -1327,6 +1334,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; ; accept connections only), and actpass (we will do both). This value will be used in ; ; accept connections only), and actpass (we will do both). This value will be used in
; ; the outgoing SDP when offering and for incoming SDP offers when the remote party sends ; ; the outgoing SDP when offering and for incoming SDP offers when the remote party sends
; ; actpass ; ; actpass
; dtlsfingerprint = sha-1 ; The hash to use for the fingerprint in SDP (valid options are sha-1 and sha-256)
;[sip_proxy] ;[sip_proxy]
; For incoming calls only. Example: FWD (Free World Dialup) ; For incoming calls only. Example: FWD (Free World Dialup)

@ -0,0 +1,32 @@
"""add further dtls options
Revision ID: 51f8cb66540e
Revises: c6d929b23a8
Create Date: 2014-06-30 07:16:12.291684
"""
# revision identifiers, used by Alembic.
revision = '51f8cb66540e'
down_revision = 'c6d929b23a8'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import ENUM
YESNO_NAME = 'yesno_values'
YESNO_VALUES = ['yes', 'no']
def upgrade():
############################# Enums ##############################
# yesno_values have already been created, so use postgres enum object
# type to get around "already created" issue - works okay with mysql
yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
op.add_column('ps_endpoints', sa.Column('force_avp', yesno_values))
op.add_column('ps_endpoints', sa.Column('media_use_received_transport', yesno_values))
def downgrade():
op.drop_column('ps_endpoints', 'force_avp')
op.drop_column('ps_endpoints', 'media_use_received_transport')

@ -473,6 +473,10 @@ struct ast_sip_media_rtp_configuration {
unsigned int use_ptime; unsigned int use_ptime;
/*! Do we use AVPF exclusively for this endpoint? */ /*! Do we use AVPF exclusively for this endpoint? */
unsigned int use_avpf; unsigned int use_avpf;
/*! Do we force AVP, AVPF, SAVP, or SAVPF even for DTLS media streams? */
unsigned int force_avp;
/*! Do we use the received media transport in our answer SDP */
unsigned int use_received_transport;
/*! \brief DTLS-SRTP configuration information */ /*! \brief DTLS-SRTP configuration information */
struct ast_rtp_dtls_cfg dtls_cfg; struct ast_rtp_dtls_cfg dtls_cfg;
/*! Should SRTP use a 32 byte tag instead of an 80 byte tag? */ /*! Should SRTP use a 32 byte tag instead of an 80 byte tag? */

@ -73,6 +73,8 @@ struct ast_sip_session_media {
struct ast_sip_session_sdp_handler *handler; struct ast_sip_session_sdp_handler *handler;
/*! \brief Holds SRTP information */ /*! \brief Holds SRTP information */
struct ast_sdp_srtp *srtp; struct ast_sdp_srtp *srtp;
/*! \brief The media transport in use for this stream */
pj_str_t transport;
/*! \brief Stream is on hold */ /*! \brief Stream is on hold */
unsigned int held:1; unsigned int held:1;
/*! \brief Stream type this session media handles */ /*! \brief Stream type this session media handles */

@ -390,6 +390,12 @@ enum ast_rtp_ice_component_type {
AST_RTP_ICE_COMPONENT_RTCP = 2, AST_RTP_ICE_COMPONENT_RTCP = 2,
}; };
/*! \brief ICE role during negotiation */
enum ast_rtp_ice_role {
AST_RTP_ICE_ROLE_CONTROLLED,
AST_RTP_ICE_ROLE_CONTROLLING,
};
/*! \brief Structure for an ICE candidate */ /*! \brief Structure for an ICE candidate */
struct ast_rtp_engine_ice_candidate { struct ast_rtp_engine_ice_candidate {
char *foundation; /*!< Foundation identifier */ char *foundation; /*!< Foundation identifier */
@ -419,6 +425,8 @@ struct ast_rtp_engine_ice {
struct ao2_container *(*get_local_candidates)(struct ast_rtp_instance *instance); struct ao2_container *(*get_local_candidates)(struct ast_rtp_instance *instance);
/*! Callback for telling the ICE support that it is talking to an ice-lite implementation */ /*! Callback for telling the ICE support that it is talking to an ice-lite implementation */
void (*ice_lite)(struct ast_rtp_instance *instance); void (*ice_lite)(struct ast_rtp_instance *instance);
/*! Callback for changing our role in negotiation */
void (*set_role)(struct ast_rtp_instance *instance, enum ast_rtp_ice_role role);
}; };
/*! \brief DTLS setup types */ /*! \brief DTLS setup types */
@ -431,22 +439,31 @@ enum ast_rtp_dtls_setup {
/*! \brief DTLS connection states */ /*! \brief DTLS connection states */
enum ast_rtp_dtls_connection { enum ast_rtp_dtls_connection {
AST_RTP_DTLS_CONNECTION_NEW, /*!< Endpoint wants to use a new connection */ AST_RTP_DTLS_CONNECTION_NEW, /*!< Endpoint wants to use a new connection */
AST_RTP_DTLS_CONNECTION_EXISTING, /*!< Endpoint wishes to use existing connection */ AST_RTP_DTLS_CONNECTION_EXISTING, /*!< Endpoint wishes to use existing connection */
}; };
/*! \brief DTLS fingerprint hashes */ /*! \brief DTLS fingerprint hashes */
enum ast_rtp_dtls_hash { enum ast_rtp_dtls_hash {
AST_RTP_DTLS_HASH_SHA1, /*!< SHA-1 fingerprint hash */ AST_RTP_DTLS_HASH_SHA256, /*!< SHA-256 fingerprint hash */
AST_RTP_DTLS_HASH_SHA1, /*!< SHA-1 fingerprint hash */
};
/*! \brief DTLS verification settings */
enum ast_rtp_dtls_verify {
AST_RTP_DTLS_VERIFY_NONE = 0, /*!< Don't verify anything */
AST_RTP_DTLS_VERIFY_FINGERPRINT = (1 << 0), /*!< Verify the fingerprint */
AST_RTP_DTLS_VERIFY_CERTIFICATE = (1 << 1), /*!< Verify the certificate */
}; };
/*! \brief DTLS configuration structure */ /*! \brief DTLS configuration structure */
struct ast_rtp_dtls_cfg { struct ast_rtp_dtls_cfg {
unsigned int enabled:1; /*!< Whether DTLS support is enabled or not */ unsigned int enabled:1; /*!< Whether DTLS support is enabled or not */
unsigned int verify:1; /*!< Whether to request and verify a client certificate when acting as server */
unsigned int rekey; /*!< Interval at which to renegotiate and rekey - defaults to 0 (off) */ unsigned int rekey; /*!< Interval at which to renegotiate and rekey - defaults to 0 (off) */
enum ast_rtp_dtls_setup default_setup; /*!< Default setup type to use for outgoing */ enum ast_rtp_dtls_setup default_setup; /*!< Default setup type to use for outgoing */
enum ast_srtp_suite suite; /*!< Crypto suite in use */ enum ast_srtp_suite suite; /*!< Crypto suite in use */
enum ast_rtp_dtls_hash hash; /*!< Hash to use for fingerprint */
enum ast_rtp_dtls_verify verify; /*!< What should be verified */
char *certfile; /*!< Certificate file */ char *certfile; /*!< Certificate file */
char *pvtfile; /*!< Private key file */ char *pvtfile; /*!< Private key file */
char *cipher; /*!< Cipher to use */ char *cipher; /*!< Cipher to use */
@ -472,8 +489,10 @@ struct ast_rtp_engine_dtls {
void (*set_setup)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_setup setup); void (*set_setup)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_setup setup);
/*! Set the remote fingerprint */ /*! Set the remote fingerprint */
void (*set_fingerprint)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash, const char *fingerprint); void (*set_fingerprint)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash, const char *fingerprint);
/*! Get the local fingerprint hash type */
enum ast_rtp_dtls_hash (*get_fingerprint_hash)(struct ast_rtp_instance *instance);
/*! Get the local fingerprint */ /*! Get the local fingerprint */
const char *(*get_fingerprint)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash); const char *(*get_fingerprint)(struct ast_rtp_instance *instance);
}; };
/*! Structure that represents an RTP stack (engine) */ /*! Structure that represents an RTP stack (engine) */

@ -118,8 +118,10 @@ const char *ast_sdp_srtp_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled,
* \param sdes_active Whether the media session is using SDES-SRTP * \param sdes_active Whether the media session is using SDES-SRTP
* \param instance The RTP instance associated with this media session * \param instance The RTP instance associated with this media session
* \param using_avpf Whether the media session is using early feedback (AVPF) * \param using_avpf Whether the media session is using early feedback (AVPF)
* \param force_avp Force SAVP or SAVPF profile when DTLS is in use
* *
* \retval A non-allocated string describing the profile in use (does not need to be freed) * \retval A non-allocated string describing the profile in use (does not need to be freed)
*/ */
char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf); char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf,
unsigned int force_avp);
#endif /* _SDP_CRYPTO_H */ #endif /* _SDP_CRYPTO_H */

@ -1556,7 +1556,17 @@ int ast_rtp_dtls_cfg_parse(struct ast_rtp_dtls_cfg *dtls_cfg, const char *name,
if (!strcasecmp(name, "dtlsenable")) { if (!strcasecmp(name, "dtlsenable")) {
dtls_cfg->enabled = ast_true(value) ? 1 : 0; dtls_cfg->enabled = ast_true(value) ? 1 : 0;
} else if (!strcasecmp(name, "dtlsverify")) { } else if (!strcasecmp(name, "dtlsverify")) {
dtls_cfg->verify = ast_true(value) ? 1 : 0; if (!strcasecmp(value, "yes")) {
dtls_cfg->verify = AST_RTP_DTLS_VERIFY_FINGERPRINT | AST_RTP_DTLS_VERIFY_CERTIFICATE;
} else if (!strcasecmp(value, "fingerprint")) {
dtls_cfg->verify = AST_RTP_DTLS_VERIFY_FINGERPRINT;
} else if (!strcasecmp(value, "certificate")) {
dtls_cfg->verify = AST_RTP_DTLS_VERIFY_CERTIFICATE;
} else if (!strcasecmp(value, "no")) {
dtls_cfg->verify = AST_RTP_DTLS_VERIFY_NONE;
} else {
return -1;
}
} else if (!strcasecmp(name, "dtlsrekey")) { } else if (!strcasecmp(name, "dtlsrekey")) {
if (sscanf(value, "%30u", &dtls_cfg->rekey) != 1) { if (sscanf(value, "%30u", &dtls_cfg->rekey) != 1) {
return -1; return -1;
@ -1584,6 +1594,12 @@ int ast_rtp_dtls_cfg_parse(struct ast_rtp_dtls_cfg *dtls_cfg, const char *name,
} else if (!strcasecmp(value, "actpass")) { } else if (!strcasecmp(value, "actpass")) {
dtls_cfg->default_setup = AST_RTP_DTLS_SETUP_ACTPASS; dtls_cfg->default_setup = AST_RTP_DTLS_SETUP_ACTPASS;
} }
} else if (!strcasecmp(name, "dtlsfingerprint")) {
if (!strcasecmp(value, "sha-256")) {
dtls_cfg->hash = AST_RTP_DTLS_HASH_SHA256;
} else if (!strcasecmp(value, "sha-1")) {
dtls_cfg->hash = AST_RTP_DTLS_HASH_SHA1;
}
} else { } else {
return -1; return -1;
} }
@ -1597,6 +1613,7 @@ void ast_rtp_dtls_cfg_copy(const struct ast_rtp_dtls_cfg *src_cfg, struct ast_rt
dst_cfg->verify = src_cfg->verify; dst_cfg->verify = src_cfg->verify;
dst_cfg->rekey = src_cfg->rekey; dst_cfg->rekey = src_cfg->rekey;
dst_cfg->suite = src_cfg->suite; dst_cfg->suite = src_cfg->suite;
dst_cfg->hash = src_cfg->hash;
dst_cfg->certfile = ast_strdup(src_cfg->certfile); dst_cfg->certfile = ast_strdup(src_cfg->certfile);
dst_cfg->pvtfile = ast_strdup(src_cfg->pvtfile); dst_cfg->pvtfile = ast_strdup(src_cfg->pvtfile);
dst_cfg->cipher = ast_strdup(src_cfg->cipher); dst_cfg->cipher = ast_strdup(src_cfg->cipher);

@ -365,12 +365,17 @@ const char *ast_sdp_srtp_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled,
return NULL; return NULL;
} }
char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf) char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf,
unsigned int force_avp)
{ {
struct ast_rtp_engine_dtls *dtls; struct ast_rtp_engine_dtls *dtls;
if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) { if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) {
return using_avpf ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP"; if (force_avp) {
return using_avpf ? "RTP/SAVPF" : "RTP/SAVP";
} else {
return using_avpf ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP";
}
} else { } else {
if (using_avpf) { if (using_avpf) {
return sdes_active ? "RTP/SAVPF" : "RTP/AVPF"; return sdes_active ? "RTP/SAVPF" : "RTP/AVPF";

@ -390,6 +390,28 @@
media offers. media offers.
</para></description> </para></description>
</configOption> </configOption>
<configOption name="force_avp" default="no">
<synopsis>Determines whether res_pjsip will use and enforce usage of AVP,
regardless of the RTP profile in use for this endpoint.</synopsis>
<description><para>
If set to <literal>yes</literal>, res_pjsip will use the AVP, AVPF, SAVP, or
SAVPF RTP profile for all media offers on outbound calls and media updates including
those for DTLS-SRTP streams.
</para><para>
If set to <literal>no</literal>, res_pjsip will use the respective RTP profile
depending on configuration.
</para></description>
</configOption>
<configOption name="media_use_received_transport" default="no">
<synopsis>Determines whether res_pjsip will use the media transport received in the
offer SDP in the corresponding answer SDP.</synopsis>
<description><para>
If set to <literal>yes</literal>, res_pjsip will use the received media transport.
</para><para>
If set to <literal>no</literal>, res_pjsip will use the respective RTP profile
depending on configuration.
</para></description>
</configOption>
<configOption name="media_encryption" default="no"> <configOption name="media_encryption" default="no">
<synopsis>Determines whether res_pjsip will use and enforce usage of media encryption <synopsis>Determines whether res_pjsip will use and enforce usage of media encryption
for this endpoint.</synopsis> for this endpoint.</synopsis>

@ -1686,6 +1686,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, media_encryption_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, media_encryption_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_avpf)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_avpf));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "force_avp", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.force_avp));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "media_use_received_transport", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_received_transport));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, info.recording.enabled)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, info.recording.enabled));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, callgroup_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, callgroup_to_str, NULL, 0, 0);

@ -390,14 +390,26 @@ static void process_ice_attributes(struct ast_sip_session *session, struct ast_s
return; return;
} }
if ((attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-ufrag", NULL))) { attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-ufrag", NULL);
if (!attr) {
attr = pjmedia_sdp_attr_find2(remote->attr_count, remote->attr, "ice-ufrag", NULL);
}
if (attr) {
ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value)); ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value));
ice->set_authentication(session_media->rtp, attr_value, NULL); ice->set_authentication(session_media->rtp, attr_value, NULL);
} else {
return;
} }
if ((attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-pwd", NULL))) { attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-pwd", NULL);
if (!attr) {
pjmedia_sdp_attr_find2(remote->attr_count, remote->attr, "ice-pwd", NULL);
}
if (attr) {
ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value)); ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value));
ice->set_authentication(session_media->rtp, NULL, attr_value); ice->set_authentication(session_media->rtp, NULL, attr_value);
} else {
return;
} }
if (pjmedia_sdp_media_find_attr2(remote_stream, "ice-lite", NULL)) { if (pjmedia_sdp_media_find_attr2(remote_stream, "ice-lite", NULL)) {
@ -452,6 +464,8 @@ static void process_ice_attributes(struct ast_sip_session *session, struct ast_s
ice->add_remote_candidate(session_media->rtp, &candidate); ice->add_remote_candidate(session_media->rtp, &candidate);
} }
ice->set_role(session_media->rtp, pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_TRUE ?
AST_RTP_ICE_ROLE_CONTROLLING : AST_RTP_ICE_ROLE_CONTROLLED);
ice->start(session_media->rtp); ice->start(session_media->rtp);
} }
@ -529,6 +543,10 @@ static enum ast_sip_session_media_encryption check_endpoint_media_transport(
return incoming_encryption; return incoming_encryption;
} }
if (endpoint->media.rtp.force_avp) {
return incoming_encryption;
}
return AST_SIP_MEDIA_TRANSPORT_INVALID; return AST_SIP_MEDIA_TRANSPORT_INVALID;
} }
@ -615,13 +633,15 @@ static int parse_dtls_attrib(struct ast_sip_session_media *session_media,
ast_log(LOG_WARNING, "Unsupported connection attribute value '%*s'\n", (int)value->slen, value->ptr); ast_log(LOG_WARNING, "Unsupported connection attribute value '%*s'\n", (int)value->slen, value->ptr);
} }
} else if (!pj_strcmp2(&attr->name, "fingerprint")) { } else if (!pj_strcmp2(&attr->name, "fingerprint")) {
char hash_value[256], hash[6]; char hash_value[256], hash[32];
char fingerprint_text[value->slen + 1]; char fingerprint_text[value->slen + 1];
ast_copy_pj_str(fingerprint_text, value, sizeof(fingerprint_text)); ast_copy_pj_str(fingerprint_text, value, sizeof(fingerprint_text));
if (sscanf(fingerprint_text, "%5s %255s", hash, hash_value) == 2) { if (sscanf(fingerprint_text, "%31s %255s", hash, hash_value) == 2) {
if (!strcasecmp(hash, "sha-1")) { if (!strcasecmp(hash, "sha-1")) {
dtls->set_fingerprint(session_media->rtp, AST_RTP_DTLS_HASH_SHA1, hash_value); dtls->set_fingerprint(session_media->rtp, AST_RTP_DTLS_HASH_SHA1, hash_value);
} else if (!strcasecmp(hash, "sha-256")) {
dtls->set_fingerprint(session_media->rtp, AST_RTP_DTLS_HASH_SHA256, hash_value);
} else { } else {
ast_log(LOG_WARNING, "Unsupported fingerprint hash type '%s'\n", ast_log(LOG_WARNING, "Unsupported fingerprint hash type '%s'\n",
hash); hash);
@ -710,7 +730,8 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct
} }
/* Ensure incoming transport is compatible with the endpoint's configuration */ /* Ensure incoming transport is compatible with the endpoint's configuration */
if (check_endpoint_media_transport(session->endpoint, stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) { if (!session->endpoint->media.rtp.use_received_transport &&
check_endpoint_media_transport(session->endpoint, stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) {
return -1; return -1;
} }
@ -727,6 +748,10 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct
return -1; return -1;
} }
if (session->endpoint->media.rtp.use_received_transport) {
pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
}
if (setup_media_encryption(session, session_media, stream)) { if (setup_media_encryption(session, session_media, stream)) {
return -1; return -1;
} }
@ -748,6 +773,7 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
{ {
pj_str_t stmp; pj_str_t stmp;
pjmedia_sdp_attr *attr; pjmedia_sdp_attr *attr;
enum ast_rtp_dtls_hash hash;
const char *crypto_attribute; const char *crypto_attribute;
struct ast_rtp_engine_dtls *dtls; struct ast_rtp_engine_dtls *dtls;
static const pj_str_t STR_NEW = { "new", 3 }; static const pj_str_t STR_NEW = { "new", 3 };
@ -824,13 +850,19 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
break; break;
} }
if ((crypto_attribute = dtls->get_fingerprint(session_media->rtp, AST_RTP_DTLS_HASH_SHA1))) { hash = dtls->get_fingerprint_hash(session_media->rtp);
crypto_attribute = dtls->get_fingerprint(session_media->rtp);
if (crypto_attribute && (hash == AST_RTP_DTLS_HASH_SHA1 || hash == AST_RTP_DTLS_HASH_SHA256)) {
RAII_VAR(struct ast_str *, fingerprint, ast_str_create(64), ast_free); RAII_VAR(struct ast_str *, fingerprint, ast_str_create(64), ast_free);
if (!fingerprint) { if (!fingerprint) {
return -1; return -1;
} }
ast_str_set(&fingerprint, 0, "SHA-1 %s", crypto_attribute); if (hash == AST_RTP_DTLS_HASH_SHA1) {
ast_str_set(&fingerprint, 0, "SHA-1 %s", crypto_attribute);
} else {
ast_str_set(&fingerprint, 0, "SHA-256 %s", crypto_attribute);
}
attr = pjmedia_sdp_attr_create(pool, "fingerprint", pj_cstr(&stmp, ast_str_buffer(fingerprint))); attr = pjmedia_sdp_attr_create(pool, "fingerprint", pj_cstr(&stmp, ast_str_buffer(fingerprint)));
media->attr[media->attr_count++] = attr; media->attr[media->attr_count++] = attr;
@ -889,9 +921,14 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
} }
media->desc.media = pj_str(session_media->stream_type); media->desc.media = pj_str(session_media->stream_type);
media->desc.transport = pj_str(ast_sdp_get_rtp_profile( if (session->endpoint->media.rtp.use_received_transport && pj_strlen(&session_media->transport)) {
session->endpoint->media.rtp.encryption == AST_SIP_MEDIA_ENCRYPT_SDES, media->desc.transport = session_media->transport;
session_media->rtp, session->endpoint->media.rtp.use_avpf)); } else {
media->desc.transport = pj_str(ast_sdp_get_rtp_profile(
session->endpoint->media.rtp.encryption == AST_SIP_MEDIA_ENCRYPT_SDES,
session_media->rtp, session->endpoint->media.rtp.use_avpf,
session->endpoint->media.rtp.force_avp));
}
/* Add connection level details */ /* Add connection level details */
if (direct_media_enabled) { if (direct_media_enabled) {
@ -1033,7 +1070,8 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
} }
/* Ensure incoming transport is compatible with the endpoint's configuration */ /* Ensure incoming transport is compatible with the endpoint's configuration */
if (check_endpoint_media_transport(session->endpoint, remote_stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) { if (!session->endpoint->media.rtp.use_received_transport &&
check_endpoint_media_transport(session->endpoint, remote_stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) {
return -1; return -1;
} }

@ -181,6 +181,16 @@ struct rtp_learning_info {
int packets; /*!< The number of remaining packets before the source is accepted */ int packets; /*!< The number of remaining packets before the source is accepted */
}; };
#ifdef HAVE_OPENSSL_SRTP
struct dtls_details {
SSL *ssl; /*!< SSL session */
BIO *read_bio; /*!< Memory buffer for reading */
BIO *write_bio; /*!< Memory buffer for writing */
enum ast_rtp_dtls_setup dtls_setup; /*!< Current setup state */
enum ast_rtp_dtls_connection connection; /*!< Whether this is a new or existing connection */
};
#endif
/*! \brief RTP session description */ /*! \brief RTP session description */
struct ast_rtp { struct ast_rtp {
int s; int s;
@ -280,19 +290,17 @@ struct ast_rtp {
#ifdef HAVE_OPENSSL_SRTP #ifdef HAVE_OPENSSL_SRTP
SSL_CTX *ssl_ctx; /*!< SSL context */ SSL_CTX *ssl_ctx; /*!< SSL context */
SSL *ssl; /*!< SSL session */
BIO *read_bio; /*!< Memory buffer for reading */
BIO *write_bio; /*!< Memory buffer for writing */
ast_mutex_t dtls_timer_lock; /*!< Lock for synchronization purposes */ ast_mutex_t dtls_timer_lock; /*!< Lock for synchronization purposes */
enum ast_rtp_dtls_setup dtls_setup; /*!< Current setup state */ enum ast_rtp_dtls_verify dtls_verify; /*!< What to verify */
enum ast_srtp_suite suite; /*!< SRTP crypto suite */ enum ast_srtp_suite suite; /*!< SRTP crypto suite */
enum ast_rtp_dtls_hash local_hash; /*!< Local hash used for the fingerprint */
char local_fingerprint[160]; /*!< Fingerprint of our certificate */ char local_fingerprint[160]; /*!< Fingerprint of our certificate */
enum ast_rtp_dtls_hash remote_hash; /*!< Remote hash used for the fingerprint */
unsigned char remote_fingerprint[EVP_MAX_MD_SIZE]; /*!< Fingerprint of the peer certificate */ unsigned char remote_fingerprint[EVP_MAX_MD_SIZE]; /*!< Fingerprint of the peer certificate */
enum ast_rtp_dtls_connection connection; /*!< Whether this is a new or existing connection */
unsigned int dtls_failure:1; /*!< Failure occurred during DTLS negotiation */
unsigned int rekey; /*!< Interval at which to renegotiate and rekey */ unsigned int rekey; /*!< Interval at which to renegotiate and rekey */
int rekeyid; /*!< Scheduled item id for rekeying */ int rekeyid; /*!< Scheduled item id for rekeying */
int dtlstimerid; /*!< Scheduled item id for DTLS retransmission for RTP */ int dtlstimerid; /*!< Scheduled item id for DTLS retransmission for RTP */
struct dtls_details dtls; /*!< DTLS state information */
#endif #endif
}; };
@ -358,6 +366,10 @@ struct ast_rtcp {
/* VP8: sequence number for the RTCP FIR FCI */ /* VP8: sequence number for the RTCP FIR FCI */
int firseq; int firseq;
#ifdef HAVE_OPENSSL_SRTP
struct dtls_details dtls; /*!< DTLS state information */
#endif
}; };
struct rtp_red { struct rtp_red {
@ -404,7 +416,7 @@ static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level);
#ifdef HAVE_OPENSSL_SRTP #ifdef HAVE_OPENSSL_SRTP
static int ast_rtp_activate(struct ast_rtp_instance *instance); static int ast_rtp_activate(struct ast_rtp_instance *instance);
static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct ast_rtp *rtp); static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct ast_rtp *rtp, int rtcp);
#endif #endif
static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice, int use_srtp); static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice, int use_srtp);
@ -542,9 +554,18 @@ static void ast_rtp_ice_stop(struct ast_rtp_instance *instance)
static int ice_reset_session(struct ast_rtp_instance *instance) static int ice_reset_session(struct ast_rtp_instance *instance)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
pj_ice_sess_role role = rtp->ice->role;
int res;
ast_rtp_ice_stop(instance); ast_rtp_ice_stop(instance);
return ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1);
res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1);
if (!res) {
/* Preserve the role that the old ICE session used */
pj_ice_sess_change_role(rtp->ice, role);
}
return res;
} }
static int ice_candidates_compare(struct ao2_container *left, struct ao2_container *right) static int ice_candidates_compare(struct ao2_container *left, struct ao2_container *right)
@ -701,6 +722,20 @@ static void ast_rtp_ice_lite(struct ast_rtp_instance *instance)
pj_ice_sess_change_role(rtp->ice, PJ_ICE_SESS_ROLE_CONTROLLING); pj_ice_sess_change_role(rtp->ice, PJ_ICE_SESS_ROLE_CONTROLLING);
} }
static void ast_rtp_ice_set_role(struct ast_rtp_instance *instance, enum ast_rtp_ice_role role)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (!rtp->ice) {
return;
}
pj_thread_register_check();
pj_ice_sess_change_role(rtp->ice, role == AST_RTP_ICE_ROLE_CONTROLLED ?
PJ_ICE_SESS_ROLE_CONTROLLED : PJ_ICE_SESS_ROLE_CONTROLLING);
}
static void ast_rtp_ice_add_cand(struct ast_rtp *rtp, unsigned comp_id, unsigned transport_id, pj_ice_cand_type type, pj_uint16_t local_pref, static void ast_rtp_ice_add_cand(struct ast_rtp *rtp, unsigned comp_id, unsigned transport_id, pj_ice_cand_type type, pj_uint16_t local_pref,
const pj_sockaddr_t *addr, const pj_sockaddr_t *base_addr, const pj_sockaddr_t *rel_addr, int addr_len) const pj_sockaddr_t *addr, const pj_sockaddr_t *base_addr, const pj_sockaddr_t *rel_addr, int addr_len)
{ {
@ -781,25 +816,83 @@ static struct ast_rtp_engine_ice ast_rtp_ice = {
.get_password = ast_rtp_ice_get_password, .get_password = ast_rtp_ice_get_password,
.get_local_candidates = ast_rtp_ice_get_local_candidates, .get_local_candidates = ast_rtp_ice_get_local_candidates,
.ice_lite = ast_rtp_ice_lite, .ice_lite = ast_rtp_ice_lite,
.set_role = ast_rtp_ice_set_role,
}; };
#endif #endif
#ifdef HAVE_OPENSSL_SRTP #ifdef HAVE_OPENSSL_SRTP
static void dtls_info_callback(const SSL *ssl, int where, int ret) static int dtls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
/* We don't want to actually verify the certificate so just accept what they have provided */
return 1;
}
static int dtls_details_initialize(struct dtls_details *dtls, SSL_CTX *ssl_ctx,
enum ast_rtp_dtls_setup setup)
{ {
struct ast_rtp *rtp = SSL_get_ex_data(ssl, 0); dtls->dtls_setup = setup;
/* We only care about alerts */ if (!(dtls->ssl = SSL_new(ssl_ctx))) {
if (!(where & SSL_CB_ALERT)) { ast_log(LOG_ERROR, "Failed to allocate memory for SSL\n");
return; goto error;
}
if (!(dtls->read_bio = BIO_new(BIO_s_mem()))) {
ast_log(LOG_ERROR, "Failed to allocate memory for inbound SSL traffic\n");
goto error;
}
BIO_set_mem_eof_return(dtls->read_bio, -1);
if (!(dtls->write_bio = BIO_new(BIO_s_mem()))) {
ast_log(LOG_ERROR, "Failed to allocate memory for outbound SSL traffic\n");
goto error;
} }
BIO_set_mem_eof_return(dtls->write_bio, -1);
SSL_set_bio(dtls->ssl, dtls->read_bio, dtls->write_bio);
if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
SSL_set_accept_state(dtls->ssl);
} else {
SSL_set_connect_state(dtls->ssl);
}
dtls->connection = AST_RTP_DTLS_CONNECTION_NEW;
return 0;
rtp->dtls_failure = 1; error:
if (dtls->read_bio) {
BIO_free(dtls->read_bio);
dtls->read_bio = NULL;
}
if (dtls->write_bio) {
BIO_free(dtls->write_bio);
dtls->write_bio = NULL;
}
if (dtls->ssl) {
SSL_free(dtls->ssl);
dtls->ssl = NULL;
}
return -1;
}
static int dtls_setup_rtcp(struct ast_rtp_instance *instance)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (!rtp->ssl_ctx || !rtp->rtcp) {
return 0;
}
return dtls_details_initialize(&rtp->rtcp->dtls, rtp->ssl_ctx, rtp->dtls.dtls_setup);
} }
static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, const struct ast_rtp_dtls_cfg *dtls_cfg) static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, const struct ast_rtp_dtls_cfg *dtls_cfg)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
int res;
if (!dtls_cfg->enabled) { if (!dtls_cfg->enabled) {
return 0; return 0;
@ -813,7 +906,11 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con
return -1; return -1;
} }
SSL_CTX_set_verify(rtp->ssl_ctx, dtls_cfg->verify ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE, NULL); rtp->dtls_verify = dtls_cfg->verify;
SSL_CTX_set_verify(rtp->ssl_ctx, (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) || (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_CERTIFICATE) ?
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE, !(rtp->dtls_verify & AST_RTP_DTLS_VERIFY_CERTIFICATE) ?
dtls_verify_callback : NULL);
if (dtls_cfg->suite == AST_AES_CM_128_HMAC_SHA1_80) { if (dtls_cfg->suite == AST_AES_CM_128_HMAC_SHA1_80) {
SSL_CTX_set_tlsext_use_srtp(rtp->ssl_ctx, "SRTP_AES128_CM_SHA1_80"); SSL_CTX_set_tlsext_use_srtp(rtp->ssl_ctx, "SRTP_AES128_CM_SHA1_80");
@ -821,13 +918,16 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con
SSL_CTX_set_tlsext_use_srtp(rtp->ssl_ctx, "SRTP_AES128_CM_SHA1_32"); SSL_CTX_set_tlsext_use_srtp(rtp->ssl_ctx, "SRTP_AES128_CM_SHA1_32");
} else { } else {
ast_log(LOG_ERROR, "Unsupported suite specified for DTLS-SRTP on RTP instance '%p'\n", instance); ast_log(LOG_ERROR, "Unsupported suite specified for DTLS-SRTP on RTP instance '%p'\n", instance);
goto error; return -1;
} }
rtp->local_hash = dtls_cfg->hash;
if (!ast_strlen_zero(dtls_cfg->certfile)) { if (!ast_strlen_zero(dtls_cfg->certfile)) {
char *private = ast_strlen_zero(dtls_cfg->pvtfile) ? dtls_cfg->certfile : dtls_cfg->pvtfile; char *private = ast_strlen_zero(dtls_cfg->pvtfile) ? dtls_cfg->certfile : dtls_cfg->pvtfile;
BIO *certbio; BIO *certbio;
X509 *cert; X509 *cert;
const EVP_MD *type;
unsigned int size, i; unsigned int size, i;
unsigned char fingerprint[EVP_MAX_MD_SIZE]; unsigned char fingerprint[EVP_MAX_MD_SIZE];
char *local_fingerprint = rtp->local_fingerprint; char *local_fingerprint = rtp->local_fingerprint;
@ -835,30 +935,40 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con
if (!SSL_CTX_use_certificate_file(rtp->ssl_ctx, dtls_cfg->certfile, SSL_FILETYPE_PEM)) { if (!SSL_CTX_use_certificate_file(rtp->ssl_ctx, dtls_cfg->certfile, SSL_FILETYPE_PEM)) {
ast_log(LOG_ERROR, "Specified certificate file '%s' for RTP instance '%p' could not be used\n", ast_log(LOG_ERROR, "Specified certificate file '%s' for RTP instance '%p' could not be used\n",
dtls_cfg->certfile, instance); dtls_cfg->certfile, instance);
goto error; return -1;
} }
if (!SSL_CTX_use_PrivateKey_file(rtp->ssl_ctx, private, SSL_FILETYPE_PEM) || if (!SSL_CTX_use_PrivateKey_file(rtp->ssl_ctx, private, SSL_FILETYPE_PEM) ||
!SSL_CTX_check_private_key(rtp->ssl_ctx)) { !SSL_CTX_check_private_key(rtp->ssl_ctx)) {
ast_log(LOG_ERROR, "Specified private key file '%s' for RTP instance '%p' could not be used\n", ast_log(LOG_ERROR, "Specified private key file '%s' for RTP instance '%p' could not be used\n",
private, instance); private, instance);
goto error; return -1;
} }
if (!(certbio = BIO_new(BIO_s_file()))) { if (!(certbio = BIO_new(BIO_s_file()))) {
ast_log(LOG_ERROR, "Failed to allocate memory for certificate fingerprinting on RTP instance '%p'\n", ast_log(LOG_ERROR, "Failed to allocate memory for certificate fingerprinting on RTP instance '%p'\n",
instance); instance);
goto error; return -1;
}
if (rtp->local_hash == AST_RTP_DTLS_HASH_SHA1) {
type = EVP_sha1();
} else if (rtp->local_hash == AST_RTP_DTLS_HASH_SHA256) {
type = EVP_sha256();
} else {
ast_log(LOG_ERROR, "Unsupported fingerprint hash type on RTP instance '%p'\n",
instance);
return -1;
} }
if (!BIO_read_filename(certbio, dtls_cfg->certfile) || if (!BIO_read_filename(certbio, dtls_cfg->certfile) ||
!(cert = PEM_read_bio_X509(certbio, NULL, 0, NULL)) || !(cert = PEM_read_bio_X509(certbio, NULL, 0, NULL)) ||
!X509_digest(cert, EVP_sha1(), fingerprint, &size) || !X509_digest(cert, type, fingerprint, &size) ||
!size) { !size) {
ast_log(LOG_ERROR, "Could not produce fingerprint from certificate '%s' for RTP instance '%p'\n", ast_log(LOG_ERROR, "Could not produce fingerprint from certificate '%s' for RTP instance '%p'\n",
dtls_cfg->certfile, instance); dtls_cfg->certfile, instance);
BIO_free_all(certbio); BIO_free_all(certbio);
goto error; return -1;
} }
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
@ -875,7 +985,7 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con
if (!SSL_CTX_set_cipher_list(rtp->ssl_ctx, dtls_cfg->cipher)) { if (!SSL_CTX_set_cipher_list(rtp->ssl_ctx, dtls_cfg->cipher)) {
ast_log(LOG_ERROR, "Invalid cipher specified in cipher list '%s' for RTP instance '%p'\n", ast_log(LOG_ERROR, "Invalid cipher specified in cipher list '%s' for RTP instance '%p'\n",
dtls_cfg->cipher, instance); dtls_cfg->cipher, instance);
goto error; return -1;
} }
} }
@ -883,69 +993,19 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con
if (!SSL_CTX_load_verify_locations(rtp->ssl_ctx, S_OR(dtls_cfg->cafile, NULL), S_OR(dtls_cfg->capath, NULL))) { if (!SSL_CTX_load_verify_locations(rtp->ssl_ctx, S_OR(dtls_cfg->cafile, NULL), S_OR(dtls_cfg->capath, NULL))) {
ast_log(LOG_ERROR, "Invalid certificate authority file '%s' or path '%s' specified for RTP instance '%p'\n", ast_log(LOG_ERROR, "Invalid certificate authority file '%s' or path '%s' specified for RTP instance '%p'\n",
S_OR(dtls_cfg->cafile, ""), S_OR(dtls_cfg->capath, ""), instance); S_OR(dtls_cfg->cafile, ""), S_OR(dtls_cfg->capath, ""), instance);
goto error; return -1;
} }
} }
rtp->rekey = dtls_cfg->rekey; rtp->rekey = dtls_cfg->rekey;
rtp->dtls_setup = dtls_cfg->default_setup;
rtp->suite = dtls_cfg->suite; rtp->suite = dtls_cfg->suite;
if (!(rtp->ssl = SSL_new(rtp->ssl_ctx))) { res = dtls_details_initialize(&rtp->dtls, rtp->ssl_ctx, dtls_cfg->default_setup);
ast_log(LOG_ERROR, "Failed to allocate memory for SSL context on RTP instance '%p'\n", if (!res) {
instance); dtls_setup_rtcp(instance);
goto error;
}
SSL_set_ex_data(rtp->ssl, 0, rtp);
SSL_set_info_callback(rtp->ssl, dtls_info_callback);
if (!(rtp->read_bio = BIO_new(BIO_s_mem()))) {
ast_log(LOG_ERROR, "Failed to allocate memory for inbound SSL traffic on RTP instance '%p'\n",
instance);
goto error;
}
BIO_set_mem_eof_return(rtp->read_bio, -1);
if (!(rtp->write_bio = BIO_new(BIO_s_mem()))) {
ast_log(LOG_ERROR, "Failed to allocate memory for outbound SSL traffic on RTP instance '%p'\n",
instance);
goto error;
}
BIO_set_mem_eof_return(rtp->write_bio, -1);
SSL_set_bio(rtp->ssl, rtp->read_bio, rtp->write_bio);
if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
SSL_set_accept_state(rtp->ssl);
} else {
SSL_set_connect_state(rtp->ssl);
}
rtp->connection = AST_RTP_DTLS_CONNECTION_NEW;
return 0;
error:
if (rtp->read_bio) {
BIO_free(rtp->read_bio);
rtp->read_bio = NULL;
}
if (rtp->write_bio) {
BIO_free(rtp->write_bio);
rtp->write_bio = NULL;
}
if (rtp->ssl) {
SSL_free(rtp->ssl);
rtp->ssl = NULL;
} }
SSL_CTX_free(rtp->ssl_ctx); return res;
rtp->ssl_ctx = NULL;
return -1;
} }
static int ast_rtp_dtls_active(struct ast_rtp_instance *instance) static int ast_rtp_dtls_active(struct ast_rtp_instance *instance)
@ -964,9 +1024,14 @@ static void ast_rtp_dtls_stop(struct ast_rtp_instance *instance)
rtp->ssl_ctx = NULL; rtp->ssl_ctx = NULL;
} }
if (rtp->ssl) { if (rtp->dtls.ssl) {
SSL_free(rtp->ssl); SSL_free(rtp->dtls.ssl);
rtp->ssl = NULL; rtp->dtls.ssl = NULL;
}
if (rtp->rtcp && rtp->rtcp->dtls.ssl) {
SSL_free(rtp->rtcp->dtls.ssl);
rtp->rtcp->dtls.ssl = NULL;
} }
} }
@ -974,49 +1039,50 @@ static void ast_rtp_dtls_reset(struct ast_rtp_instance *instance)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
/* If the SSL session is not yet finalized don't bother resetting */ if (SSL_is_init_finished(rtp->dtls.ssl)) {
if (!SSL_is_init_finished(rtp->ssl)) { SSL_shutdown(rtp->dtls.ssl);
return; rtp->dtls.connection = AST_RTP_DTLS_CONNECTION_NEW;
} }
SSL_shutdown(rtp->ssl); if (rtp->rtcp && SSL_is_init_finished(rtp->rtcp->dtls.ssl)) {
rtp->connection = AST_RTP_DTLS_CONNECTION_NEW; SSL_shutdown(rtp->rtcp->dtls.ssl);
rtp->rtcp->dtls.connection = AST_RTP_DTLS_CONNECTION_NEW;
}
} }
static enum ast_rtp_dtls_connection ast_rtp_dtls_get_connection(struct ast_rtp_instance *instance) static enum ast_rtp_dtls_connection ast_rtp_dtls_get_connection(struct ast_rtp_instance *instance)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
return rtp->connection; return rtp->dtls.connection;
} }
static enum ast_rtp_dtls_setup ast_rtp_dtls_get_setup(struct ast_rtp_instance *instance) static enum ast_rtp_dtls_setup ast_rtp_dtls_get_setup(struct ast_rtp_instance *instance)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
return rtp->dtls_setup; return rtp->dtls.dtls_setup;
} }
static void ast_rtp_dtls_set_setup(struct ast_rtp_instance *instance, enum ast_rtp_dtls_setup setup) static void dtls_set_setup(enum ast_rtp_dtls_setup *dtls_setup, enum ast_rtp_dtls_setup setup, SSL *ssl)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); enum ast_rtp_dtls_setup old = *dtls_setup;
enum ast_rtp_dtls_setup old = rtp->dtls_setup;
switch (setup) { switch (setup) {
case AST_RTP_DTLS_SETUP_ACTIVE: case AST_RTP_DTLS_SETUP_ACTIVE:
rtp->dtls_setup = AST_RTP_DTLS_SETUP_PASSIVE; *dtls_setup = AST_RTP_DTLS_SETUP_PASSIVE;
break; break;
case AST_RTP_DTLS_SETUP_PASSIVE: case AST_RTP_DTLS_SETUP_PASSIVE:
rtp->dtls_setup = AST_RTP_DTLS_SETUP_ACTIVE; *dtls_setup = AST_RTP_DTLS_SETUP_ACTIVE;
break; break;
case AST_RTP_DTLS_SETUP_ACTPASS: case AST_RTP_DTLS_SETUP_ACTPASS:
/* We can't respond to an actpass setup with actpass ourselves... so respond with active, as we can initiate connections */ /* We can't respond to an actpass setup with actpass ourselves... so respond with active, as we can initiate connections */
if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTPASS) { if (*dtls_setup == AST_RTP_DTLS_SETUP_ACTPASS) {
rtp->dtls_setup = AST_RTP_DTLS_SETUP_ACTIVE; *dtls_setup = AST_RTP_DTLS_SETUP_ACTIVE;
} }
break; break;
case AST_RTP_DTLS_SETUP_HOLDCONN: case AST_RTP_DTLS_SETUP_HOLDCONN:
rtp->dtls_setup = AST_RTP_DTLS_SETUP_HOLDCONN; *dtls_setup = AST_RTP_DTLS_SETUP_HOLDCONN;
break; break;
default: default:
/* This should never occur... if it does exit early as we don't know what state things are in */ /* This should never occur... if it does exit early as we don't know what state things are in */
@ -1024,46 +1090,64 @@ static void ast_rtp_dtls_set_setup(struct ast_rtp_instance *instance, enum ast_r
} }
/* If the setup state did not change we go on as if nothing happened */ /* If the setup state did not change we go on as if nothing happened */
if (old == rtp->dtls_setup) { if (old == *dtls_setup) {
return; return;
} }
/* If they don't want us to establish a connection wait until later */ /* If they don't want us to establish a connection wait until later */
if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_HOLDCONN) { if (*dtls_setup == AST_RTP_DTLS_SETUP_HOLDCONN) {
return; return;
} }
if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) { if (*dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) {
SSL_set_connect_state(rtp->ssl); SSL_set_connect_state(ssl);
} else if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) { } else if (*dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
SSL_set_accept_state(rtp->ssl); SSL_set_accept_state(ssl);
} else { } else {
return; return;
} }
} }
static void ast_rtp_dtls_set_setup(struct ast_rtp_instance *instance, enum ast_rtp_dtls_setup setup)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (rtp->dtls.ssl) {
dtls_set_setup(&rtp->dtls.dtls_setup, setup, rtp->dtls.ssl);
}
if (rtp->rtcp && rtp->rtcp->dtls.ssl) {
dtls_set_setup(&rtp->rtcp->dtls.dtls_setup, setup, rtp->rtcp->dtls.ssl);
}
}
static void ast_rtp_dtls_set_fingerprint(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash, const char *fingerprint) static void ast_rtp_dtls_set_fingerprint(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash, const char *fingerprint)
{ {
char *tmp = ast_strdupa(fingerprint), *value; char *tmp = ast_strdupa(fingerprint), *value;
int pos = 0; int pos = 0;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (hash != AST_RTP_DTLS_HASH_SHA1) { if (hash != AST_RTP_DTLS_HASH_SHA1 && hash != AST_RTP_DTLS_HASH_SHA256) {
return; return;
} }
rtp->remote_hash = hash;
while ((value = strsep(&tmp, ":")) && (pos != (EVP_MAX_MD_SIZE - 1))) { while ((value = strsep(&tmp, ":")) && (pos != (EVP_MAX_MD_SIZE - 1))) {
sscanf(value, "%02x", (unsigned int*)&rtp->remote_fingerprint[pos++]); sscanf(value, "%02x", (unsigned int*)&rtp->remote_fingerprint[pos++]);
} }
} }
static const char *ast_rtp_dtls_get_fingerprint(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash) static enum ast_rtp_dtls_hash ast_rtp_dtls_get_fingerprint_hash(struct ast_rtp_instance *instance)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (hash != AST_RTP_DTLS_HASH_SHA1) { return rtp->local_hash;
return NULL; }
}
static const char *ast_rtp_dtls_get_fingerprint(struct ast_rtp_instance *instance)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
return rtp->local_fingerprint; return rtp->local_fingerprint;
} }
@ -1078,6 +1162,7 @@ static struct ast_rtp_engine_dtls ast_rtp_dtls = {
.get_setup = ast_rtp_dtls_get_setup, .get_setup = ast_rtp_dtls_get_setup,
.set_setup = ast_rtp_dtls_set_setup, .set_setup = ast_rtp_dtls_set_setup,
.set_fingerprint = ast_rtp_dtls_set_fingerprint, .set_fingerprint = ast_rtp_dtls_set_fingerprint,
.get_fingerprint_hash = ast_rtp_dtls_get_fingerprint_hash,
.get_fingerprint = ast_rtp_dtls_get_fingerprint, .get_fingerprint = ast_rtp_dtls_get_fingerprint,
}; };
@ -1121,9 +1206,41 @@ static struct ast_rtp_engine asterisk_rtp_engine = {
#ifdef HAVE_PJPROJECT #ifdef HAVE_PJPROJECT
static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq); 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)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (!dtls->ssl) {
return;
}
if (SSL_is_init_finished(dtls->ssl)) {
SSL_clear(dtls->ssl);
if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
SSL_set_accept_state(dtls->ssl);
} else {
SSL_set_connect_state(dtls->ssl);
}
dtls->connection = AST_RTP_DTLS_CONNECTION_NEW;
}
SSL_do_handshake(dtls->ssl);
dtls_srtp_check_pending(instance, rtp, rtcp);
}
#endif
static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status) static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status)
{ {
struct ast_rtp *rtp = ice->user_data; struct ast_rtp_instance *instance = ice->user_data;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
#ifdef HAVE_OPENSSL_SRTP
dtls_perform_handshake(instance, &rtp->dtls, 0);
if (rtp->rtcp) {
dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
}
#endif
if (!strictrtp) { if (!strictrtp) {
return; return;
@ -1135,7 +1252,8 @@ static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status)
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) 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)
{ {
struct ast_rtp *rtp = ice->user_data; struct ast_rtp_instance *instance = ice->user_data;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
/* Instead of handling the packet here (which really doesn't work with our architecture) we set a bit to indicate that it should be handled after pj_ice_sess_on_rx_pkt /* Instead of handling the packet here (which really doesn't work with our architecture) we set a bit to indicate that it should be handled after pj_ice_sess_on_rx_pkt
* returns */ * returns */
@ -1144,7 +1262,8 @@ static void ast_rtp_on_ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned
static pj_status_t ast_rtp_on_ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) static pj_status_t ast_rtp_on_ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len)
{ {
struct ast_rtp *rtp = ice->user_data; struct ast_rtp_instance *instance = ice->user_data;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
pj_status_t status = PJ_EINVALIDOP; pj_status_t status = PJ_EINVALIDOP;
pj_ssize_t _size = (pj_ssize_t)size; pj_ssize_t _size = (pj_ssize_t)size;
@ -1345,36 +1464,51 @@ static int dtls_srtp_handle_timeout(const void *data)
rtp->dtlstimerid = -1; rtp->dtlstimerid = -1;
ast_mutex_unlock(&rtp->dtls_timer_lock); ast_mutex_unlock(&rtp->dtls_timer_lock);
if (rtp->ssl) { if (rtp->dtls.ssl && !SSL_is_init_finished(rtp->dtls.ssl)) {
DTLSv1_handle_timeout(rtp->ssl); DTLSv1_handle_timeout(rtp->dtls.ssl);
} }
dtls_srtp_check_pending(instance, rtp, 0);
dtls_srtp_check_pending(instance, rtp); if (rtp->rtcp && rtp->rtcp->dtls.ssl && !SSL_is_init_finished(rtp->rtcp->dtls.ssl)) {
DTLSv1_handle_timeout(rtp->rtcp->dtls.ssl);
}
dtls_srtp_check_pending(instance, rtp, 1);
ao2_ref(instance, -1); ao2_ref(instance, -1);
return 0; return 0;
} }
static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct ast_rtp *rtp) static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct ast_rtp *rtp, int rtcp)
{ {
size_t pending = BIO_ctrl_pending(rtp->write_bio); struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls;
size_t pending;
struct timeval dtls_timeout; /* timeout on DTLS */ struct timeval dtls_timeout; /* timeout on DTLS */
if (!dtls->ssl || !dtls->write_bio) {
return;
}
pending = BIO_ctrl_pending(dtls->write_bio);
if (pending > 0) { if (pending > 0) {
char outgoing[pending]; char outgoing[pending];
size_t out; size_t out;
struct ast_sockaddr remote_address = { {0, } }; struct ast_sockaddr remote_address = { {0, } };
int ice; int ice;
ast_rtp_instance_get_remote_address(instance, &remote_address); if (!rtcp) {
ast_rtp_instance_get_remote_address(instance, &remote_address);
} else {
ast_sockaddr_copy(&remote_address, &rtp->rtcp->them);
}
/* If we do not yet know an address to send this to defer it until we do */ /* If we do not yet know an address to send this to defer it until we do */
if (ast_sockaddr_isnull(&remote_address)) { if (ast_sockaddr_isnull(&remote_address)) {
return; return;
} }
out = BIO_read(rtp->write_bio, outgoing, sizeof(outgoing)); out = BIO_read(dtls->write_bio, outgoing, sizeof(outgoing));
/* Stop existing DTLS timer if running */ /* Stop existing DTLS timer if running */
ast_mutex_lock(&rtp->dtls_timer_lock); ast_mutex_lock(&rtp->dtls_timer_lock);
@ -1383,7 +1517,7 @@ static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct as
rtp->dtlstimerid = -1; rtp->dtlstimerid = -1;
} }
if (DTLSv1_get_timeout(rtp->ssl, &dtls_timeout)) { if (DTLSv1_get_timeout(dtls->ssl, &dtls_timeout)) {
int timeout = dtls_timeout.tv_sec * 1000 + dtls_timeout.tv_usec / 1000; int timeout = dtls_timeout.tv_sec * 1000 + dtls_timeout.tv_usec / 1000;
ao2_ref(instance, +1); ao2_ref(instance, +1);
if ((rtp->dtlstimerid = ast_sched_add(rtp->sched, timeout, dtls_srtp_handle_timeout, instance)) < 0) { if ((rtp->dtlstimerid = ast_sched_add(rtp->sched, timeout, dtls_srtp_handle_timeout, instance)) < 0) {
@ -1393,7 +1527,7 @@ static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct as
} }
ast_mutex_unlock(&rtp->dtls_timer_lock); ast_mutex_unlock(&rtp->dtls_timer_lock);
__rtp_sendto(instance, outgoing, out, 0, &remote_address, 0, &ice, 0); __rtp_sendto(instance, outgoing, out, 0, &remote_address, rtcp, &ice, 0);
} }
} }
@ -1402,9 +1536,15 @@ static int dtls_srtp_renegotiate(const void *data)
struct ast_rtp_instance *instance = (struct ast_rtp_instance *)data; struct ast_rtp_instance *instance = (struct ast_rtp_instance *)data;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
SSL_renegotiate(rtp->ssl); SSL_renegotiate(rtp->dtls.ssl);
SSL_do_handshake(rtp->ssl); SSL_do_handshake(rtp->dtls.ssl);
dtls_srtp_check_pending(instance, rtp); dtls_srtp_check_pending(instance, rtp, 0);
if (rtp->rtcp && rtp->rtcp->dtls.ssl) {
SSL_renegotiate(rtp->rtcp->dtls.ssl);
SSL_do_handshake(rtp->rtcp->dtls.ssl);
dtls_srtp_check_pending(instance, rtp, 1);
}
rtp->rekeyid = -1; rtp->rekeyid = -1;
ao2_ref(instance, -1); ao2_ref(instance, -1);
@ -1420,20 +1560,30 @@ static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct as
struct ast_rtp_instance_stats stats = { 0, }; struct ast_rtp_instance_stats stats = { 0, };
/* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
if (SSL_CTX_get_verify_mode(rtp->ssl_ctx) != SSL_VERIFY_NONE) { if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) {
X509 *certificate; X509 *certificate;
if (!(certificate = SSL_get_peer_certificate(rtp->ssl))) { if (!(certificate = SSL_get_peer_certificate(rtp->dtls.ssl))) {
ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance); ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance);
return -1; return -1;
} }
/* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
if (rtp->remote_fingerprint[0]) { if (rtp->remote_fingerprint[0]) {
const EVP_MD *type;
unsigned char fingerprint[EVP_MAX_MD_SIZE]; unsigned char fingerprint[EVP_MAX_MD_SIZE];
unsigned int size; unsigned int size;
if (!X509_digest(certificate, EVP_sha1(), fingerprint, &size) || if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) {
type = EVP_sha1();
} else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) {
type = EVP_sha256();
} else {
ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance);
return -1;
}
if (!X509_digest(certificate, type, fingerprint, &size) ||
!size || !size ||
memcmp(fingerprint, rtp->remote_fingerprint, size)) { memcmp(fingerprint, rtp->remote_fingerprint, size)) {
X509_free(certificate); X509_free(certificate);
@ -1447,21 +1597,21 @@ static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct as
} }
/* Ensure that certificate verification was successful */ /* Ensure that certificate verification was successful */
if (SSL_get_verify_result(rtp->ssl) != X509_V_OK) { if ((rtp->dtls_verify & AST_RTP_DTLS_VERIFY_CERTIFICATE) && SSL_get_verify_result(rtp->dtls.ssl) != X509_V_OK) {
ast_log(LOG_WARNING, "Peer certificate on RTP instance '%p' failed verification test\n", ast_log(LOG_WARNING, "Peer certificate on RTP instance '%p' failed verification test\n",
instance); instance);
return -1; return -1;
} }
/* Produce key information and set up SRTP */ /* Produce key information and set up SRTP */
if (!SSL_export_keying_material(rtp->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { if (!SSL_export_keying_material(rtp->dtls.ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) {
ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n", ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n",
instance); instance);
return -1; return -1;
} }
/* Whether we are acting as a server or client determines where the keys/salts are */ /* Whether we are acting as a server or client determines where the keys/salts are */
if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) { if (rtp->dtls.dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) {
local_key = material; local_key = material;
remote_key = local_key + SRTP_MASTER_KEY_LEN; remote_key = local_key + SRTP_MASTER_KEY_LEN;
local_salt = remote_key + SRTP_MASTER_KEY_LEN; local_salt = remote_key + SRTP_MASTER_KEY_LEN;
@ -1547,50 +1697,51 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
} }
#ifdef HAVE_OPENSSL_SRTP #ifdef HAVE_OPENSSL_SRTP
if (!rtcp) { dtls_srtp_check_pending(instance, rtp, rtcp);
dtls_srtp_check_pending(instance, rtp);
/* If this is an SSL packet pass it to OpenSSL for processing */
if ((*in >= 20) && (*in <= 64)) {
int res = 0;
/* If no SSL session actually exists terminate things */ /* If this is an SSL packet pass it to OpenSSL for processing */
if (!rtp->ssl) { if ((*in >= 20) && (*in <= 64)) {
ast_log(LOG_ERROR, "Received SSL traffic on RTP instance '%p' without an SSL session\n", struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls;
instance); int res = 0;
return -1;
}
/* If we don't yet know if we are active or passive and we receive a packet... we are obviously passive */ /* If no SSL session actually exists terminate things */
if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTPASS) { if (!dtls->ssl) {
rtp->dtls_setup = AST_RTP_DTLS_SETUP_PASSIVE; ast_log(LOG_ERROR, "Received SSL traffic on RTP instance '%p' without an SSL session\n",
SSL_set_accept_state(rtp->ssl); instance);
} return -1;
}
dtls_srtp_check_pending(instance, rtp); /* If we don't yet know if we are active or passive and we receive a packet... we are obviously passive */
if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_ACTPASS) {
dtls->dtls_setup = AST_RTP_DTLS_SETUP_PASSIVE;
SSL_set_accept_state(dtls->ssl);
}
BIO_write(rtp->read_bio, buf, len); dtls_srtp_check_pending(instance, rtp, rtcp);
len = SSL_read(rtp->ssl, buf, len); BIO_write(dtls->read_bio, buf, len);
dtls_srtp_check_pending(instance, rtp); len = SSL_read(dtls->ssl, buf, len);
if (rtp->dtls_failure) { if ((len < 0) && (SSL_get_error(dtls->ssl, len) == SSL_ERROR_SSL)) {
ast_log(LOG_ERROR, "DTLS failure occurred on RTP instance '%p', terminating\n", unsigned long error = ERR_get_error();
instance); ast_log(LOG_ERROR, "DTLS failure occurred on RTP instance '%p' due to reason '%s', terminating\n",
return -1; instance, ERR_reason_error_string(error));
} return -1;
}
if (SSL_is_init_finished(rtp->ssl)) { dtls_srtp_check_pending(instance, rtp, rtcp);
/* Any further connections will be existing since this is now established */
rtp->connection = AST_RTP_DTLS_CONNECTION_EXISTING;
if (SSL_is_init_finished(dtls->ssl)) {
/* Any further connections will be existing since this is now established */
dtls->connection = AST_RTP_DTLS_CONNECTION_EXISTING;
if (!rtcp) {
/* Use the keying material to set up key/salt information */ /* Use the keying material to set up key/salt information */
res = dtls_srtp_setup(rtp, srtp, instance); res = dtls_srtp_setup(rtp, srtp, instance);
} }
return res;
} }
return res;
} }
#endif #endif
@ -1919,7 +2070,7 @@ static int ice_create(struct ast_rtp_instance *instance, struct ast_sockaddr *ad
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, 2,
&ast_rtp_ice_sess_cb, &ufrag, &passwd, NULL, &rtp->ice) == PJ_SUCCESS) { &ast_rtp_ice_sess_cb, &ufrag, &passwd, NULL, &rtp->ice) == PJ_SUCCESS) {
/* Make this available for the callbacks */ /* Make this available for the callbacks */
rtp->ice->user_data = rtp; rtp->ice->user_data = instance;
/* Add all of the available candidates to the ICE session */ /* Add all of the available candidates to the ICE session */
rtp_add_candidates_to_ice(instance, rtp, addr, port, AST_RTP_ICE_COMPONENT_RTP, rtp_add_candidates_to_ice(instance, rtp, addr, port, AST_RTP_ICE_COMPONENT_RTP,
@ -2054,6 +2205,11 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
* RTP instance while it's active. * RTP instance while it's active.
*/ */
close(rtp->rtcp->s); close(rtp->rtcp->s);
#ifdef HAVE_OPENSSL_SRTP
if (rtp->rtcp->dtls.ssl) {
SSL_free(rtp->rtcp->dtls.ssl);
}
#endif
ast_free(rtp->rtcp); ast_free(rtp->rtcp);
} }
@ -2100,8 +2256,8 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
} }
/* Destroy the SSL session if present */ /* Destroy the SSL session if present */
if (rtp->ssl) { if (rtp->dtls.ssl) {
SSL_free(rtp->ssl); SSL_free(rtp->dtls.ssl);
} }
#endif #endif
@ -4166,6 +4322,10 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
} }
#endif #endif
#ifdef HAVE_OPENSSL_SRTP
dtls_setup_rtcp(instance);
#endif
return; return;
} else { } else {
if (rtp->rtcp) { if (rtp->rtcp) {
@ -4181,6 +4341,11 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
rtp->rtcp->schedid = -1; rtp->rtcp->schedid = -1;
} }
close(rtp->rtcp->s); close(rtp->rtcp->s);
#ifdef HAVE_OPENSSL_SRTP
if (rtp->rtcp->dtls.ssl) {
SSL_free(rtp->rtcp->dtls.ssl);
}
#endif
ast_free(rtp->rtcp); ast_free(rtp->rtcp);
rtp->rtcp = NULL; rtp->rtcp = NULL;
} }
@ -4464,13 +4629,18 @@ static int ast_rtp_activate(struct ast_rtp_instance *instance)
{ {
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
if (!rtp->ssl) { /* If ICE negotiation is enabled the DTLS Handshake will be performed upon completion of it */
#ifdef USE_PJPROJECT
if (rtp->ice) {
return 0; return 0;
} }
#endif
SSL_do_handshake(rtp->ssl); dtls_perform_handshake(instance, &rtp->dtls, 0);
dtls_srtp_check_pending(instance, rtp); if (rtp->rtcp) {
dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
}
return 0; return 0;
} }

Loading…
Cancel
Save