diff --git a/CHANGES b/CHANGES index 1d28310a37..0c46e0d6dd 100644 --- a/CHANGES +++ b/CHANGES @@ -79,6 +79,13 @@ The features.conf general section has three new configurable options: For more information on what these options do, see the Asterisk wiki: https://wiki.asterisk.org/wiki/x/W4fAAQ +Channel Drivers +------------------ + +chan_pjsip +------------------ + * New 'media_encryption_optimistic' endpoint setting. This will use SRTP + when possible but does not consider lack of it a failure. ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 12 to Asterisk 13 -------------------- diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index b3dd86a99c..03066d9dc8 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -655,6 +655,8 @@ ;media_encryption=no ; Determines whether res_pjsip will use and enforce ; usage of media encryption for this endpoint (default: ; "no") +;media_encryption_optimistic=no ; Use encryption if possible but don't fail the call + ; if not possible. ;inband_progress=no ; Determines whether chan_pjsip will indicate ringing ; using inband progress (default: "no") ;call_group= ; The numeric pickup groups for a channel (default: "") diff --git a/contrib/ast-db-manage/config/versions/945b1098bdd_add_media_encryption_optimistic_to_pjsip.py b/contrib/ast-db-manage/config/versions/945b1098bdd_add_media_encryption_optimistic_to_pjsip.py new file mode 100644 index 0000000000..7a463f0575 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/945b1098bdd_add_media_encryption_optimistic_to_pjsip.py @@ -0,0 +1,31 @@ +"""add media encryption optimistic to pjsip + +Revision ID: 945b1098bdd +Revises: 15b1430ad6f1 +Create Date: 2014-11-19 07:47:52.490388 + +""" + +# revision identifiers, used by Alembic. +revision = '945b1098bdd' +down_revision = '15b1430ad6f1' + +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('media_encryption_optimistic', yesno_values)) + + +def downgrade(): + op.drop_column('ps_endpoints', 'media_encryption_optimistic') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 0c8d3038ab..37830c7bd5 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -483,6 +483,8 @@ struct ast_sip_media_rtp_configuration { unsigned int srtp_tag_32; /*! Do we use media encryption? what type? */ enum ast_sip_session_media_encryption encryption; + /*! Do we want to optimistically support encryption if possible? */ + unsigned int encryption_optimistic; }; /*! diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index 887d52a1a8..9a133fcb3b 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -73,6 +73,8 @@ struct ast_sip_session_media { struct ast_sip_session_sdp_handler *handler; /*! \brief Holds SRTP information */ struct ast_sdp_srtp *srtp; + /*! \brief What type of encryption is in use on this stream */ + enum ast_sip_session_media_encryption encryption; /*! \brief The media transport in use for this stream */ pj_str_t transport; /*! \brief Stream is on hold by remote side */ diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 510be6d91b..426b9d7963 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -431,6 +431,14 @@ + + Determines whether encryption should be used if possible but does not terminate the + session if not achieved. + + This option only applies if media_encryption is + set to sdes or dtls. + + Determines whether chan_pjsip will indicate ringing using inband progress. @@ -1504,6 +1512,9 @@ + + + diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 126c1614c6..f5c777b350 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -1764,6 +1764,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_setup", "", dtls_handler, dtlssetup_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_fingerprint", "", dtls_handler, dtlsfingerprint_to_str, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "srtp_tag_32", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.srtp_tag_32)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "media_encryption_optimistic", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.encryption_optimistic)); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "redirect_method", "user", redirect_handler, NULL, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "set_var", "", set_var_handler, set_var_to_str, set_var_to_vl, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "message_context", "", OPT_STRINGFIELD_T, 1, STRFLDSET(struct ast_sip_endpoint, message_context)); diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index 74c980d39a..ee9976bc02 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -494,14 +494,41 @@ static void process_ice_attributes(struct ast_sip_session *session, struct ast_s ice->start(session_media->rtp); } +/*! \brief figure out if media stream has crypto lines for sdes */ +static int media_stream_has_crypto(const struct pjmedia_sdp_media *stream) +{ + int i; + + for (i = 0; i < stream->attr_count; i++) { + pjmedia_sdp_attr *attr; + + /* check the stream for the required crypto attribute */ + attr = stream->attr[i]; + if (pj_strcmp2(&attr->name, "crypto")) { + continue; + } + + return 1; + } + + return 0; +} + /*! \brief figure out media transport encryption type from the media transport string */ -static enum ast_sip_session_media_encryption get_media_encryption_type(pj_str_t transport) +static enum ast_sip_session_media_encryption get_media_encryption_type(pj_str_t transport, + const struct pjmedia_sdp_media *stream, unsigned int *optimistic) { RAII_VAR(char *, transport_str, ast_strndup(transport.ptr, transport.slen), ast_free); + + *optimistic = 0; + if (strstr(transport_str, "UDP/TLS")) { return AST_SIP_MEDIA_ENCRYPT_DTLS; } else if (strstr(transport_str, "SAVP")) { return AST_SIP_MEDIA_ENCRYPT_SDES; + } else if (media_stream_has_crypto(stream)) { + *optimistic = 1; + return AST_SIP_MEDIA_ENCRYPT_SDES; } else { return AST_SIP_MEDIA_ENCRYPT_NONE; } @@ -523,22 +550,31 @@ static enum ast_sip_session_media_encryption check_endpoint_media_transport( { enum ast_sip_session_media_encryption incoming_encryption; char transport_end = stream->desc.transport.ptr[stream->desc.transport.slen - 1]; + unsigned int optimistic; if ((transport_end == 'F' && !endpoint->media.rtp.use_avpf) || (transport_end != 'F' && endpoint->media.rtp.use_avpf)) { return AST_SIP_MEDIA_TRANSPORT_INVALID; } - incoming_encryption = get_media_encryption_type(stream->desc.transport); + incoming_encryption = get_media_encryption_type(stream->desc.transport, stream, &optimistic); if (incoming_encryption == endpoint->media.rtp.encryption) { return incoming_encryption; } - if (endpoint->media.rtp.force_avp) { + if (endpoint->media.rtp.force_avp || + endpoint->media.rtp.encryption_optimistic) { return incoming_encryption; } + /* If an optimistic offer has been made but encryption is not enabled consider it as having + * no offer of crypto at all instead of invalid so the session proceeds. + */ + if (optimistic) { + return AST_SIP_MEDIA_ENCRYPT_NONE; + } + return AST_SIP_MEDIA_TRANSPORT_INVALID; } @@ -697,7 +733,7 @@ static int setup_media_encryption(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream) { - switch (session->endpoint->media.rtp.encryption) { + switch (session_media->encryption) { case AST_SIP_MEDIA_ENCRYPT_SDES: if (setup_sdes_srtp(session_media, stream)) { return -1; @@ -726,6 +762,8 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct char host[NI_MAXHOST]; RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr); enum ast_media_type media_type = stream_to_media_type(session_media->stream_type); + enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE; + int res; /* If port is 0, ignore this media stream */ if (!stream->desc.port) { @@ -740,9 +778,12 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct } /* Ensure incoming transport is compatible with the endpoint's configuration */ - if (!session->endpoint->media.rtp.use_received_transport && - check_endpoint_media_transport(session->endpoint, stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) { - return -1; + if (!session->endpoint->media.rtp.use_received_transport) { + encryption = check_endpoint_media_transport(session->endpoint, stream); + + if (encryption == AST_SIP_MEDIA_TRANSPORT_INVALID) { + return -1; + } } ast_copy_pj_str(host, stream->conn ? &stream->conn->addr : &sdp->conn->addr, sizeof(host)); @@ -758,13 +799,27 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct return -1; } - if (session->endpoint->media.rtp.use_received_transport) { + res = setup_media_encryption(session, session_media, sdp, stream); + if (res) { + if (!session->endpoint->media.rtp.encryption_optimistic) { + /* If optimistic encryption is disabled and crypto should have been enabled + * but was not this session must fail. + */ + return -1; + } + /* There is no encryption, sad. */ + session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE; + } + + /* If we've been explicitly configured to use the received transport OR if + * encryption is on and crypto is present use the received transport. + * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending + * on the configuration of the remote endpoint (optimistic themselves or mandatory). + */ + if ((session->endpoint->media.rtp.use_received_transport) || + ((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) { pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport); - } - - if (setup_media_encryption(session, session_media, sdp, stream)) { - return -1; - } + } if (set_caps(session, session_media, stream)) { return 0; @@ -788,7 +843,7 @@ static int add_crypto_to_stream(struct ast_sip_session *session, static const pj_str_t STR_ACTPASS = { "actpass", 7 }; static const pj_str_t STR_HOLDCONN = { "holdconn", 8 }; - switch (session->endpoint->media.rtp.encryption) { + switch (session_media->encryption) { case AST_SIP_MEDIA_ENCRYPT_NONE: case AST_SIP_MEDIA_TRANSPORT_INVALID: break; @@ -923,11 +978,14 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as } media->desc.media = pj_str(session_media->stream_type); - if (session->endpoint->media.rtp.use_received_transport && pj_strlen(&session_media->transport)) { + if (pj_strlen(&session_media->transport)) { + /* If a transport has already been specified use it */ media->desc.transport = session_media->transport; } else { media->desc.transport = pj_str(ast_sdp_get_rtp_profile( - session->endpoint->media.rtp.encryption == AST_SIP_MEDIA_ENCRYPT_SDES, + /* Optimistic encryption places crypto in the normal RTP/AVP profile */ + !session->endpoint->media.rtp.encryption_optimistic && + (session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES), session_media->rtp, session->endpoint->media.rtp.use_avpf, session->endpoint->media.rtp.force_avp)); } @@ -1063,7 +1121,7 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr); enum ast_media_type media_type = stream_to_media_type(session_media->stream_type); char host[NI_MAXHOST]; - int fdno; + int fdno, res; if (!session->channel) { return 1; @@ -1084,10 +1142,18 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a return -1; } - if (setup_media_encryption(session, session_media, remote, 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 + * this session must fail. + */ return -1; } + if (!remote_stream->conn && !remote->conn) { + return 1; + } + ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host)); /* Ensure that the address provided is valid */ @@ -1137,6 +1203,9 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a session_media->remotely_held = 0; } + /* This purposely resets the encryption to the configured in case it gets added later */ + session_media->encryption = session->endpoint->media.rtp.encryption; + return 1; } diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index ebe321958c..85c4b61219 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -1000,6 +1000,7 @@ static int add_session_media(void *obj, void *arg, int flags) if (!session_media) { return CMP_STOP; } + session_media->encryption = session->endpoint->media.rtp.encryption; /* Safe use of strcpy */ strcpy(session_media->stream_type, handler_list->stream_type); ao2_link(session->media, session_media); @@ -1046,6 +1047,8 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, return NULL; } + session->endpoint = ao2_bump(endpoint); + session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp); if (!session->media) { return NULL; @@ -1061,7 +1064,6 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, ast_sip_dialog_set_endpoint(inv_session->dlg, endpoint); pjsip_dlg_inc_session(inv_session->dlg, &session_module); inv_session->mod_data[session_module.id] = ao2_bump(session); - session->endpoint = ao2_bump(endpoint); session->contact = ao2_bump(contact); session->inv_session = inv_session; session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);